wize-dev-kit 0.1.3 → 0.1.5
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/CHANGELOG.md +59 -1
- package/package.json +1 -1
- package/src/app-overlay/playbooks/apple-hig.md +112 -0
- package/src/app-overlay/playbooks/detox-maestro.md +179 -0
- package/src/app-overlay/playbooks/device-matrix.md +121 -0
- package/src/app-overlay/playbooks/material-design-3.md +135 -0
- package/src/app-overlay/playbooks/mobile-perf-budgets.md +145 -0
- package/src/app-overlay/playbooks/permissions-ux.md +147 -0
- package/src/app-overlay/playbooks/touch-targets-and-gestures.md +127 -0
- package/src/app-overlay/stack-catalog.md +178 -0
- package/src/orchestrator-skills/wize-help/skill.md +9 -2
- package/src/orchestrator-skills/wize-orchestrator/persona.md +9 -1
- package/src/web-overlay/playbooks/playwright-vitest.md +211 -0
- package/src/web-overlay/playbooks/responsive-breakpoints.md +104 -0
- package/src/web-overlay/playbooks/semantic-html.md +114 -0
- package/src/web-overlay/playbooks/wcag-aa.md +97 -0
- package/src/web-overlay/playbooks/web-perf-budgets.md +140 -0
- package/src/web-overlay/stack-catalog.md +208 -0
- package/tools/installer/setup-helpers.js +105 -0
- package/tools/installer/wize-cli.js +26 -9
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
---
|
|
2
|
+
playbook: playwright-vitest
|
|
3
|
+
owner: wize-agent-test-architect # Hawkeye
|
|
4
|
+
applies_when: web-overlay active
|
|
5
|
+
status: ready
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Web Test Stack — Hawkeye Playbook
|
|
9
|
+
|
|
10
|
+
Two tools, one rule per: **Vitest** for fast, isolated, deterministic. **Playwright** for end-to-end through real browsers.
|
|
11
|
+
|
|
12
|
+
## 1. What goes where
|
|
13
|
+
|
|
14
|
+
| Test type | Tool | Speed | What it proves |
|
|
15
|
+
|---|---|---|---|
|
|
16
|
+
| Pure function | Vitest | µs | A unit of logic does its job. |
|
|
17
|
+
| React/Vue component | Vitest + Testing Library | ms | A component renders and reacts correctly. |
|
|
18
|
+
| Hook / store / state | Vitest | ms | Side-effect-free transitions are correct. |
|
|
19
|
+
| Service / API call | Vitest + MSW | ms | Client glue handles 2xx/4xx/5xx. |
|
|
20
|
+
| Integration (multi-component) | Vitest + Testing Library | tens of ms | Real interactions on a fake DOM. |
|
|
21
|
+
| **User journey (E2E)** | Playwright | seconds | A real browser can complete the flow. |
|
|
22
|
+
| Visual regression | Playwright (toHaveScreenshot) | seconds | UI didn't shift unintentionally. |
|
|
23
|
+
| Cross-browser | Playwright (chromium/firefox/webkit) | minutes | Behavior is consistent. |
|
|
24
|
+
|
|
25
|
+
**Default split (Hawkeye `tea-design.md`):** 70/20/10 — unit/integration/E2E.
|
|
26
|
+
|
|
27
|
+
## 2. Vitest setup (per project)
|
|
28
|
+
|
|
29
|
+
```jsonc
|
|
30
|
+
// package.json
|
|
31
|
+
{
|
|
32
|
+
"scripts": {
|
|
33
|
+
"test": "vitest",
|
|
34
|
+
"test:run": "vitest run",
|
|
35
|
+
"test:coverage": "vitest run --coverage"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"vitest": "^2",
|
|
39
|
+
"@vitest/coverage-v8": "^2",
|
|
40
|
+
"@testing-library/dom": "^10",
|
|
41
|
+
"@testing-library/user-event": "^14",
|
|
42
|
+
"happy-dom": "^15",
|
|
43
|
+
"msw": "^2"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// vitest.config.ts
|
|
50
|
+
import { defineConfig } from 'vitest/config';
|
|
51
|
+
export default defineConfig({
|
|
52
|
+
test: {
|
|
53
|
+
environment: 'happy-dom', // jsdom if you need broader DOM compat
|
|
54
|
+
globals: true,
|
|
55
|
+
setupFiles: ['./test/setup.ts'],
|
|
56
|
+
coverage: { reporter: ['text', 'lcov'], thresholds: { lines: 80, branches: 75 } }
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Test naming
|
|
62
|
+
|
|
63
|
+
`{feature}.spec.ts` for code. Stories sit beside them: `{Story}.story.tsx`. Avoid `__tests__` folders — co-locate.
|
|
64
|
+
|
|
65
|
+
### Patterns
|
|
66
|
+
|
|
67
|
+
- Use **Testing Library** queries by role first (`getByRole('button', { name: /save/i })`). Fall back to `getByLabelText`, then `getByText`, then `getByTestId` only as a last resort.
|
|
68
|
+
- Use **user-event v14**, never `fireEvent`, for interactions. It mimics real user input.
|
|
69
|
+
- For network: **MSW** at the network boundary. Don't mock `fetch` per-test.
|
|
70
|
+
- Snapshot tests are fine for small, stable shapes (a discriminated union, a normalized payload). Not for component trees.
|
|
71
|
+
|
|
72
|
+
## 3. Playwright setup
|
|
73
|
+
|
|
74
|
+
```jsonc
|
|
75
|
+
// package.json
|
|
76
|
+
{
|
|
77
|
+
"scripts": {
|
|
78
|
+
"e2e": "playwright test",
|
|
79
|
+
"e2e:ui": "playwright test --ui",
|
|
80
|
+
"e2e:headed": "playwright test --headed"
|
|
81
|
+
},
|
|
82
|
+
"devDependencies": { "@playwright/test": "^1.50" }
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// playwright.config.ts
|
|
88
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
89
|
+
export default defineConfig({
|
|
90
|
+
testDir: './e2e',
|
|
91
|
+
fullyParallel: true,
|
|
92
|
+
retries: process.env.CI ? 2 : 0,
|
|
93
|
+
reporter: process.env.CI ? [['github'], ['html']] : 'list',
|
|
94
|
+
use: {
|
|
95
|
+
baseURL: process.env.E2E_BASE_URL ?? 'http://localhost:3000',
|
|
96
|
+
trace: 'on-first-retry',
|
|
97
|
+
screenshot: 'only-on-failure',
|
|
98
|
+
video: 'retain-on-failure'
|
|
99
|
+
},
|
|
100
|
+
projects: [
|
|
101
|
+
{ name: 'chromium', use: devices['Desktop Chrome'] },
|
|
102
|
+
{ name: 'firefox', use: devices['Desktop Firefox'] },
|
|
103
|
+
{ name: 'webkit', use: devices['Desktop Safari'] },
|
|
104
|
+
{ name: 'mobile-ios', use: devices['iPhone 14'] },
|
|
105
|
+
{ name: 'mobile-android', use: devices['Pixel 7'] }
|
|
106
|
+
],
|
|
107
|
+
webServer: process.env.CI ? undefined : {
|
|
108
|
+
command: 'npm run dev', port: 3000, reuseExistingServer: true
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Page Object Model (light)
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
// e2e/pages/signin.page.ts
|
|
117
|
+
export class SignInPage {
|
|
118
|
+
constructor(public page: Page) {}
|
|
119
|
+
goto = () => this.page.goto('/signin');
|
|
120
|
+
emailField = () => this.page.getByLabel(/email/i);
|
|
121
|
+
passwordField = () => this.page.getByLabel(/password/i);
|
|
122
|
+
submit = () => this.page.getByRole('button', { name: /sign in/i });
|
|
123
|
+
async signIn(email: string, password: string) {
|
|
124
|
+
await this.emailField().fill(email);
|
|
125
|
+
await this.passwordField().fill(password);
|
|
126
|
+
await this.submit().click();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Keep POMs **thin**. They abstract selectors, not flows.
|
|
132
|
+
|
|
133
|
+
### Selectors — Hawkeye's rules
|
|
134
|
+
|
|
135
|
+
1. `getByRole` first.
|
|
136
|
+
2. `getByLabel` for forms.
|
|
137
|
+
3. `getByText` for content.
|
|
138
|
+
4. `data-testid` only when none of the above work (last resort).
|
|
139
|
+
5. **Never** CSS classes for selectors. Class names are styling concerns; they shift.
|
|
140
|
+
|
|
141
|
+
### Determinism
|
|
142
|
+
|
|
143
|
+
- Tests must pass 100× in a row locally. If they don't, they're flaky — fix the test or the code, never `retry: 5`.
|
|
144
|
+
- Use Playwright's auto-waiting (`expect(locator).toBeVisible()`). Never `setTimeout`.
|
|
145
|
+
- Mock time with `page.clock` when timing affects the test.
|
|
146
|
+
|
|
147
|
+
## 4. Visual regression (selective)
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
test('hero matches baseline', async ({ page }) => {
|
|
151
|
+
await page.goto('/');
|
|
152
|
+
await expect(page.getByTestId('hero')).toHaveScreenshot('hero.png', {
|
|
153
|
+
maxDiffPixelRatio: 0.005
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Use for **stable, high-value** regions (logged-out marketing, primary nav, design-system showcase). Avoid on dynamic content (timestamps, user-generated text).
|
|
159
|
+
|
|
160
|
+
## 5. CI integration
|
|
161
|
+
|
|
162
|
+
```yaml
|
|
163
|
+
# .github/workflows/test.yml (sketch)
|
|
164
|
+
jobs:
|
|
165
|
+
unit:
|
|
166
|
+
runs-on: ubuntu-latest
|
|
167
|
+
steps:
|
|
168
|
+
- uses: actions/checkout@v4
|
|
169
|
+
- uses: actions/setup-node@v4
|
|
170
|
+
with: { node-version: '20', cache: 'npm' }
|
|
171
|
+
- run: npm ci
|
|
172
|
+
- run: npm run test:run -- --reporter=junit --outputFile=test-results/junit.xml
|
|
173
|
+
- uses: actions/upload-artifact@v4
|
|
174
|
+
with: { name: junit, path: test-results/ }
|
|
175
|
+
e2e:
|
|
176
|
+
runs-on: ubuntu-latest
|
|
177
|
+
needs: unit
|
|
178
|
+
steps:
|
|
179
|
+
- uses: actions/checkout@v4
|
|
180
|
+
- uses: actions/setup-node@v4
|
|
181
|
+
with: { node-version: '20', cache: 'npm' }
|
|
182
|
+
- run: npm ci
|
|
183
|
+
- run: npx playwright install --with-deps
|
|
184
|
+
- run: npm run build && npm run start &
|
|
185
|
+
- run: npx wait-on http://localhost:3000
|
|
186
|
+
- run: npm run e2e
|
|
187
|
+
- if: failure()
|
|
188
|
+
uses: actions/upload-artifact@v4
|
|
189
|
+
with: { name: playwright-report, path: playwright-report/ }
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## 6. Coverage targets (advisory)
|
|
193
|
+
|
|
194
|
+
- **Lines:** 80%+ unit/integration.
|
|
195
|
+
- **Branches:** 75%+ on logic modules.
|
|
196
|
+
- **E2E:** every critical user journey from the PRD. Not every page.
|
|
197
|
+
|
|
198
|
+
Coverage is a signal, not a goal. Hawkeye prefers one good E2E over five low-value unit tests.
|
|
199
|
+
|
|
200
|
+
## 7. Anti-patterns Hawkeye fails fast on
|
|
201
|
+
|
|
202
|
+
- `test.skip()` left in main.
|
|
203
|
+
- `if (!process.env.SLOW) test.skip()` — flaky shielded as flag.
|
|
204
|
+
- Snapshots of entire DOM trees.
|
|
205
|
+
- `wait(1000)` — non-deterministic by definition.
|
|
206
|
+
- Mocking the unit under test (then you're testing the mock).
|
|
207
|
+
- Selecting by class name.
|
|
208
|
+
|
|
209
|
+
## 8. Hand-off
|
|
210
|
+
|
|
211
|
+
For every story Tony slices, Hawkeye's `tea-design.md` declares: unit count / integration count / E2E count, fixtures needed, network mocks, environment. Shuri implements against that contract.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
playbook: responsive-breakpoints
|
|
3
|
+
owner: wize-agent-ux-designer # Mantis
|
|
4
|
+
applies_when: web-overlay active
|
|
5
|
+
status: ready
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Responsive Layout — Mantis Playbook
|
|
9
|
+
|
|
10
|
+
Mobile-first by default. Container queries first; media queries when you have to. Fluid typography over stepped scales. **Design from content, not from device.**
|
|
11
|
+
|
|
12
|
+
## 1. Breakpoint stack (mobile-first)
|
|
13
|
+
|
|
14
|
+
Token names matter more than the px value — name by intent, not device.
|
|
15
|
+
|
|
16
|
+
| Token | Min width | Rough viewport | Typical layout shift |
|
|
17
|
+
|---|---|---|---|
|
|
18
|
+
| `xs` | 0 | phone portrait | single column, full-bleed images |
|
|
19
|
+
| `sm` | 480px | phone landscape, phablet | wider gutters, two-col on cards |
|
|
20
|
+
| `md` | 768px | tablet portrait | two-column page, persistent header |
|
|
21
|
+
| `lg` | 1024px | tablet landscape, small laptop | three-column, sidebar appears |
|
|
22
|
+
| `xl` | 1280px | desktop | max canvas, multi-region pages |
|
|
23
|
+
| `2xl` | 1536px | large desktop | larger gutters, bigger type |
|
|
24
|
+
|
|
25
|
+
**Do not** add a breakpoint per device. Add one when the design actually breaks.
|
|
26
|
+
|
|
27
|
+
## 2. Container queries (preferred for components)
|
|
28
|
+
|
|
29
|
+
Components should respond to **their own container width**, not the page width. Same card behaves differently in a sidebar vs hero region.
|
|
30
|
+
|
|
31
|
+
```css
|
|
32
|
+
.card { container-type: inline-size; }
|
|
33
|
+
|
|
34
|
+
@container (min-width: 360px) {
|
|
35
|
+
.card { display: grid; grid-template-columns: 80px 1fr; }
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Use a media query only when the change is page-level (nav layout, page chrome).
|
|
40
|
+
|
|
41
|
+
## 3. Fluid typography
|
|
42
|
+
|
|
43
|
+
Step scales jump at breakpoints; fluid scales grow continuously. Use `clamp()` to avoid both extremes.
|
|
44
|
+
|
|
45
|
+
```css
|
|
46
|
+
:root {
|
|
47
|
+
/* clamp(min, preferred, max) */
|
|
48
|
+
--step-0: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
|
|
49
|
+
--step-1: clamp(1.125rem, 1.0rem + 0.7vw, 1.375rem);
|
|
50
|
+
--step-2: clamp(1.4rem, 1.2rem + 1.0vw, 1.8rem);
|
|
51
|
+
--step-3: clamp(1.75rem, 1.4rem + 1.7vw, 2.5rem);
|
|
52
|
+
--step-4: clamp(2.2rem, 1.6rem + 2.5vw, 3.5rem);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Use **rem** for type, **em** for spacing within type contexts. Never hardcode px on body text.
|
|
57
|
+
|
|
58
|
+
## 4. Layout primitives (use these names in design specs)
|
|
59
|
+
|
|
60
|
+
| Primitive | What it does | When |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| **Stack** | Vertical rhythm, configurable gap | Default layout. |
|
|
63
|
+
| **Cluster** | Inline flex with wrap | Tag rows, action groups. |
|
|
64
|
+
| **Switcher** | Switches row → column under threshold | Hero/sidebar pair. |
|
|
65
|
+
| **Sidebar** | Sticky content beside main | Lists + detail panes. |
|
|
66
|
+
| **Grid** | Auto-fit grid of equal cards | Card walls. |
|
|
67
|
+
| **Cover** | Header / centered content / footer | Landing sections. |
|
|
68
|
+
| **Frame** | Aspect-ratio container | Videos, hero images. |
|
|
69
|
+
|
|
70
|
+
These map directly to CSS — keep specs talking about primitives, not pixels.
|
|
71
|
+
|
|
72
|
+
## 5. Image strategy
|
|
73
|
+
|
|
74
|
+
- **Always set `width` and `height` attributes** (prevents CLS).
|
|
75
|
+
- Serve **AVIF → WebP → JPEG/PNG** via `<picture>`.
|
|
76
|
+
- Use `srcset` + `sizes` for art-direction and density.
|
|
77
|
+
- Lazy-load below-the-fold images: `loading="lazy" decoding="async"`.
|
|
78
|
+
- Hero images: preload (`<link rel="preload" as="image">`) the LCP candidate.
|
|
79
|
+
|
|
80
|
+
## 6. Touch + pointer
|
|
81
|
+
|
|
82
|
+
- Touch targets: **24×24 CSS px minimum** (WCAG 2.5.8); promote to 44×44 for primary CTAs.
|
|
83
|
+
- Hover states must have a non-hover equivalent (touch users don't hover).
|
|
84
|
+
- Use `@media (hover: hover)` to gate hover-only affordances.
|
|
85
|
+
|
|
86
|
+
## 7. Dark mode
|
|
87
|
+
|
|
88
|
+
- Honor `prefers-color-scheme` by default.
|
|
89
|
+
- Provide an in-app override stored in `localStorage`.
|
|
90
|
+
- Token-driven: every color comes from a semantic token (`--surface`, `--text`, `--accent`), not a raw hex.
|
|
91
|
+
- Test contrast in **both** modes.
|
|
92
|
+
|
|
93
|
+
## 8. Motion
|
|
94
|
+
|
|
95
|
+
- Honor `prefers-reduced-motion`. Default to motion only when the user opted in or the system says it's OK.
|
|
96
|
+
- Durations: 100ms (micro) / 200ms (transition) / 300ms (page-level). Anything > 300ms feels slow.
|
|
97
|
+
|
|
98
|
+
## 9. Smoke walk before sign-off
|
|
99
|
+
|
|
100
|
+
Walk every page at: **320px**, **768px**, **1024px**, **1440px**, **portrait/landscape**, plus **200% zoom**. No horizontal scroll. No clipped content. No focus loss.
|
|
101
|
+
|
|
102
|
+
## 10. Hand-off to Shuri
|
|
103
|
+
|
|
104
|
+
Specs should reference tokens (`--space-3`, `--step-2`, `--surface-1`) — not raw values. Tony picks the implementation (Tailwind, CSS modules, Vanilla Extract); Shuri implements against the tokens Mantis defined in `.wize/solutioning/design-system/tokens.json`.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
playbook: semantic-html
|
|
3
|
+
owner: wize-agent-ux-designer # Mantis (with Tony for component patterns)
|
|
4
|
+
applies_when: web-overlay active
|
|
5
|
+
status: ready
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Semantic HTML — Mantis Playbook
|
|
9
|
+
|
|
10
|
+
The first rule: **use the right element**. ARIA is for when the platform doesn't ship the element you need; it is not a substitute for `<button>`.
|
|
11
|
+
|
|
12
|
+
## 1. Landmarks (one per page, usually)
|
|
13
|
+
|
|
14
|
+
| Element | Role | Purpose |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `<header>` (top-level) | `banner` | Site/app chrome. |
|
|
17
|
+
| `<nav>` | `navigation` | Primary nav. Label multiples with `aria-label`. |
|
|
18
|
+
| `<main>` | `main` | Unique main content. One per page. |
|
|
19
|
+
| `<aside>` | `complementary` | Related-to-main content. |
|
|
20
|
+
| `<footer>` (top-level) | `contentinfo` | Site/app footer. |
|
|
21
|
+
| `<search>` (HTML 2024) | `search` | Search regions. |
|
|
22
|
+
|
|
23
|
+
Screen reader users navigate by landmarks. Use them; label duplicates.
|
|
24
|
+
|
|
25
|
+
## 2. Headings
|
|
26
|
+
|
|
27
|
+
- One `<h1>` per page (the page topic).
|
|
28
|
+
- Headings define **structure**, not size. Never skip levels for style — adjust style with CSS.
|
|
29
|
+
- Sections that need their own heading use `<section>` with `aria-labelledby` pointing at the heading.
|
|
30
|
+
|
|
31
|
+
## 3. The 12 elements you must reach for first
|
|
32
|
+
|
|
33
|
+
| Need | Element | Why not the alternative |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| Clickable action | `<button>` | `<div onclick>` has no keyboard, no focus, no role. |
|
|
36
|
+
| Navigation link | `<a href>` | `<button>` doesn't open URLs; routers should still hit `<a>`. |
|
|
37
|
+
| Form field | `<input>`/`<textarea>`/`<select>` with `<label>` | DIY inputs lose autofill, IME, mobile keyboards. |
|
|
38
|
+
| Yes/No state | `<input type="checkbox">` | Toggles aren't divs. |
|
|
39
|
+
| List of things | `<ul>`/`<ol>`/`<li>` | Readers announce count and position. |
|
|
40
|
+
| Tabular data | `<table>` with `<thead>`/`<th scope>` | Real semantics for real data. (Not for layout.) |
|
|
41
|
+
| Disclosure | `<details>`/`<summary>` | Built-in keyboard + state. |
|
|
42
|
+
| Modal | `<dialog>` with `.showModal()` | Focus trap + Escape are free. |
|
|
43
|
+
| Tooltips | `<button aria-describedby>` + visible region | Hover-only tooltips fail touch + keyboard. |
|
|
44
|
+
| Date input | `<input type="date">` (when locale-OK) | Native pickers feel right on mobile. |
|
|
45
|
+
| Range | `<input type="range">` | Keyboard arrows + ARIA are built in. |
|
|
46
|
+
| Progress | `<progress>` / `<meter>` | Semantic value + max. |
|
|
47
|
+
|
|
48
|
+
## 4. Forms — the contract
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<form>
|
|
52
|
+
<div class="field">
|
|
53
|
+
<label for="email">Email address</label>
|
|
54
|
+
<input id="email" name="email" type="email" autocomplete="email"
|
|
55
|
+
required aria-describedby="email-help email-error">
|
|
56
|
+
<p id="email-help">We'll never share your email.</p>
|
|
57
|
+
<p id="email-error" role="alert" hidden>Please enter a valid email.</p>
|
|
58
|
+
</div>
|
|
59
|
+
<button type="submit">Sign up</button>
|
|
60
|
+
</form>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Rules:
|
|
64
|
+
1. Label is **always visible** (not placeholder-only).
|
|
65
|
+
2. `autocomplete` on every input that has a meaningful value.
|
|
66
|
+
3. `required` + a `aria-describedby` error region.
|
|
67
|
+
4. Error region uses `role="alert"` or `aria-live="assertive"` only at submit; live regions on every keystroke are noisy.
|
|
68
|
+
5. Submit triggers via `Enter` automatically when inside `<form>` — keep it that way.
|
|
69
|
+
|
|
70
|
+
## 5. ARIA — when (rarely) to use it
|
|
71
|
+
|
|
72
|
+
Use ARIA only when:
|
|
73
|
+
- You can't use the native element (rare).
|
|
74
|
+
- You're building a widget HTML doesn't ship (combobox, treeview, slider with custom track).
|
|
75
|
+
|
|
76
|
+
The five ARIA rules:
|
|
77
|
+
1. **Don't** use ARIA if native works.
|
|
78
|
+
2. Don't change native semantics with `role` unless you must.
|
|
79
|
+
3. All ARIA controls must be keyboard accessible.
|
|
80
|
+
4. Don't use `role="presentation"` on focusable elements.
|
|
81
|
+
5. Interactive elements must have an accessible name.
|
|
82
|
+
|
|
83
|
+
## 6. Common widget patterns (with minimum ARIA)
|
|
84
|
+
|
|
85
|
+
| Widget | Native option | Custom shape (minimum) |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| Accordion | `<details>`/`<summary>` | `<button aria-expanded aria-controls="id">` + `<div id="id" hidden>`. |
|
|
88
|
+
| Tabs | — (no native) | `role="tablist"` > `role="tab" aria-selected aria-controls` ; `role="tabpanel" aria-labelledby`. |
|
|
89
|
+
| Combobox | `<input list>` for simple | ARIA 1.2 combobox pattern; non-trivial. Use a tested lib. |
|
|
90
|
+
| Toggle | `<input type="checkbox">` with switch styling | `<button role="switch" aria-checked>`. |
|
|
91
|
+
| Toast | — | `role="status" aria-live="polite"` (info); `role="alert"` (errors). |
|
|
92
|
+
| Tooltip | `title` (limited) | `<button aria-describedby="tip">` + `<div role="tooltip" id="tip">`. |
|
|
93
|
+
| Modal | `<dialog>` | `role="dialog" aria-modal="true" aria-labelledby`; focus trap; restore focus on close. |
|
|
94
|
+
| Menu | — | `role="menu"` > `role="menuitem"` ; arrow keys + Escape; rarely needed for app UIs. |
|
|
95
|
+
|
|
96
|
+
## 7. Lint and audit
|
|
97
|
+
|
|
98
|
+
- **eslint-plugin-jsx-a11y** for React/JSX projects.
|
|
99
|
+
- **eslint-plugin-vuejs-accessibility** for Vue.
|
|
100
|
+
- **axe-core** at runtime (browser ext + CI).
|
|
101
|
+
- **Manual screen-reader walk** on critical flows before each major release.
|
|
102
|
+
|
|
103
|
+
## 8. Don'ts (most common in the wild)
|
|
104
|
+
|
|
105
|
+
- `<div onclick>` instead of `<button>`.
|
|
106
|
+
- `outline: none` on `:focus` without replacement.
|
|
107
|
+
- Placeholders as labels.
|
|
108
|
+
- Icon-only buttons without `aria-label` (or visible text).
|
|
109
|
+
- `aria-label` on a `<div>` that isn't interactive (does nothing).
|
|
110
|
+
- `tabindex` > 0 (breaks focus order).
|
|
111
|
+
|
|
112
|
+
## 9. Hand-off
|
|
113
|
+
|
|
114
|
+
When Mantis writes UX specs, every interactive element is named with its **HTML element name**, not its style ("button: Continue", not "blue rectangle"). This forces semantic discussion upstream of CSS.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
playbook: wcag-aa
|
|
3
|
+
owner: wize-agent-ux-designer # Mantis
|
|
4
|
+
applies_when: web-overlay active
|
|
5
|
+
status: ready
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# WCAG 2.2 AA — Mantis Playbook
|
|
9
|
+
|
|
10
|
+
Use this when you're shaping UX and need a concrete accessibility floor. Treat AA as the **minimum**; promote items to AAA when the product audience demands it (e.g., gov, healthcare).
|
|
11
|
+
|
|
12
|
+
## 1. Quick principles (POUR)
|
|
13
|
+
|
|
14
|
+
| Principle | What it means | Most-broken in the wild |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| **Perceivable** | Content can be seen/heard by everyone. | Color contrast, alt text. |
|
|
17
|
+
| **Operable** | UI works with keyboard + assistive tech. | Focus traps, no-skip nav. |
|
|
18
|
+
| **Understandable** | Predictable, error-tolerant. | Form errors with no explanation. |
|
|
19
|
+
| **Robust** | Works across browsers and ATs. | Custom controls without ARIA roles. |
|
|
20
|
+
|
|
21
|
+
## 2. Mandatory AA checklist for every screen
|
|
22
|
+
|
|
23
|
+
### Perceivable
|
|
24
|
+
- [ ] Text contrast **≥ 4.5:1** (normal) / **≥ 3:1** (large ≥ 18pt / 14pt-bold).
|
|
25
|
+
- [ ] Non-text UI (icons, focus rings, form borders) contrast **≥ 3:1** against background.
|
|
26
|
+
- [ ] Every `<img>` has `alt`; decorative images use `alt=""` + `aria-hidden`.
|
|
27
|
+
- [ ] Video has captions; audio-only content has a transcript.
|
|
28
|
+
- [ ] Don't convey meaning by color alone (error = red + icon + label).
|
|
29
|
+
- [ ] Page works at **200% zoom** without loss of content or function.
|
|
30
|
+
|
|
31
|
+
### Operable
|
|
32
|
+
- [ ] **Every interactive element is reachable and operable by keyboard.** Tab order matches visual order.
|
|
33
|
+
- [ ] Visible focus indicator (≥ 3:1 contrast) on **all** focusable elements. Don't override with `outline:none` without replacement.
|
|
34
|
+
- [ ] "Skip to main content" link is the first focusable element on the page.
|
|
35
|
+
- [ ] No keyboard trap. `Esc` closes modals; modals trap focus *within* the modal until closed.
|
|
36
|
+
- [ ] Touch targets ≥ **24×24 CSS px** (WCAG 2.2 SC 2.5.8). Promote to 44×44 for primary actions.
|
|
37
|
+
- [ ] No motion-triggered actions without a non-motion fallback.
|
|
38
|
+
- [ ] User can pause/stop/hide content that auto-plays > 5s.
|
|
39
|
+
|
|
40
|
+
### Understandable
|
|
41
|
+
- [ ] `lang` attribute set on `<html>` (and on inline lang switches).
|
|
42
|
+
- [ ] Form fields have **persistent, visible labels** (not placeholder-only).
|
|
43
|
+
- [ ] Required fields marked with text + visual cue (not asterisk alone).
|
|
44
|
+
- [ ] Error messages: identify the field, describe the fix, link back to the input.
|
|
45
|
+
- [ ] Consistent navigation across pages (same links in same order).
|
|
46
|
+
|
|
47
|
+
### Robust
|
|
48
|
+
- [ ] Use semantic HTML before reaching for ARIA. (See `semantic-html.md`.)
|
|
49
|
+
- [ ] Custom widgets have proper ARIA role + state + value.
|
|
50
|
+
- [ ] No duplicate IDs on a page.
|
|
51
|
+
- [ ] Status messages (toasts, async results) announced via `role="status"` or `aria-live="polite"`.
|
|
52
|
+
|
|
53
|
+
## 3. WCAG 2.2 specifics (newer SCs to remember)
|
|
54
|
+
|
|
55
|
+
| SC | Topic | Practical implication |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| 2.4.11 | Focus not obscured (minimum) | Sticky headers/footers must not cover the focused element. |
|
|
58
|
+
| 2.5.7 | Dragging movements | Anything drag-only needs a non-drag alternative. |
|
|
59
|
+
| 2.5.8 | Target size (minimum) | 24×24 CSS px (excluding ample spacing). |
|
|
60
|
+
| 3.2.6 | Consistent help | Help link in the same relative location across pages. |
|
|
61
|
+
| 3.3.7 | Redundant entry | Don't ask the user to retype info already submitted in the same session. |
|
|
62
|
+
| 3.3.8 | Accessible authentication | Don't require cognitive function tests for login (no "type these letters" puzzles). |
|
|
63
|
+
|
|
64
|
+
## 4. Tools to run (every PR that touches UI)
|
|
65
|
+
|
|
66
|
+
| Tool | What it catches | Notes |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| **axe DevTools** (browser ext) | ~57% of WCAG issues automatically. | First line. Free. |
|
|
69
|
+
| **Lighthouse → Accessibility** | Subset of axe; ships in Chrome. | CI-friendly. |
|
|
70
|
+
| **pa11y / pa11y-ci** | Headless audit in pipelines. | Add to PR check. |
|
|
71
|
+
| **NVDA** (Windows) or **VoiceOver** (Mac/iOS) | Manual screen reader pass. | At least the critical flows. |
|
|
72
|
+
| **Keyboard-only walk** | Focus order, traps, hidden controls. | 5 minutes per screen. |
|
|
73
|
+
|
|
74
|
+
## 5. Common patterns Mantis ships with
|
|
75
|
+
|
|
76
|
+
- **Skip link:** first focusable element, hidden visually until focused.
|
|
77
|
+
- **Form pattern:** `<label>` always visible, error region with `aria-live="polite"` next to each input.
|
|
78
|
+
- **Modal:** `role="dialog" aria-modal="true"`, focus trapped, focus restored to opener on close.
|
|
79
|
+
- **Disclosure/Accordion:** `<button aria-expanded>` toggling a `<div id>`; not raw `<div onclick>`.
|
|
80
|
+
- **Toast:** `role="status"`, dismiss-on-press + auto-dismiss after ≥ 10s for readers.
|
|
81
|
+
- **Async loading:** announce via `aria-live="polite"`, never silent.
|
|
82
|
+
|
|
83
|
+
## 6. Hand-off note for Tony and Shuri
|
|
84
|
+
|
|
85
|
+
Tony, when picking a component library, evaluate it on:
|
|
86
|
+
- Real focus indicators (not just outline override).
|
|
87
|
+
- Form components that ship labels + error regions out of the box.
|
|
88
|
+
- Modals that handle focus return.
|
|
89
|
+
|
|
90
|
+
Shuri, when implementing, **never delete the focus ring** without replacing it with a custom one of equal contrast. Every PR touching UI runs axe + a keyboard walk.
|
|
91
|
+
|
|
92
|
+
## 7. When AA isn't enough
|
|
93
|
+
|
|
94
|
+
Promote to AAA when:
|
|
95
|
+
- Product targets users with low vision (consider 7:1 contrast).
|
|
96
|
+
- Public-sector or healthcare deployment.
|
|
97
|
+
- Compliance (Section 508, EN 301 549, ADA).
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
playbook: web-perf-budgets
|
|
3
|
+
owner: wize-agent-test-architect # Hawkeye (with Tony on stack picks)
|
|
4
|
+
applies_when: web-overlay active
|
|
5
|
+
status: ready
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Web Performance Budgets — Hawkeye Playbook
|
|
9
|
+
|
|
10
|
+
Budgets are decisions, not measurements. Set them once, enforce them on every PR.
|
|
11
|
+
|
|
12
|
+
## 1. Core Web Vitals — the floor
|
|
13
|
+
|
|
14
|
+
| Metric | Good | Needs work | Poor |
|
|
15
|
+
|---|---|---|---|
|
|
16
|
+
| **LCP** (Largest Contentful Paint) | ≤ 2.5s | 2.5–4.0s | > 4.0s |
|
|
17
|
+
| **INP** (Interaction to Next Paint) | ≤ 200ms | 200–500ms | > 500ms |
|
|
18
|
+
| **CLS** (Cumulative Layout Shift) | ≤ 0.1 | 0.1–0.25 | > 0.25 |
|
|
19
|
+
|
|
20
|
+
Measure on **mobile, slow 4G, mid-range device** — not your laptop on fiber.
|
|
21
|
+
|
|
22
|
+
## 2. Baseline budgets (mid-range mobile, 3G fast)
|
|
23
|
+
|
|
24
|
+
| Resource | Budget | How to enforce |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| **Total transferred** (initial route) | ≤ 200 KB compressed | `lighthouse-ci` budget. |
|
|
27
|
+
| **JS** | ≤ 100 KB compressed (≤ 300 KB uncompressed) | Bundle analyzer + CI check. |
|
|
28
|
+
| **CSS** | ≤ 30 KB | Critical-CSS inlined; rest deferred. |
|
|
29
|
+
| **Images (above the fold)** | ≤ 100 KB total | AVIF/WebP, `<picture>`. |
|
|
30
|
+
| **Fonts** | ≤ 30 KB (2 weights subset) | `font-display: swap`, preload. |
|
|
31
|
+
| **Third-party requests** | ≤ 5 | Audit on every release. |
|
|
32
|
+
|
|
33
|
+
These are starting points. Tony tunes them per project after the PRD nails the audience and target device.
|
|
34
|
+
|
|
35
|
+
## 3. JS budget — what eats it
|
|
36
|
+
|
|
37
|
+
- Framework runtime (React: ~50 KB gz, Vue: ~30 KB, Svelte: 0 KB at runtime).
|
|
38
|
+
- Router.
|
|
39
|
+
- State manager.
|
|
40
|
+
- Date library (date-fns ≪ moment).
|
|
41
|
+
- Form library.
|
|
42
|
+
- UI components from `node_modules`.
|
|
43
|
+
|
|
44
|
+
Audit with `npx vite-bundle-visualizer` (Vite) or `next-bundle-analyzer` (Next). Remove the top 3 by size every quarter.
|
|
45
|
+
|
|
46
|
+
## 4. Image strategy
|
|
47
|
+
|
|
48
|
+
1. **Format:** AVIF first → WebP → JPEG/PNG fallback.
|
|
49
|
+
2. **Width:** never serve more than 2× the rendered CSS width.
|
|
50
|
+
3. **Lazy:** below-the-fold = `loading="lazy"`. LCP image = `<link rel="preload" as="image">`.
|
|
51
|
+
4. **Dimensions:** always set `width` and `height` HTML attrs to prevent CLS.
|
|
52
|
+
5. **Responsive:** `<picture>` with `srcset` + `sizes`, or a framework-native `<Image>`.
|
|
53
|
+
|
|
54
|
+
## 5. Font strategy
|
|
55
|
+
|
|
56
|
+
- Self-host. Don't fetch from CDN for performance work.
|
|
57
|
+
- Subset to the characters you actually use (Glyphhanger / fonttools).
|
|
58
|
+
- `font-display: swap` so text appears instantly.
|
|
59
|
+
- Preload the primary font; defer secondaries.
|
|
60
|
+
- Cap variable fonts at 2 axes.
|
|
61
|
+
|
|
62
|
+
## 6. Third parties (the silent killers)
|
|
63
|
+
|
|
64
|
+
For every third-party script, answer:
|
|
65
|
+
|
|
66
|
+
1. **What user value does it deliver?**
|
|
67
|
+
2. **Can it load after `load` event?** (analytics, A/B, chat)
|
|
68
|
+
3. **Can it load only on interaction?** (chat widgets, video embeds)
|
|
69
|
+
4. **Has it been replaced by a smaller alternative?** (e.g., Plausible vs GA)
|
|
70
|
+
5. **Is it sandboxed in a `<iframe>` or web worker?**
|
|
71
|
+
|
|
72
|
+
Audit list in `.wize/planning/web/perf-budget.md`. Remove or defer one every release until they all justify their bytes.
|
|
73
|
+
|
|
74
|
+
## 7. Critical rendering path
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
<head>
|
|
78
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
79
|
+
<link rel="preload" as="image" href="/hero.avif" type="image/avif">
|
|
80
|
+
<link rel="preload" as="font" type="font/woff2" href="/fonts/Inter.var.woff2" crossorigin>
|
|
81
|
+
<style>/* critical above-the-fold CSS, ≤ 14 KB */</style>
|
|
82
|
+
<link rel="stylesheet" href="/main.css" media="print" onload="this.media='all'">
|
|
83
|
+
</head>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- Critical CSS inline (≤ 14 KB so the first packet contains it).
|
|
87
|
+
- Everything else deferred or preloaded.
|
|
88
|
+
- No render-blocking external scripts above content.
|
|
89
|
+
|
|
90
|
+
## 8. INP — interaction responsiveness
|
|
91
|
+
|
|
92
|
+
INP measures **the slowest interaction** a user has across the session, not the average. One bad input handler ruins your score.
|
|
93
|
+
|
|
94
|
+
Hot fixes:
|
|
95
|
+
1. Yield to the main thread inside long handlers (`await scheduler.yield()` or `setTimeout(0)`).
|
|
96
|
+
2. Move heavy work to a worker (`react-server-components`, `comlink`).
|
|
97
|
+
3. Debounce typing-driven recalculations.
|
|
98
|
+
4. Use `<input type=*` natives; custom inputs are slower.
|
|
99
|
+
5. Defer hydration (Astro Islands, Qwik, React Server Components).
|
|
100
|
+
|
|
101
|
+
## 9. Build-time enforcement
|
|
102
|
+
|
|
103
|
+
```jsonc
|
|
104
|
+
// lighthouserc.json (lighthouse-ci)
|
|
105
|
+
{
|
|
106
|
+
"ci": {
|
|
107
|
+
"collect": {
|
|
108
|
+
"url": ["https://staging.example.com/", "https://staging.example.com/dashboard"],
|
|
109
|
+
"settings": { "preset": "perf" }
|
|
110
|
+
},
|
|
111
|
+
"assert": {
|
|
112
|
+
"assertions": {
|
|
113
|
+
"categories:performance": ["error", { "minScore": 0.9 }],
|
|
114
|
+
"first-contentful-paint": ["warn", { "maxNumericValue": 1800 }],
|
|
115
|
+
"largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
|
|
116
|
+
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
|
|
117
|
+
"total-byte-weight": ["error", { "maxNumericValue": 250000 }]
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Wire into CI; gate merges on perf score.
|
|
125
|
+
|
|
126
|
+
## 10. Field measurement
|
|
127
|
+
|
|
128
|
+
Lab tests catch obvious regressions; field tests catch the truth. Add a Web Vitals beacon:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { onLCP, onINP, onCLS } from 'web-vitals';
|
|
132
|
+
const beacon = (m: any) => navigator.sendBeacon('/v', JSON.stringify(m));
|
|
133
|
+
onLCP(beacon); onINP(beacon); onCLS(beacon);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Aggregate by route + device class + connection in your analytics. Look at p75, not avg.
|
|
137
|
+
|
|
138
|
+
## 11. Hand-off
|
|
139
|
+
|
|
140
|
+
For every epic, Hawkeye attaches an NFR report (`tea/nfr/{epic}.md`) with current vs target Core Web Vitals. Fury sets the targets in `nfr-principles.md`. Tony picks the stack mindful of the runtime cost ahead of time, not afterward.
|