# Skill: Frontend JavaScript

Skill ID: `frontend-javascript`

Rules
- Vanilla ES6+ only; no React/Vue/Angular in runtime scripts.
- Entry points can use IIFE; reusable code as ES modules.
- Use dynamic `import()` for lazy features.
- Use descriptive globals with plugin prefix.
- Never render user/API data with unsafe `innerHTML`; use `textContent`.
- If trusted HTML is required, sanitize via controlled parser flow before insertion.
- To clear a container, use `while (el.firstChild) el.removeChild(el.firstChild)` — do not assign empty string to the element's markup property.
- NEVER set inline styles via `.style.*` properties or `style:` attribute in `el()` calls — use CSS classes instead. All visual state must come from SCSS.
- NEVER use deprecated ARIA attributes (`aria-grabbed`, `aria-dropeffect`) — use `aria-roledescription` and live region announcements instead.
- DataTransfer operations must use `text/plain` MIME type, not `text/html`.
- Include nonce in authenticated requests (`X-WP-Nonce` or payload).
- Use `credentials: 'same-origin'` for authenticated fetch.
- Use event delegation; avoid inline handlers.
- Localize user-facing text with WP i18n helpers.
- Handle nonce expiry/auth errors with deterministic UI fallback.
- Check element existence before DOM reads/writes.
- Use shared API client naming (`API.delete()`, no deprecated aliases).
- Use `API.delete()`, not `api.del()`.
- `api.del()` is forbidden.
- Ensure script module mode where required (`type="module"` path).

Accessibility
- Enter/Space support for custom controls.
- Modal focus trap and focus restore on close.
- `aria-live` announcements for dynamic updates.
- Field errors linked via `aria-describedby` and `aria-invalid`.
- Escape closes modals and returns focus.

Mistakes to avoid
| Mistake | Fix |
|---|---|
| Missing nonce/header | Include nonce in every authenticated request |
| Missing `credentials: 'same-origin'` | Add credentials for authenticated fetch |
| Unsafe `innerHTML` | Use `textContent` or sanitized trusted template |
| `.innerHTML = ''` to clear elements | Use `while (el.firstChild) el.removeChild(el.firstChild)` |
| `.style.*` or `style:` in `el()` | Use CSS classes — all styling lives in SCSS |
| Deprecated `aria-grabbed`/`aria-dropeffect` | Use modern ARIA patterns with live announcements |
| `text/html` in DataTransfer | Use `text/plain` MIME type |
| Hardcoded JS strings | Use i18n wrappers |
| No keyboard handlers | Add Enter/Space handlers |
| No modal focus restore | Restore focus to trigger element |
| `api.del()` usage | Use `API.delete()` |
| Missing `type="module"` handling | Enqueue script as module where required |
| Direct DOM access without checks | Guard element lookups |
| Inline ad hoc notices | Use shared Toast/status component |
| Missing user feedback on action | Return/render success or error consistently |
| Grid table columns: all percentages + `auto` on Actions | Leftover grid space inflates `auto`/`max-content` columns. Always use `1fr` on the primary content column (Title) so it absorbs extra space — utility columns (Actions, Characters) stay compact at any viewport width |
