jdi-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +209 -0
- package/ARCHITECTURE.md +210 -0
- package/COMMANDS.md +241 -0
- package/CREATE-EXAMPLE.md +385 -0
- package/CREATE.md +315 -0
- package/EXTENSION.md +141 -0
- package/LICENSE +21 -0
- package/MEMORY.md +471 -0
- package/PORTABILITY.md +438 -0
- package/README.md +789 -0
- package/bin/git-hooks/post-commit +16 -0
- package/bin/git-hooks/pre-commit +21 -0
- package/bin/jdi-build.ps1 +381 -0
- package/bin/jdi-build.sh +332 -0
- package/bin/jdi-doctor.ps1 +403 -0
- package/bin/jdi-doctor.sh +400 -0
- package/bin/jdi-install-caveman.ps1 +97 -0
- package/bin/jdi-install-caveman.sh +99 -0
- package/bin/jdi-install-playwright.ps1 +319 -0
- package/bin/jdi-install-playwright.sh +284 -0
- package/bin/jdi-install.ps1 +154 -0
- package/bin/jdi-install.sh +132 -0
- package/bin/jdi-uninstall.ps1 +309 -0
- package/bin/jdi-uninstall.sh +264 -0
- package/bin/jdi-update.ps1 +215 -0
- package/bin/jdi-update.sh +209 -0
- package/bin/jdi.js +460 -0
- package/bin/lib/jdi-monitor.ps1 +66 -0
- package/bin/lib/jdi-monitor.sh +74 -0
- package/bin/lib/jdi-truncate.ps1 +96 -0
- package/bin/lib/jdi-truncate.sh +99 -0
- package/bin/lib/ui.js +197 -0
- package/core/agents/jdi-adopter.md +465 -0
- package/core/agents/jdi-architect.md +894 -0
- package/core/agents/jdi-asker.md +153 -0
- package/core/agents/jdi-bootstrap.md +247 -0
- package/core/agents/jdi-planner.md +254 -0
- package/core/agents/jdi-researcher.md +303 -0
- package/core/commands/jdi-adopt.md +155 -0
- package/core/commands/jdi-bootstrap.md +81 -0
- package/core/commands/jdi-create.md +80 -0
- package/core/commands/jdi-discuss.md +80 -0
- package/core/commands/jdi-do.md +200 -0
- package/core/commands/jdi-loop.md +315 -0
- package/core/commands/jdi-new.md +131 -0
- package/core/commands/jdi-plan.md +73 -0
- package/core/commands/jdi-ship.md +146 -0
- package/core/commands/jdi-verify.md +159 -0
- package/core/skills/clean-code/SKILL.md +261 -0
- package/core/skills/dry/SKILL.md +150 -0
- package/core/skills/frontend-rules/SKILL.md +386 -0
- package/core/skills/frontend-validator/SKILL.md +567 -0
- package/core/skills/kiss/SKILL.md +178 -0
- package/core/skills/solid/SKILL.md +281 -0
- package/core/skills/yagni/SKILL.md +207 -0
- package/core/templates/agent.md +72 -0
- package/core/templates/doer-specialist.md +216 -0
- package/core/templates/reviewer-specialist.md +405 -0
- package/core/templates/skill.md +66 -0
- package/package.json +70 -0
- package/runtimes/antigravity/agents.md +74 -0
- package/runtimes/antigravity/skills/clean-code/SKILL.md +252 -0
- package/runtimes/antigravity/skills/dry/SKILL.md +141 -0
- package/runtimes/antigravity/skills/frontend-rules/SKILL.md +376 -0
- package/runtimes/antigravity/skills/frontend-validator/SKILL.md +559 -0
- package/runtimes/antigravity/skills/jdi-adopt/SKILL.md +155 -0
- package/runtimes/antigravity/skills/jdi-adopter/SKILL.md +436 -0
- package/runtimes/antigravity/skills/jdi-architect/SKILL.md +872 -0
- package/runtimes/antigravity/skills/jdi-asker/SKILL.md +125 -0
- package/runtimes/antigravity/skills/jdi-asker/references/context-template.md +34 -0
- package/runtimes/antigravity/skills/jdi-asker/references/decision-format.md +19 -0
- package/runtimes/antigravity/skills/jdi-asker/scripts/find_phase_dir.sh +25 -0
- package/runtimes/antigravity/skills/jdi-bootstrap/SKILL.md +81 -0
- package/runtimes/antigravity/skills/jdi-create/SKILL.md +80 -0
- package/runtimes/antigravity/skills/jdi-discuss/SKILL.md +80 -0
- package/runtimes/antigravity/skills/jdi-discuss/scripts/run_command.sh +62 -0
- package/runtimes/antigravity/skills/jdi-do/SKILL.md +200 -0
- package/runtimes/antigravity/skills/jdi-loop/SKILL.md +315 -0
- package/runtimes/antigravity/skills/jdi-new/SKILL.md +131 -0
- package/runtimes/antigravity/skills/jdi-plan/SKILL.md +73 -0
- package/runtimes/antigravity/skills/jdi-planner/SKILL.md +225 -0
- package/runtimes/antigravity/skills/jdi-researcher/SKILL.md +274 -0
- package/runtimes/antigravity/skills/jdi-ship/SKILL.md +146 -0
- package/runtimes/antigravity/skills/jdi-verify/SKILL.md +159 -0
- package/runtimes/antigravity/skills/kiss/SKILL.md +169 -0
- package/runtimes/antigravity/skills/solid/SKILL.md +272 -0
- package/runtimes/antigravity/skills/yagni/SKILL.md +198 -0
- package/runtimes/claude/CLAUDE.md +91 -0
- package/runtimes/claude/agents/jdi-adopter.md +430 -0
- package/runtimes/claude/agents/jdi-architect.md +864 -0
- package/runtimes/claude/agents/jdi-asker.md +119 -0
- package/runtimes/claude/agents/jdi-bootstrap.md +213 -0
- package/runtimes/claude/agents/jdi-planner.md +221 -0
- package/runtimes/claude/agents/jdi-researcher.md +269 -0
- package/runtimes/claude/commands/jdi-adopt.md +155 -0
- package/runtimes/claude/commands/jdi-bootstrap.md +81 -0
- package/runtimes/claude/commands/jdi-create.md +80 -0
- package/runtimes/claude/commands/jdi-discuss.md +80 -0
- package/runtimes/claude/commands/jdi-do.md +200 -0
- package/runtimes/claude/commands/jdi-loop.md +315 -0
- package/runtimes/claude/commands/jdi-new.md +131 -0
- package/runtimes/claude/commands/jdi-plan.md +73 -0
- package/runtimes/claude/commands/jdi-ship.md +146 -0
- package/runtimes/claude/commands/jdi-verify.md +159 -0
- package/runtimes/claude/settings.example.json +132 -0
- package/runtimes/claude/skills/clean-code/SKILL.md +247 -0
- package/runtimes/claude/skills/dry/SKILL.md +136 -0
- package/runtimes/claude/skills/frontend-rules/SKILL.md +369 -0
- package/runtimes/claude/skills/frontend-validator/SKILL.md +553 -0
- package/runtimes/claude/skills/kiss/SKILL.md +164 -0
- package/runtimes/claude/skills/solid/SKILL.md +267 -0
- package/runtimes/claude/skills/yagni/SKILL.md +193 -0
- package/runtimes/copilot/agents/jdi-adopter.agent.md +430 -0
- package/runtimes/copilot/agents/jdi-architect.agent.md +864 -0
- package/runtimes/copilot/agents/jdi-asker.agent.md +119 -0
- package/runtimes/copilot/agents/jdi-bootstrap.agent.md +213 -0
- package/runtimes/copilot/agents/jdi-planner.agent.md +221 -0
- package/runtimes/copilot/agents/jdi-researcher.agent.md +269 -0
- package/runtimes/copilot/copilot-instructions.md +80 -0
- package/runtimes/copilot/prompts/jdi-adopt.prompt.md +155 -0
- package/runtimes/copilot/prompts/jdi-bootstrap.prompt.md +81 -0
- package/runtimes/copilot/prompts/jdi-create.prompt.md +80 -0
- package/runtimes/copilot/prompts/jdi-discuss.prompt.md +80 -0
- package/runtimes/copilot/prompts/jdi-do.prompt.md +200 -0
- package/runtimes/copilot/prompts/jdi-loop.prompt.md +315 -0
- package/runtimes/copilot/prompts/jdi-new.prompt.md +131 -0
- package/runtimes/copilot/prompts/jdi-plan.prompt.md +73 -0
- package/runtimes/copilot/prompts/jdi-ship.prompt.md +146 -0
- package/runtimes/copilot/prompts/jdi-verify.prompt.md +159 -0
- package/runtimes/opencode/AGENTS.md +87 -0
- package/runtimes/opencode/agents/jdi-adopter.md +434 -0
- package/runtimes/opencode/agents/jdi-architect.md +861 -0
- package/runtimes/opencode/agents/jdi-asker.md +123 -0
- package/runtimes/opencode/agents/jdi-bootstrap.md +217 -0
- package/runtimes/opencode/agents/jdi-planner.md +225 -0
- package/runtimes/opencode/agents/jdi-researcher.md +273 -0
- package/runtimes/opencode/commands/jdi-adopt.md +155 -0
- package/runtimes/opencode/commands/jdi-bootstrap.md +81 -0
- package/runtimes/opencode/commands/jdi-create.md +80 -0
- package/runtimes/opencode/commands/jdi-discuss.md +80 -0
- package/runtimes/opencode/commands/jdi-do.md +200 -0
- package/runtimes/opencode/commands/jdi-loop.md +315 -0
- package/runtimes/opencode/commands/jdi-new.md +131 -0
- package/runtimes/opencode/commands/jdi-plan.md +73 -0
- package/runtimes/opencode/commands/jdi-ship.md +146 -0
- package/runtimes/opencode/commands/jdi-verify.md +159 -0
- package/runtimes/opencode/opencode.example.jsonc +169 -0
- package/runtimes/opencode/skills/clean-code/SKILL.md +247 -0
- package/runtimes/opencode/skills/dry/SKILL.md +136 -0
- package/runtimes/opencode/skills/frontend-rules/SKILL.md +369 -0
- package/runtimes/opencode/skills/frontend-validator/SKILL.md +553 -0
- package/runtimes/opencode/skills/kiss/SKILL.md +164 -0
- package/runtimes/opencode/skills/solid/SKILL.md +267 -0
- package/runtimes/opencode/skills/yagni/SKILL.md +193 -0
- package/templates-jdi-folder/config.json +18 -0
- package/templates-jdi-folder/registry.md +31 -0
- package/templates-jdi-folder/reviewers.md +33 -0
- package/templates-jdi-folder/skills-registry.md +32 -0
- package/templates-jdi-folder/specialists.md +39 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frontend-rules
|
|
3
|
+
description: Universal UI/UX and accessibility rules for any web interface. Framework-agnostic - works for React, Vue, Svelte, Solid, Angular, Blazor, Razor, Twig, Jinja, ERB, Blade, and any template engine. Based on WCAG 2.2 AA, Nielsen heuristics, Material/Apple HIG.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: jdi-frontend-rules
|
|
7
|
+
|
|
8
|
+
UI/UX standards that CANNOT be violated - regardless of stack. Concepts > syntax. Works for SPA, SSR, MPA, hybrid, any template engine.
|
|
9
|
+
|
|
10
|
+
## When to apply
|
|
11
|
+
|
|
12
|
+
Whenever code touches a visible human interface:
|
|
13
|
+
|
|
14
|
+
- Files `.tsx, .jsx, .vue, .svelte, .astro, .qwik, .solid` (JS-based components)
|
|
15
|
+
- Files `.razor, .cshtml` (Blazor / Razor Pages / MVC)
|
|
16
|
+
- Files `.html, .twig, .jinja, .j2, .erb, .blade.php, .hbs, .liquid, .mustache, .ejs, .pug` (template engines)
|
|
17
|
+
- CSS/Tailwind/SCSS/Less affecting layout, contrast, focus, or accessibility
|
|
18
|
+
- ARIA / semantic HTML in any language
|
|
19
|
+
|
|
20
|
+
Does NOT apply to: API-only backends, CLI tools, services without UI.
|
|
21
|
+
|
|
22
|
+
## Universal rules (hard gates)
|
|
23
|
+
|
|
24
|
+
### 1. Accessibility - WCAG 2.2 level AA
|
|
25
|
+
|
|
26
|
+
All mandatory. Violation = BLOCK on review.
|
|
27
|
+
|
|
28
|
+
- **Color contrast**:
|
|
29
|
+
- Normal text: minimum 4.5:1 against background
|
|
30
|
+
- Large text (18pt+ or 14pt+ bold): minimum 3:1
|
|
31
|
+
- UI components and graphics: minimum 3:1
|
|
32
|
+
- Verify in hover/focus/disabled states too
|
|
33
|
+
- **Visible focus**: never `outline: none` or `outline: 0` without a replacement. Focus must be perceptible in strong light and on a cheap monitor. `:focus-visible` is the standard
|
|
34
|
+
- **Keyboard navigation**: 100% of interactions reachable via keyboard. Tab follows logical visual order. No trap (modal without Esc, dropdown without Escape/arrows)
|
|
35
|
+
- **Semantic HTML first**:
|
|
36
|
+
- `<button>` for action (even if styled as a link)
|
|
37
|
+
- `<a href>` for navigation (even if styled as a button)
|
|
38
|
+
- `<form>` for forms (Enter submits, native validation works)
|
|
39
|
+
- Headings in order (`h1` -> `h2` -> `h3`, no level skipping)
|
|
40
|
+
- `<nav>, <main>, <header>, <footer>, <aside>, <section>, <article>` when appropriate
|
|
41
|
+
- `<ul>/<ol>` for lists, not repeated `<div>`
|
|
42
|
+
- **ARIA when needed**:
|
|
43
|
+
- Icon-only button: `aria-label="descriptive action"`
|
|
44
|
+
- Form error: `role="alert"` or `aria-live="assertive"`
|
|
45
|
+
- Loading region: `aria-busy="true"` + `aria-live="polite"`
|
|
46
|
+
- Toggle/expand: `aria-expanded="true|false"` + `aria-controls`
|
|
47
|
+
- Modal: `role="dialog"` + `aria-modal="true"` + `aria-labelledby`
|
|
48
|
+
- Tooltip: `aria-describedby`
|
|
49
|
+
- ARIA NEVER REPLACES semantic HTML. ARIA only complements
|
|
50
|
+
- **Skip link**: first tab order offers "Skip to main content"
|
|
51
|
+
- **Minimum touch target**: 44x44 CSS px (Apple HIG / WCAG 2.5.5). Increase on mobile with `padding`, not margin
|
|
52
|
+
- **Color is not the only indicator**:
|
|
53
|
+
- Red error needs icon OR explicit text
|
|
54
|
+
- Colored link needs underline OR different visual weight
|
|
55
|
+
- Active nav state needs border/weight, not just color
|
|
56
|
+
- Color blindness affects 8% of men. Always color + shape + text
|
|
57
|
+
- **Form labels**: every `<input>, <textarea>, <select>` with:
|
|
58
|
+
- Associated `<label htmlFor="id">`, OR
|
|
59
|
+
- `aria-label="..."`, OR
|
|
60
|
+
- `aria-labelledby="id-of-another-element"`
|
|
61
|
+
- Placeholder DOES NOT count as label (disappears when user types)
|
|
62
|
+
- **Associated error**: field error connected via `aria-describedby="error-id"`. Error text has matching `id`
|
|
63
|
+
- **Language**: `<html lang="pt-BR">` declared. Without this screen reader reads English for pt-BR text
|
|
64
|
+
- **prefers-reduced-motion**: respect. Animations should disable via `@media (prefers-reduced-motion: reduce)`
|
|
65
|
+
|
|
66
|
+
### 2. Mandatory states on every UI surface
|
|
67
|
+
|
|
68
|
+
Every screen/component that loads or mutates data must cover all 5:
|
|
69
|
+
|
|
70
|
+
- **Loading**:
|
|
71
|
+
- Skeleton with shape matching real content (avoids layout shift)
|
|
72
|
+
- OR spinner/progress if shape unpredictable
|
|
73
|
+
- Visible minimum 200ms (avoids flash that flickers)
|
|
74
|
+
- Maximum 10s without extra feedback - after that explain "almost there" or offer cancel
|
|
75
|
+
- **Empty**:
|
|
76
|
+
- Never empty screen. Always message + icon + actionable CTA
|
|
77
|
+
- Text orients next step: "Create your first X by clicking Y"
|
|
78
|
+
- Don't confuse empty with error (empty is success, error is failure)
|
|
79
|
+
- **Error**:
|
|
80
|
+
- Specific message: what failed + how to fix
|
|
81
|
+
- NEVER "Something went wrong" / "Unexpected error" as final message to user
|
|
82
|
+
- Visible recovery action: retry, go back, contact support
|
|
83
|
+
- Inline validation errors + general message if needed
|
|
84
|
+
- **Success**:
|
|
85
|
+
- Visible confirmation - toast is OK for non-destructive actions
|
|
86
|
+
- Destructive action (delete, transfer) needs undo OR prior confirmation
|
|
87
|
+
- Toast disappears in 4-6s; destructive actions with undo have 5-10s
|
|
88
|
+
- **Disabled**:
|
|
89
|
+
- ALWAYS with visible reason: tooltip, helper text, or hint
|
|
90
|
+
- Silent disabled = bug ("why can't I click?")
|
|
91
|
+
- Consider alternative: don't disable, let click and show specific error
|
|
92
|
+
|
|
93
|
+
### 3. Feedback timing - Nielsen heuristics
|
|
94
|
+
|
|
95
|
+
- **< 100ms**: feels instant. No indicator needed
|
|
96
|
+
- **100ms to 1s**: acceptable without indicator. Cursor may change to waiting
|
|
97
|
+
- **1s to 10s**: progress indicator required. Spinner or bar
|
|
98
|
+
- **> 10s**: progress + estimated time OR allow cancel
|
|
99
|
+
- **Indeterminate and > 30s**: offer background notification, free up UI
|
|
100
|
+
- **Optimistic UI**: like/save/toggle - update UI immediately, rollback if it fails
|
|
101
|
+
|
|
102
|
+
### 4. Forms - universal patterns
|
|
103
|
+
|
|
104
|
+
- **Validation**:
|
|
105
|
+
- On blur for individual field (after user leaves field)
|
|
106
|
+
- On submit for general validation
|
|
107
|
+
- On change ONLY for positive feedback (e.g., password strength)
|
|
108
|
+
- NEVER on keypress for error ("missing character") - tiring
|
|
109
|
+
- **Inline errors**: next to/below the wrong field, WITH optional general top-of-form message. Never just top
|
|
110
|
+
- **Required**:
|
|
111
|
+
- Red asterisk is NOT enough - add text "(required)" or a clear mark before submit
|
|
112
|
+
- Indicate required at design time, not after the error
|
|
113
|
+
- Modern alternative: mark optionals ("Phone (optional)")
|
|
114
|
+
- **Autocomplete**: correct `autocomplete` attribute: `email, current-password, new-password, name, given-name, family-name, tel, postal-code, etc`. Enables browser autofill
|
|
115
|
+
- **Inputmode + type**:
|
|
116
|
+
- `type="email"` shows keyboard with @ on mobile
|
|
117
|
+
- `inputmode="numeric"` for OTP/PIN/ZIP
|
|
118
|
+
- `type="tel"` for phone
|
|
119
|
+
- `type="url"` for URL
|
|
120
|
+
- `type="date"` for date (with fallback if browser doesn't support)
|
|
121
|
+
- **Submit**:
|
|
122
|
+
- DO NOT disable button before user tries - teaches wrong and hides cause
|
|
123
|
+
- Disable ONLY during in-flight request (avoids double submit)
|
|
124
|
+
- Loading state on the button (text + inline spinner)
|
|
125
|
+
- **Password**:
|
|
126
|
+
- "Show password" toggle (eye icon)
|
|
127
|
+
- Always force HTTPS - never send password in plain HTTP
|
|
128
|
+
- Show requirements before user types (8+ chars, etc)
|
|
129
|
+
- **Destructive confirmation**:
|
|
130
|
+
- Irreversible actions (delete account, drop data) require typing name/word or explicit checkbox
|
|
131
|
+
- Plain "are you sure?" modal is insufficient for truly destructive action
|
|
132
|
+
|
|
133
|
+
### 5. Navigation
|
|
134
|
+
|
|
135
|
+
- **Current location**: active nav highlighted (weight + color + indicator). Breadcrumbs in deep hierarchy
|
|
136
|
+
- **Browser back button**: respect history. Modal doesn't use `pushState` without reason. Single-page nav uses router that emits real history
|
|
137
|
+
- **Custom 404**: friendly page with search or sitemap, not blank screen
|
|
138
|
+
- **Logo links home**: universal convention
|
|
139
|
+
- **Search**: if app has search, keyboard shortcut `/` or `Ctrl+K` (convention)
|
|
140
|
+
|
|
141
|
+
### 6. Responsive - mobile-first
|
|
142
|
+
|
|
143
|
+
- **Design starts at 320px** and grows - not the other way around
|
|
144
|
+
- **Breakpoints based on content**, not devices: point where layout breaks, not "iPhone 12"
|
|
145
|
+
- **No horizontal scroll on mobile** (except intentional carousel). Audit with viewport 375px
|
|
146
|
+
- **Touch-friendly spacing**: minimum 8px between clickable targets
|
|
147
|
+
- **Hover-only is bad on mobile**: anything needing hover needs fallback (long press, tap to reveal)
|
|
148
|
+
- **Density**: mobile needs more space than desktop for the same legibility
|
|
149
|
+
|
|
150
|
+
### 7. UX performance - Core Web Vitals
|
|
151
|
+
|
|
152
|
+
- **CLS < 0.1** (Cumulative Layout Shift):
|
|
153
|
+
- `width` + `height` on every `<img>` (avoids jump on load)
|
|
154
|
+
- `font-display: swap` with metric-compatible fallback
|
|
155
|
+
- Reserve space for ads/embeds/skeletons
|
|
156
|
+
- **LCP < 2.5s** (Largest Contentful Paint):
|
|
157
|
+
- Optimized hero image (WebP/AVIF + responsive `srcset`)
|
|
158
|
+
- Critical CSS inline
|
|
159
|
+
- Preload critical resource (`<link rel="preload">`)
|
|
160
|
+
- **INP < 200ms** (Interaction to Next Paint):
|
|
161
|
+
- No heavy JS blocking main thread during interaction
|
|
162
|
+
- Debounce on input handlers
|
|
163
|
+
- Web Workers for heavy computation
|
|
164
|
+
- **TTFB < 800ms** (Time To First Byte):
|
|
165
|
+
- Static cache, CDN, lazy loading
|
|
166
|
+
- **Optimistic UI**: already mentioned - update immediately
|
|
167
|
+
|
|
168
|
+
### 8. Visual hierarchy
|
|
169
|
+
|
|
170
|
+
- **1 primary action per view**. Multiple = paralyzed decision (Hicks Law)
|
|
171
|
+
- **Secondary visually smaller**: ghost, outline, or link
|
|
172
|
+
- **Whitespace separates groups** - no little box (border) for everything
|
|
173
|
+
- **Fixed spacing scale**: 4/8/16/24/32/48/64 (multiples of 4 or 8). No `margin: 13px`
|
|
174
|
+
- **Type scale**: max 5-6 sizes in the entire app. More than that = visual chaos
|
|
175
|
+
- **Color palette**:
|
|
176
|
+
- 60-30-10 rule: 60% neutral (background), 30% complementary, 10% accent (CTA)
|
|
177
|
+
- Max 1 brand color + 1 or 2 accents
|
|
178
|
+
- States (success/warn/error) are a separate palette
|
|
179
|
+
- **Alignment**: every element aligned to a grid - not "eyeball"
|
|
180
|
+
|
|
181
|
+
### 9. i18n + l10n
|
|
182
|
+
|
|
183
|
+
- **Zero hardcoded string in markup**. Always a translation key
|
|
184
|
+
- JSX/TSX: don't write pt-BR text directly, use `t("key")` or equivalent
|
|
185
|
+
- Templates: use translate tag (`{% trans %}`, `<t>`, `@Localize`)
|
|
186
|
+
- HTML: separate content from markup
|
|
187
|
+
- **RTL ready** (Arabic, Hebrew):
|
|
188
|
+
- Logical properties: `margin-inline-start` instead of `margin-left`
|
|
189
|
+
- `padding-block` instead of `padding-top`
|
|
190
|
+
- `text-align: start/end` instead of `left/right`
|
|
191
|
+
- `dir="auto"` on fields accepting input in any language
|
|
192
|
+
- **Format by locale**:
|
|
193
|
+
- Dates: `Intl.DateTimeFormat` or backend equivalent
|
|
194
|
+
- Numbers: `Intl.NumberFormat`
|
|
195
|
+
- Currency: never hardcoded "$" - currency comes from locale + value
|
|
196
|
+
- **Pluralization**: ICU MessageFormat or equivalent. Languages have 1, 2, 3+ or more forms (Russian has 4)
|
|
197
|
+
|
|
198
|
+
### 10. UI security - overlap with general rules
|
|
199
|
+
|
|
200
|
+
- **Tokens NEVER in localStorage/sessionStorage**:
|
|
201
|
+
- Vulnerable to XSS. Any malicious script reads everything
|
|
202
|
+
- Safe pattern: httpOnly cookie + SameSite=Strict
|
|
203
|
+
- Token in memory with refresh via cookie is OK for SPAs
|
|
204
|
+
- **Strict CSP**:
|
|
205
|
+
- `script-src 'self'` at minimum - no `unsafe-inline`, no `unsafe-eval`
|
|
206
|
+
- `frame-ancestors 'none'` or whitelist - prevents clickjacking
|
|
207
|
+
- **HTTPS only**:
|
|
208
|
+
- Redirect HTTP -> HTTPS on the server
|
|
209
|
+
- HSTS header with `includeSubDomains`
|
|
210
|
+
- No mixed content (HTTP on HTTPS page)
|
|
211
|
+
- **CSRF**:
|
|
212
|
+
- CSRF token on every form with authenticated side-effect
|
|
213
|
+
- SameSite=Strict cookie helps but isn't enough
|
|
214
|
+
- **External links**:
|
|
215
|
+
- `target="_blank"` ALWAYS with `rel="noopener noreferrer"` (prevents tabnabbing)
|
|
216
|
+
- **dangerouslySetInnerHTML / v-html / @Html.Raw**:
|
|
217
|
+
- Never with user input without sanitization (DOMPurify or backend sanitizer)
|
|
218
|
+
- Prefer semantic parsing (markdown -> AST -> render)
|
|
219
|
+
- **External form action**: never accept a user-controllable `action` URL
|
|
220
|
+
|
|
221
|
+
## Anti-patterns - BLOCK list for reviewer
|
|
222
|
+
|
|
223
|
+
Each item below is automatic violation. Reviewer marks BLOCK + cites rule.
|
|
224
|
+
|
|
225
|
+
| Anti-pattern | Why it is BLOCK |
|
|
226
|
+
|---|---|
|
|
227
|
+
| Button that looks like a link / link that looks like a button | Confuses mental model, violates convention |
|
|
228
|
+
| `<div onclick>` instead of `<button>` | No keyboard, no ARIA, no semantics |
|
|
229
|
+
| `<a href="#">` or `<a href="javascript:">` for action | Becomes a destinationless link - use `<button>` |
|
|
230
|
+
| Modal without Esc close | Keyboard trap - WCAG 2.1.2 |
|
|
231
|
+
| Modal without visible close button | Same reason |
|
|
232
|
+
| Infinite spinner without timeout/fallback | User doesn't know if it's stuck |
|
|
233
|
+
| Auto-play media with sound | WCAG 1.4.2 |
|
|
234
|
+
| Toast as ONLY confirmation of destructive action | Toast disappears - destructive needs persistent |
|
|
235
|
+
| Disabled state without visible reason | "Why doesn't it work?" - UX bug |
|
|
236
|
+
| Generic error "Something went wrong" | Not actionable |
|
|
237
|
+
| Color as ONLY state indicator | Color blindness - WCAG 1.4.1 |
|
|
238
|
+
| Required marked ONLY by color (red border) | Same reason |
|
|
239
|
+
| Required shown ONLY after submit | User didn't know it was required |
|
|
240
|
+
| Form without `<label>` or `aria-label` | WCAG 3.3.2 |
|
|
241
|
+
| Placeholder replacing label | Disappears when user types - WCAG 3.3.2 |
|
|
242
|
+
| Heading skip (h1 -> h3 without h2) | WCAG 1.3.1 |
|
|
243
|
+
| `<img>` without `alt` | WCAG 1.1.1 |
|
|
244
|
+
| `<img alt="image">` or redundant alt "image of X" | Good alt describes content, not media |
|
|
245
|
+
| Text over image without overlay/guaranteed contrast | WCAG 1.4.3 |
|
|
246
|
+
| Animation > 400ms on direct interaction | Perceived as slow |
|
|
247
|
+
| `prefers-reduced-motion` ignored | WCAG 2.3.3 |
|
|
248
|
+
| Outline removed without replacement | WCAG 2.4.7 |
|
|
249
|
+
| Arbitrary positive `tabindex` (`tabindex="5"`) | Breaks natural order - only use 0 and -1 |
|
|
250
|
+
| `lang` absent on `<html>` | Screen reader pronounces wrong |
|
|
251
|
+
| Form action or href with direct user input | Risk of XSS/open redirect |
|
|
252
|
+
| `localStorage.setItem('token', ...)` or similar for credential | XSS risk - use httpOnly cookie |
|
|
253
|
+
|
|
254
|
+
## Procedure (use by agent)
|
|
255
|
+
|
|
256
|
+
### Doer (write/edit)
|
|
257
|
+
|
|
258
|
+
#### Step 1: Detect type of change
|
|
259
|
+
If task touches UI files, load checklist in mind before writing.
|
|
260
|
+
|
|
261
|
+
#### Step 2: For each new component/template
|
|
262
|
+
Apply the checklist of rules 1-10. In particular:
|
|
263
|
+
- Does it cover the 5 states (loading/empty/error/success/disabled)?
|
|
264
|
+
- Semantic HTML first?
|
|
265
|
+
- Visible focus maintained?
|
|
266
|
+
- Contrast OK in light/dark states?
|
|
267
|
+
- Strings via i18n key?
|
|
268
|
+
|
|
269
|
+
#### Step 3: When in architectural doubt
|
|
270
|
+
Consult references:
|
|
271
|
+
- Full WCAG: `references/wcag-checklist.md`
|
|
272
|
+
- States: `references/state-coverage.md`
|
|
273
|
+
- Forms: `references/forms-patterns.md`
|
|
274
|
+
- Anti-patterns explained: `references/anti-patterns.md`
|
|
275
|
+
|
|
276
|
+
### Reviewer (gate 5)
|
|
277
|
+
|
|
278
|
+
#### Step 1: For each modified file in frontend
|
|
279
|
+
Run specific greps based on the type:
|
|
280
|
+
|
|
281
|
+
**JSX/TSX/Vue/Svelte:**
|
|
282
|
+
```bash
|
|
283
|
+
# Button without aria-label and without inner text
|
|
284
|
+
grep -RnE '<button[^>]*>(\s*<[^>]+/?>\s*)+</button>' src/
|
|
285
|
+
|
|
286
|
+
# Input without label
|
|
287
|
+
grep -RnE '<input(?![^>]*aria-label)(?![^>]*id=)' src/
|
|
288
|
+
|
|
289
|
+
# href="#" for action
|
|
290
|
+
grep -RnE 'href="#"' src/
|
|
291
|
+
|
|
292
|
+
# localStorage with token
|
|
293
|
+
grep -RnE 'localStorage\.(set|get)Item.*[Tt]oken' src/
|
|
294
|
+
|
|
295
|
+
# Outline removed
|
|
296
|
+
grep -RnE 'outline\s*:\s*(none|0)' src/
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Server-side templates (Razor/Twig/Blade/ERB/Jinja):**
|
|
300
|
+
Similar greps adapted to the syntax.
|
|
301
|
+
|
|
302
|
+
#### Step 2: Classify
|
|
303
|
+
- Match on violation listed in the table above -> BLOCK
|
|
304
|
+
- Suspicious pattern but not certain -> WARN
|
|
305
|
+
- No match -> PASS on gate 5
|
|
306
|
+
|
|
307
|
+
## Expected inputs
|
|
308
|
+
|
|
309
|
+
- Path of the modified file
|
|
310
|
+
- Diff or complete content of the file
|
|
311
|
+
|
|
312
|
+
## Outputs
|
|
313
|
+
|
|
314
|
+
Does NOT produce its own file. Modifies the parent agent's judgement:
|
|
315
|
+
- Doer chooses NOT to introduce a violation - writes correct code from the start
|
|
316
|
+
- Reviewer marks BLOCK/WARN with rule cited
|
|
317
|
+
|
|
318
|
+
## References
|
|
319
|
+
|
|
320
|
+
- `references/wcag-checklist.md` - WCAG 2.2 AA expanded with code examples
|
|
321
|
+
- `references/state-coverage.md` - Patterns for loading/empty/error/success/disabled across engines
|
|
322
|
+
- `references/forms-patterns.md` - Universal patterns for form validation and UX
|
|
323
|
+
- `references/anti-patterns.md` - Gallery of anti-patterns with wrong example + fix
|
|
324
|
+
|
|
325
|
+
## Anti-patterns of this skill
|
|
326
|
+
|
|
327
|
+
- Applying only to JS stacks - rules work for any template engine
|
|
328
|
+
- Making a rule framework-specific (e.g., "use React.useState") - skill is agnostic
|
|
329
|
+
- Replacing human design code review - skill covers the technically broken, not the aesthetically mediocre
|
|
330
|
+
- Blocking MVP for minor a11y - severity matters, minor is INFO/WARN
|
|
331
|
+
|
|
332
|
+
## Examples
|
|
333
|
+
|
|
334
|
+
### Example 1: Doer receives task "add delete button to ItemCard"
|
|
335
|
+
|
|
336
|
+
Applies skill before writing:
|
|
337
|
+
- Destructive action needs: explicit confirmation, focus returns to origin button after modal closes, descriptive label (`aria-label="Delete item Order #123"`), undo if possible
|
|
338
|
+
- Loading state during request
|
|
339
|
+
- Error state with retry
|
|
340
|
+
- Success state with undo (5s timer)
|
|
341
|
+
- Use `<button>`, not `<a>` or `<div>`
|
|
342
|
+
- Tab order: button -> modal opens -> modal buttons navigable -> Esc closes -> focus returns
|
|
343
|
+
|
|
344
|
+
Code written comes out compliant.
|
|
345
|
+
|
|
346
|
+
### Example 2: Reviewer finds `<input>` without label
|
|
347
|
+
|
|
348
|
+
Marks gate 5 as BLOCK:
|
|
349
|
+
```
|
|
350
|
+
[BLOCK] src/components/LoginForm.tsx:42
|
|
351
|
+
Rule: Forms - Form labels (WCAG 1.3.1, 3.3.2)
|
|
352
|
+
Violation: <input type="email" /> without <label>, aria-label, or aria-labelledby
|
|
353
|
+
Fix: <label htmlFor="email">Email</label><input id="email" type="email" />
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Example 3: Reviewer finds `localStorage.setItem('access_token', token)`
|
|
357
|
+
|
|
358
|
+
Marks gate 5 as BLOCK:
|
|
359
|
+
```
|
|
360
|
+
[BLOCK] src/auth/store.ts:18
|
|
361
|
+
Rule: UI Security - Tokens in storage
|
|
362
|
+
Violation: localStorage.setItem with authentication token
|
|
363
|
+
Why: vulnerable to XSS - any malicious script on the page reads the token
|
|
364
|
+
Fix: backend sets httpOnly cookie SameSite=Strict; frontend doesn't touch token
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Example 4: Backend-only API (Python FastAPI)
|
|
368
|
+
|
|
369
|
+
Skill is not loaded. PROJECT.md has `frontend.has_frontend: false`. Nothing happens.
|