@brunosps00/dev-workflow 0.10.0 → 0.13.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/README.md +78 -6
- package/lib/constants.js +20 -20
- package/lib/init.js +44 -4
- package/lib/migrate-skills.js +129 -0
- package/lib/removed-bundled-skills.js +16 -0
- package/lib/uninstall.js +6 -2
- package/lib/utils.js +51 -4
- package/package.json +1 -1
- package/scaffold/en/agent-instructions.md +68 -0
- package/scaffold/en/commands/dw-analyze-project.md +61 -0
- package/scaffold/en/commands/dw-autopilot.md +1 -1
- package/scaffold/en/commands/dw-brainstorm.md +1 -1
- package/scaffold/en/commands/dw-bugfix.md +3 -3
- package/scaffold/en/commands/dw-code-review.md +28 -0
- package/scaffold/en/commands/dw-create-prd.md +16 -0
- package/scaffold/en/commands/dw-create-tasks.md +42 -0
- package/scaffold/en/commands/dw-create-techspec.md +18 -1
- package/scaffold/en/commands/dw-deps-audit.md +1 -1
- package/scaffold/en/commands/dw-fix-qa.md +1 -1
- package/scaffold/en/commands/dw-functional-doc.md +2 -2
- package/scaffold/en/commands/dw-help.md +1 -1
- package/scaffold/en/commands/dw-redesign-ui.md +7 -7
- package/scaffold/en/commands/dw-run-qa.md +4 -4
- package/scaffold/en/commands/dw-run-task.md +2 -2
- package/scaffold/en/templates/constitution-template.md +111 -0
- package/scaffold/pt-br/agent-instructions.md +68 -0
- package/scaffold/pt-br/commands/dw-analyze-project.md +61 -0
- package/scaffold/pt-br/commands/dw-autopilot.md +1 -1
- package/scaffold/pt-br/commands/dw-brainstorm.md +1 -1
- package/scaffold/pt-br/commands/dw-bugfix.md +3 -3
- package/scaffold/pt-br/commands/dw-code-review.md +28 -0
- package/scaffold/pt-br/commands/dw-create-prd.md +16 -0
- package/scaffold/pt-br/commands/dw-create-tasks.md +42 -0
- package/scaffold/pt-br/commands/dw-create-techspec.md +18 -1
- package/scaffold/pt-br/commands/dw-deps-audit.md +1 -1
- package/scaffold/pt-br/commands/dw-fix-qa.md +1 -1
- package/scaffold/pt-br/commands/dw-functional-doc.md +2 -2
- package/scaffold/pt-br/commands/dw-help.md +1 -1
- package/scaffold/pt-br/commands/dw-redesign-ui.md +7 -7
- package/scaffold/pt-br/commands/dw-run-qa.md +4 -4
- package/scaffold/pt-br/commands/dw-run-task.md +2 -2
- package/scaffold/pt-br/templates/constitution-template.md +111 -0
- package/scaffold/skills/dw-council/SKILL.md +1 -1
- package/scaffold/skills/dw-testing-discipline/SKILL.md +148 -0
- package/scaffold/skills/dw-testing-discipline/references/ai-agent-gates.md +170 -0
- package/scaffold/skills/dw-testing-discipline/references/anti-patterns.md +336 -0
- package/scaffold/skills/dw-testing-discipline/references/flaky-discipline.md +163 -0
- package/scaffold/skills/dw-testing-discipline/references/iron-laws.md +128 -0
- package/scaffold/skills/dw-testing-discipline/references/playwright-recipes.md +282 -0
- package/scaffold/skills/dw-testing-discipline/references/positive-patterns.md +241 -0
- package/scaffold/skills/{webapp-testing → dw-testing-discipline}/references/security-boundary.md +1 -1
- package/scaffold/skills/dw-ui-discipline/SKILL.md +128 -0
- package/scaffold/skills/dw-ui-discipline/references/accessibility-floor.md +225 -0
- package/scaffold/skills/dw-ui-discipline/references/anti-slop.md +162 -0
- package/scaffold/skills/dw-ui-discipline/references/curated-defaults.md +195 -0
- package/scaffold/skills/dw-ui-discipline/references/hard-gate.md +142 -0
- package/scaffold/skills/dw-ui-discipline/references/state-matrix.md +101 -0
- package/scaffold/templates-overrides-readme.md +75 -0
- package/scaffold/skills/ui-ux-pro-max/LICENSE +0 -21
- package/scaffold/skills/ui-ux-pro-max/SKILL.md +0 -659
- package/scaffold/skills/ui-ux-pro-max/data/_sync_all.py +0 -414
- package/scaffold/skills/ui-ux-pro-max/data/app-interface.csv +0 -31
- package/scaffold/skills/ui-ux-pro-max/data/charts.csv +0 -26
- package/scaffold/skills/ui-ux-pro-max/data/colors.csv +0 -162
- package/scaffold/skills/ui-ux-pro-max/data/design.csv +0 -1776
- package/scaffold/skills/ui-ux-pro-max/data/draft.csv +0 -1779
- package/scaffold/skills/ui-ux-pro-max/data/google-fonts.csv +0 -1924
- package/scaffold/skills/ui-ux-pro-max/data/icons.csv +0 -106
- package/scaffold/skills/ui-ux-pro-max/data/landing.csv +0 -35
- package/scaffold/skills/ui-ux-pro-max/data/products.csv +0 -162
- package/scaffold/skills/ui-ux-pro-max/data/react-performance.csv +0 -45
- package/scaffold/skills/ui-ux-pro-max/data/stacks/angular.csv +0 -51
- package/scaffold/skills/ui-ux-pro-max/data/stacks/astro.csv +0 -54
- package/scaffold/skills/ui-ux-pro-max/data/stacks/flutter.csv +0 -53
- package/scaffold/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +0 -56
- package/scaffold/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +0 -53
- package/scaffold/skills/ui-ux-pro-max/data/stacks/laravel.csv +0 -51
- package/scaffold/skills/ui-ux-pro-max/data/stacks/nextjs.csv +0 -53
- package/scaffold/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +0 -51
- package/scaffold/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +0 -59
- package/scaffold/skills/ui-ux-pro-max/data/stacks/react-native.csv +0 -52
- package/scaffold/skills/ui-ux-pro-max/data/stacks/react.csv +0 -54
- package/scaffold/skills/ui-ux-pro-max/data/stacks/shadcn.csv +0 -61
- package/scaffold/skills/ui-ux-pro-max/data/stacks/svelte.csv +0 -54
- package/scaffold/skills/ui-ux-pro-max/data/stacks/swiftui.csv +0 -51
- package/scaffold/skills/ui-ux-pro-max/data/stacks/threejs.csv +0 -54
- package/scaffold/skills/ui-ux-pro-max/data/stacks/vue.csv +0 -50
- package/scaffold/skills/ui-ux-pro-max/data/styles.csv +0 -85
- package/scaffold/skills/ui-ux-pro-max/data/typography.csv +0 -74
- package/scaffold/skills/ui-ux-pro-max/data/ui-reasoning.csv +0 -162
- package/scaffold/skills/ui-ux-pro-max/data/ux-guidelines.csv +0 -100
- package/scaffold/skills/ui-ux-pro-max/scripts/core.py +0 -262
- package/scaffold/skills/ui-ux-pro-max/scripts/design_system.py +0 -1148
- package/scaffold/skills/ui-ux-pro-max/scripts/search.py +0 -114
- package/scaffold/skills/ui-ux-pro-max/skills/brand/SKILL.md +0 -97
- package/scaffold/skills/ui-ux-pro-max/skills/design/SKILL.md +0 -302
- package/scaffold/skills/ui-ux-pro-max/skills/design-system/SKILL.md +0 -244
- package/scaffold/skills/ui-ux-pro-max/templates/base/quick-reference.md +0 -297
- package/scaffold/skills/ui-ux-pro-max/templates/base/skill-content.md +0 -358
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/agent.json +0 -21
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/augment.json +0 -18
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/claude.json +0 -21
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/codebuddy.json +0 -21
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/codex.json +0 -21
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/continue.json +0 -21
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/copilot.json +0 -21
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/cursor.json +0 -21
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/droid.json +0 -21
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/gemini.json +0 -21
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/kilocode.json +0 -21
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/kiro.json +0 -21
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/opencode.json +0 -21
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/qoder.json +0 -21
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/roocode.json +0 -21
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/trae.json +0 -21
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/warp.json +0 -18
- package/scaffold/skills/ui-ux-pro-max/templates/platforms/windsurf.json +0 -21
- package/scaffold/skills/webapp-testing/SKILL.md +0 -138
- package/scaffold/skills/webapp-testing/assets/test-helper.js +0 -56
- /package/scaffold/skills/{webapp-testing → dw-testing-discipline}/references/three-workflow-patterns.md +0 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# Twelve positive patterns — pseudo-code that survives refactors
|
|
2
|
+
|
|
3
|
+
Each pattern survives across testing frameworks (Jest, Vitest, Playwright, Cypress, pytest, JUnit). Pseudo-code in JavaScript-flavored examples; translate to your stack.
|
|
4
|
+
|
|
5
|
+
## 1. Query by behavior and accessible role, not CSS selectors
|
|
6
|
+
|
|
7
|
+
**Pattern:**
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
// GOOD — describes what the user does
|
|
11
|
+
const submitBtn = await page.getByRole('button', { name: 'Submit order' });
|
|
12
|
+
await submitBtn.click();
|
|
13
|
+
expect(await page.getByText(/order #\d+ confirmed/i)).toBeVisible();
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Anti-pattern:**
|
|
17
|
+
|
|
18
|
+
```javascript
|
|
19
|
+
// BAD — describes implementation
|
|
20
|
+
await page.click('.btn-primary.submit-btn');
|
|
21
|
+
expect(page.querySelector('.confirmation-toast')).toBeTruthy();
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The good version survives a CSS refactor, a className rename, a Tailwind migration. The bad version breaks on each.
|
|
25
|
+
|
|
26
|
+
## 2. Selector hierarchy
|
|
27
|
+
|
|
28
|
+
When the role isn't enough, climb DOWN this ladder. Stop at the highest rung that disambiguates:
|
|
29
|
+
|
|
30
|
+
1. **Role + name** — `getByRole('button', { name: 'Submit' })`
|
|
31
|
+
2. **Label / form association** — `getByLabel('Email')`
|
|
32
|
+
3. **Text content** — `getByText('Welcome back')`
|
|
33
|
+
4. **Test-id** — `getByTestId('user-menu')` (escape hatch; do not start here)
|
|
34
|
+
5. **Structural / CSS** — `querySelector('article:nth-child(3)')` (last resort; flags)
|
|
35
|
+
|
|
36
|
+
Test-id is fine. Test-id as your default is a sign you're not designing accessible UI.
|
|
37
|
+
|
|
38
|
+
## 3. Wait on observable conditions, never wall-clock
|
|
39
|
+
|
|
40
|
+
**Pattern:**
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
// GOOD — waits for the actual condition
|
|
44
|
+
await expect(page.getByText('Order confirmed')).toBeVisible({ timeout: 5000 });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Anti-pattern:**
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
// BAD — wall-clock hopes
|
|
51
|
+
await page.waitForTimeout(3000);
|
|
52
|
+
expect(page.getByText('Order confirmed')).toBeVisible();
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`waitForTimeout` is the #1 source of flakiness. It races with the network, with rendering, with the event loop. Always wait on what you actually need to see.
|
|
56
|
+
|
|
57
|
+
## 4. Each test independent and order-free
|
|
58
|
+
|
|
59
|
+
Every test runs cleanly when invoked in isolation (with `.only`) or in a randomized order.
|
|
60
|
+
|
|
61
|
+
**Pattern:**
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
beforeEach(async () => {
|
|
65
|
+
// Set up state THIS test needs
|
|
66
|
+
await db.users.create({ id: 'test-1', email: 'a@b.c' });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
afterEach(async () => {
|
|
70
|
+
// Clean up — but if tests are independent, you may not need this
|
|
71
|
+
await db.users.deleteAll();
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Anti-pattern:** Shared state in `beforeAll`. Tests passing only when run in suite order. Brittle CI runs.
|
|
76
|
+
|
|
77
|
+
**Healthier:** prefer setup in `beforeEach` over teardown in `afterEach`. A test that sets up its own state from a clean baseline never wonders "did the previous test corrupt me?"
|
|
78
|
+
|
|
79
|
+
## 5. One behavior per test, as many assertions as that behavior needs
|
|
80
|
+
|
|
81
|
+
**Pattern:**
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
test('successful login redirects to dashboard and shows user name', async () => {
|
|
85
|
+
await login('user@example.com', 'password');
|
|
86
|
+
|
|
87
|
+
expect(await page.url()).toContain('/dashboard');
|
|
88
|
+
expect(await page.getByRole('heading', { name: /welcome/i })).toBeVisible();
|
|
89
|
+
expect(await page.getByText('user@example.com')).toBeVisible();
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
ONE behavior ("successful login leads to dashboard with user info"). THREE assertions because that behavior has three observable parts.
|
|
94
|
+
|
|
95
|
+
**Anti-pattern:** Two unrelated behaviors crammed into one test ("login + then-also-test-search-works"). When it fails, you don't know which broke.
|
|
96
|
+
|
|
97
|
+
## 6. Names read as specifications
|
|
98
|
+
|
|
99
|
+
**Pattern:**
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
should reject invalid email when registering given no prior account
|
|
103
|
+
should approve refund within SLA given amount under threshold
|
|
104
|
+
should sync calendar events when user reconnects given offline edits
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Form: `should <expected outcome> when <triggering condition> [given <starting state>]`
|
|
108
|
+
|
|
109
|
+
**Anti-pattern:**
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
test_login
|
|
113
|
+
test_email_1
|
|
114
|
+
testHappy
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
These tell you nothing on failure. A spec-style name doubles as documentation when failure messages get noisy.
|
|
118
|
+
|
|
119
|
+
## 7. Table-driven / parameterized when inputs vary
|
|
120
|
+
|
|
121
|
+
**Pattern:**
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
describe.each([
|
|
125
|
+
{ input: '', expected: 'required' },
|
|
126
|
+
{ input: 'a', expected: 'too short' },
|
|
127
|
+
{ input: 'a'.repeat(100), expected: 'too long' },
|
|
128
|
+
{ input: 'a@b.c', expected: 'valid' },
|
|
129
|
+
])('validateEmail($input)', ({ input, expected }) => {
|
|
130
|
+
test(`returns ${expected}`, () => {
|
|
131
|
+
expect(validateEmail(input)).toBe(expected);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Easy to add cases; impossible to forget one input class.
|
|
137
|
+
|
|
138
|
+
**Anti-pattern:** Five copy-pasted tests with one variable changed. Drift between them; one gets updated, others don't.
|
|
139
|
+
|
|
140
|
+
## 8. Build test data via factories
|
|
141
|
+
|
|
142
|
+
**Pattern:**
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
const buildUser = (overrides = {}) => ({
|
|
146
|
+
id: 'u-' + Math.random().toString(36).slice(2),
|
|
147
|
+
email: 'test@example.com',
|
|
148
|
+
role: 'member',
|
|
149
|
+
createdAt: new Date(),
|
|
150
|
+
...overrides,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('admin can delete users', () => {
|
|
154
|
+
const admin = buildUser({ role: 'admin' });
|
|
155
|
+
const target = buildUser();
|
|
156
|
+
expect(canDelete(admin, target)).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Tests focus on the FIELDS that matter to the behavior. Default everything else.
|
|
161
|
+
|
|
162
|
+
**Anti-pattern:** Literal 50-field JSON blobs copy-pasted across tests. When the schema changes, you update them all — or worse, miss some.
|
|
163
|
+
|
|
164
|
+
## 9. Mock at boundaries you don't control
|
|
165
|
+
|
|
166
|
+
**Pattern:**
|
|
167
|
+
|
|
168
|
+
| Boundary | Test treatment |
|
|
169
|
+
|----------|---------------|
|
|
170
|
+
| Your own modules | Real (don't mock) |
|
|
171
|
+
| Your own DB (with testcontainers) | Real |
|
|
172
|
+
| Third-party HTTP API | Mock at fetch/axios level |
|
|
173
|
+
| Cloud SDK (AWS, GCP, Stripe) | Mock at SDK level OR sandbox account |
|
|
174
|
+
| System clock | Mock when test depends on time |
|
|
175
|
+
| RNG | Mock when test depends on randomness |
|
|
176
|
+
| File system (when external) | Mock; in tests of fs logic, real temp dir |
|
|
177
|
+
|
|
178
|
+
**Anti-pattern:** Mocking your own modules so the test is fast. You're now testing the mock setup, not the code.
|
|
179
|
+
|
|
180
|
+
## 10. Real systems gate final merge
|
|
181
|
+
|
|
182
|
+
**Pattern:**
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
unit (mocks ok) → every commit, run locally and in CI
|
|
186
|
+
integration (real DB) → every PR, run in CI
|
|
187
|
+
contract (boundary) → every PR
|
|
188
|
+
E2E (real services) → before merge to main, run in CI on preview env
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
No merge to main without a real-system path going green. Mocks are speed, real is truth.
|
|
192
|
+
|
|
193
|
+
**Anti-pattern:** 100% mocked test suite. "It all passes locally" → first user request fails because the mock didn't match the real API shape.
|
|
194
|
+
|
|
195
|
+
## 11. Mutation score over coverage percentage
|
|
196
|
+
|
|
197
|
+
**Pattern:**
|
|
198
|
+
|
|
199
|
+
Set up mutation testing (Stryker for JS/TS, mutmut for Python, etc.) ONCE per project. Run weekly on critical modules.
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
npx stryker run
|
|
203
|
+
# Output: 87 mutants, 78 killed, 9 survived → mutation score 89.6%
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
A surviving mutant means: this code path runs in tests, but the tests don't actually assert anything that breaks when the code changes. Investigate each.
|
|
207
|
+
|
|
208
|
+
**Anti-pattern:** 95% line coverage with assertions like `expect(result).toBeTruthy()` — every line ran, but mutations all survive. The suite is decorative.
|
|
209
|
+
|
|
210
|
+
## 12. Page Object Model is a tool, not a religion
|
|
211
|
+
|
|
212
|
+
For small E2E suites (<20 tests, <5 pages), POM is over-engineering — direct queries are clearer.
|
|
213
|
+
|
|
214
|
+
For large suites (>50 tests, many pages, multi-step flows), POM pays off:
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
class CheckoutPage {
|
|
218
|
+
constructor(page) { this.page = page; }
|
|
219
|
+
|
|
220
|
+
async fillShipping(addr) { /* ... */ }
|
|
221
|
+
async selectPayment(method) { /* ... */ }
|
|
222
|
+
async place() { /* ... */ }
|
|
223
|
+
async waitForConfirmation() { return this.page.getByText(/confirmed/i); }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
test('checkout end-to-end', async ({ page }) => {
|
|
227
|
+
const checkout = new CheckoutPage(page);
|
|
228
|
+
await checkout.fillShipping(defaultAddress);
|
|
229
|
+
await checkout.selectPayment('card');
|
|
230
|
+
await checkout.place();
|
|
231
|
+
await expect(checkout.waitForConfirmation()).toBeVisible();
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The page object hides the selector mess; tests read like specs. But for a 5-test suite, that wrapper is just noise.
|
|
236
|
+
|
|
237
|
+
**Rule of thumb:** apply POM when you have ≥3 tests sharing the same page interactions. Otherwise inline.
|
|
238
|
+
|
|
239
|
+
## Applying these patterns
|
|
240
|
+
|
|
241
|
+
When `/dw-run-task` generates a new test, it must comply with these 12. `/dw-code-review` checks each diff hunk under test paths against these patterns and the anti-patterns in the sibling reference. Violations become findings.
|
package/scaffold/skills/{webapp-testing → dw-testing-discipline}/references/security-boundary.md
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Security boundary — every browser is hostile
|
|
2
2
|
|
|
3
|
-
> Adapted from [`addyosmani/agent-skills/browser-devtools`](https://github.com/addyosmani/agent-skills/tree/main/browser-devtools) (MIT). Adopts the security-boundary principle to inform how
|
|
3
|
+
> Adapted from [`addyosmani/agent-skills/browser-devtools`](https://github.com/addyosmani/agent-skills/tree/main/browser-devtools) (MIT). Adopts the security-boundary principle to inform how browser-based testing scenarios validate trust boundaries.
|
|
4
4
|
|
|
5
5
|
The browser is not a secure environment. Anything that runs there — JS, CSS, HTML, devtools — is under the user's control. When you write a webapp test, you're not just verifying functionality; you're often verifying that this assumption holds.
|
|
6
6
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dw-ui-discipline
|
|
3
|
+
description: Use BEFORE any UI work — enforces a hard-gate (brand authorities or curated defaults, surface job, state matrix, scene sentence), 14 anti-slop patterns, and WCAG 2.2 AA floor so UI ships with discipline instead of training-data defaults.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# UI Discipline
|
|
7
|
+
|
|
8
|
+
> **Inspired by** [`pedronauck/skills/ui-craft`](https://github.com/pedronauck/skills/tree/main/skills/mine/ui-craft) (MIT). Hard-gate protocol, anti-slop catalog, state matrix enforcement, and accessibility-floor patterns adapted from Pedro Nauck's work; specifics rewritten for dev-workflow's redesign and review loops.
|
|
9
|
+
|
|
10
|
+
The fastest path through UI work is the disciplined one. "It's just a small change" is the most common slop excuse. This skill blocks that excuse at four checkpoints before any design decision lands.
|
|
11
|
+
|
|
12
|
+
## When this skill applies
|
|
13
|
+
|
|
14
|
+
- Any work invoked from `/dw-redesign-ui`, `/dw-create-techspec` (UI sections), `/dw-functional-doc`, or `/dw-code-review` when the diff touches UI.
|
|
15
|
+
- Adding new screens, components, or surfaces.
|
|
16
|
+
- Reviewing visual changes in a PR.
|
|
17
|
+
- Auditing accessibility on existing surfaces.
|
|
18
|
+
|
|
19
|
+
If you're tempted to skip the gate "because it's just a tweak" — that's the trigger. Run the gate.
|
|
20
|
+
|
|
21
|
+
## The hard-gate (4 mandatory items before any UI work)
|
|
22
|
+
|
|
23
|
+
Before proposing colors, layouts, components, or any visual decision, complete all four:
|
|
24
|
+
|
|
25
|
+
### 1. Brand authorities OR curated defaults
|
|
26
|
+
|
|
27
|
+
Locate the project's design source of truth:
|
|
28
|
+
- `.dw/rules/{frontend-module}.md` design system section
|
|
29
|
+
- `DESIGN.md`, `BRAND.md`, or design tokens config (Tailwind, CSS vars, theme file)
|
|
30
|
+
- Component library docs (shadcn/ui, MUI, Chakra, etc.)
|
|
31
|
+
|
|
32
|
+
If **none exist**, do NOT invent. Read `references/curated-defaults.md` — pick from the 10 neutral palettes + 10 font pairings shipped there. Mark the choice as a finding in the techspec ("no design authority found; used curated default <name>; recommend establishing one").
|
|
33
|
+
|
|
34
|
+
### 2. Surface job sentence
|
|
35
|
+
|
|
36
|
+
Write one sentence: "This surface helps the user <do what> so that <outcome>." Vague language ("show data", "manage settings") fails — be specific ("filter overdue invoices so they can chase late payers in <30s").
|
|
37
|
+
|
|
38
|
+
If you can't write the sentence, the requirements are unclear. Stop and clarify before proceeding.
|
|
39
|
+
|
|
40
|
+
### 3. Complete state matrix
|
|
41
|
+
|
|
42
|
+
Enumerate all states the surface can be in. See `references/state-matrix.md` for the full list:
|
|
43
|
+
- `default`, `hover`, `active`, `focus-visible`, `disabled`, `loading`, `empty`, `error`, `success`
|
|
44
|
+
- Plus any domain-specific states (read/unread, online/offline, etc.)
|
|
45
|
+
|
|
46
|
+
Missing a state at design time = production bug later. The "we'll add empty state later" trap is real.
|
|
47
|
+
|
|
48
|
+
### 4. Scene sentence
|
|
49
|
+
|
|
50
|
+
One sentence describing the physical context: **who** is using this, **where** (mobile bus, office desktop, on-call laptop), **what light** (dark room, bright outdoor), **what mood** (rushed, exploring, troubleshooting).
|
|
51
|
+
|
|
52
|
+
This forcing function prevents category-level defaults from becoming the answer. A "dashboard for an on-call engineer at 3am in a dark room troubleshooting a fire" produces different decisions than "dashboard for a manager during business hours."
|
|
53
|
+
|
|
54
|
+
## Required Reading Router
|
|
55
|
+
|
|
56
|
+
| Context | Read |
|
|
57
|
+
|---------|------|
|
|
58
|
+
| Any UI work | `references/hard-gate.md` (full protocol with examples) |
|
|
59
|
+
| Interactive widgets (buttons, forms, modals) | `references/accessibility-floor.md` (WCAG 2.2 AA non-negotiable) |
|
|
60
|
+
| Reviewing a UI diff | `references/anti-slop.md` (14 anti-patterns + 17 anti-defaults) |
|
|
61
|
+
| Designing state coverage | `references/state-matrix.md` (full enumeration + checklist) |
|
|
62
|
+
| No design authority exists in project | `references/curated-defaults.md` (10 palettes + 10 fonts) |
|
|
63
|
+
|
|
64
|
+
## Anti-slop summary (full list in references/anti-slop.md)
|
|
65
|
+
|
|
66
|
+
The 14 patterns this skill catches:
|
|
67
|
+
|
|
68
|
+
1. **Visual sameness** — every section looks like every other section.
|
|
69
|
+
2. **Weak hierarchy** — nothing draws the eye to what matters first.
|
|
70
|
+
3. **Fake interactivity** — hover states that don't change anything functional.
|
|
71
|
+
4. **Emoji spam** — emojis as decoration where icons or restraint would serve.
|
|
72
|
+
5. **Gradient crutch** — gradients used to mask weak composition.
|
|
73
|
+
6. **Glass everything** — frosted glass on every panel.
|
|
74
|
+
7. **Centered all the things** — center-aligned text when left-aligned reads better.
|
|
75
|
+
8. **AI gray washing** — neutral grays everywhere, no character.
|
|
76
|
+
9. **Generic CTAs** — "Get Started", "Learn More", "Click Here" with no specificity.
|
|
77
|
+
10. **Stock illustration** — generic figure-with-laptop hero art.
|
|
78
|
+
11. **Drop shadow soup** — shadows on cards on shadows on borders.
|
|
79
|
+
12. **Loading spinner default** — spinner as the only loading state for everything.
|
|
80
|
+
13. **Empty state void** — empty list with no guidance on what to do next.
|
|
81
|
+
14. **Notification-soup tray** — every UI event becomes a toast.
|
|
82
|
+
|
|
83
|
+
Plus 17 anti-defaults (specific values to NEVER use without intent — `#3B82F6` blue, `rounded-lg` everywhere, etc.) in `references/anti-slop.md`.
|
|
84
|
+
|
|
85
|
+
## Accessibility floor — non-negotiable
|
|
86
|
+
|
|
87
|
+
Before any interactive widget ships:
|
|
88
|
+
|
|
89
|
+
- [ ] Color contrast meets WCAG 2.2 AA (4.5:1 for body text, 3:1 for large text and UI components).
|
|
90
|
+
- [ ] Focus-visible state exists and is distinct from hover.
|
|
91
|
+
- [ ] Keyboard navigation works (tab order, escape closes modals, enter submits forms).
|
|
92
|
+
- [ ] ARIA labels for icon-only buttons.
|
|
93
|
+
- [ ] Form errors are announced to screen readers.
|
|
94
|
+
- [ ] No keyboard traps.
|
|
95
|
+
|
|
96
|
+
Full verification recipes in `references/accessibility-floor.md`. This is a hard gate — `/dw-code-review` fails verdict if any interactive widget ships without these.
|
|
97
|
+
|
|
98
|
+
## When the gate bends
|
|
99
|
+
|
|
100
|
+
Real-world UI can't always be perfect:
|
|
101
|
+
|
|
102
|
+
- **Bug fix in existing UI** — gate applies only to the area touched, not the whole surface.
|
|
103
|
+
- **Pure copy change** — gate is just "scene sentence still holds?" — quick check.
|
|
104
|
+
- **Spike / exploration** — gate skipped if the spike is explicitly marked throwaway; production code must run the gate.
|
|
105
|
+
|
|
106
|
+
In all bend cases, document the bend in the PR description (one line). "I skipped the state matrix because this is a one-line copy fix" is fine. "I skipped because I was in a hurry" is not.
|
|
107
|
+
|
|
108
|
+
## Integration with dev-workflow commands
|
|
109
|
+
|
|
110
|
+
- `/dw-redesign-ui` runs the gate end-to-end. Steps 4 (propose design) and 7 (validate WCAG) consult this skill.
|
|
111
|
+
- `/dw-create-techspec` UI sections must list which authorities were consulted (brand vs curated default) and reference the state matrix.
|
|
112
|
+
- `/dw-code-review` checks the diff against `references/anti-slop.md` and the accessibility floor.
|
|
113
|
+
- `/dw-functional-doc` documents the scene sentence in the overview.
|
|
114
|
+
|
|
115
|
+
## Anti-patterns this skill prevents
|
|
116
|
+
|
|
117
|
+
- "Just use the same hero as the marketing page" — without verifying the surface job differs.
|
|
118
|
+
- "We'll add empty/error states later" — they're never added later.
|
|
119
|
+
- "It looks fine on my desktop" — without checking mobile + keyboard + screen reader.
|
|
120
|
+
- Designing in isolation from `.dw/rules/` documented patterns.
|
|
121
|
+
- Inventing color values when the design system has tokens that fit.
|
|
122
|
+
- Shipping interactive widgets without WCAG 2.2 AA verification.
|
|
123
|
+
|
|
124
|
+
## Why this skill exists
|
|
125
|
+
|
|
126
|
+
The previous bundled skill (`ui-ux-pro-max`) provided 161 color palettes and 57 font pairings — a CATALOG of taste. It had no gates, no anti-patterns, no enforcement. Agents loaded KB of palette data and still produced slop because there was no DISCIPLINE.
|
|
127
|
+
|
|
128
|
+
This skill inverts that trade-off: 10 curated defaults (enough for 90% of cases) + a strong gate + an anti-slop catalog. Less context bytes, more leverage.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Accessibility floor — WCAG 2.2 AA is the minimum, not the goal
|
|
2
|
+
|
|
3
|
+
This reference is read in full before any interactive widget ships. Skipping any item below is a hard-block in `/dw-code-review`.
|
|
4
|
+
|
|
5
|
+
## The non-negotiables
|
|
6
|
+
|
|
7
|
+
### 1. Color contrast
|
|
8
|
+
|
|
9
|
+
| Element | Minimum ratio (WCAG 2.2 AA) |
|
|
10
|
+
|---------|----------------------------|
|
|
11
|
+
| Body text (under 18pt or under 14pt bold) | 4.5:1 |
|
|
12
|
+
| Large text (18pt+ or 14pt+ bold) | 3:1 |
|
|
13
|
+
| UI components (button borders, icons, focus rings) | 3:1 |
|
|
14
|
+
| Decorative graphics | No requirement |
|
|
15
|
+
|
|
16
|
+
**Verification:**
|
|
17
|
+
- Use `axe DevTools` browser extension OR `playwright/test` with `@axe-core/playwright`.
|
|
18
|
+
- Manual quick check: `chrome://flags` → Enable "Simulate Vision Deficiencies" + try "Achromatopsia" (no colors). If buttons still legible, contrast is doing its job.
|
|
19
|
+
|
|
20
|
+
**Common violation:** Light gray text on white. `#9CA3AF` on white = 2.85:1. FAIL.
|
|
21
|
+
|
|
22
|
+
### 2. Focus-visible state
|
|
23
|
+
|
|
24
|
+
Every interactive element must have a distinct focus-visible state when keyboard-focused.
|
|
25
|
+
|
|
26
|
+
**Rules:**
|
|
27
|
+
- `:focus-visible` MUST be distinct from `:hover` — keyboard users need an affordance.
|
|
28
|
+
- Default browser outline is acceptable; replacing with `outline: none` requires alternative (border, ring, shadow).
|
|
29
|
+
- Focus must be visible against any background the element can appear on (dark + light themes).
|
|
30
|
+
|
|
31
|
+
**Tailwind pattern:**
|
|
32
|
+
```css
|
|
33
|
+
focus-visible:outline-none
|
|
34
|
+
focus-visible:ring-2
|
|
35
|
+
focus-visible:ring-offset-2
|
|
36
|
+
focus-visible:ring-blue-500
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Verification:**
|
|
40
|
+
- Tab through the entire surface with keyboard. Every interactive element must light up visibly.
|
|
41
|
+
- If you can't tell what's focused, the contrast on the focus ring is too low.
|
|
42
|
+
|
|
43
|
+
### 3. Keyboard navigation
|
|
44
|
+
|
|
45
|
+
| Capability | Requirement |
|
|
46
|
+
|------------|-------------|
|
|
47
|
+
| Tab order | Logical (left-to-right, top-to-bottom in LTR) |
|
|
48
|
+
| Modals | Tab cycles WITHIN modal; Escape closes; focus returns to trigger |
|
|
49
|
+
| Dropdowns / select | Arrow keys navigate options; Enter selects; Escape closes |
|
|
50
|
+
| Forms | Enter submits; Tab moves to next field; Shift+Tab moves back |
|
|
51
|
+
| Custom widgets | Match the ARIA Authoring Practices Guide patterns |
|
|
52
|
+
|
|
53
|
+
**Verification:**
|
|
54
|
+
- Unplug mouse. Complete the primary user flow with keyboard only.
|
|
55
|
+
- If you got stuck or couldn't reach an element, keyboard nav is broken.
|
|
56
|
+
|
|
57
|
+
### 4. ARIA labels for icon-only interactives
|
|
58
|
+
|
|
59
|
+
Icon-only buttons (e.g., a trash can icon as a delete button) need `aria-label` describing the action.
|
|
60
|
+
|
|
61
|
+
```html
|
|
62
|
+
<!-- BAD -->
|
|
63
|
+
<button><TrashIcon /></button>
|
|
64
|
+
|
|
65
|
+
<!-- GOOD -->
|
|
66
|
+
<button aria-label="Delete invoice"><TrashIcon /></button>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Same applies to:
|
|
70
|
+
- Icon-only links
|
|
71
|
+
- Icon-only toggles
|
|
72
|
+
- Tooltips (the trigger needs aria-describedby pointing to the tooltip)
|
|
73
|
+
- Decorative images (`aria-hidden="true"` if purely decorative; `alt=""` not enough)
|
|
74
|
+
|
|
75
|
+
### 5. Form errors announced to screen readers
|
|
76
|
+
|
|
77
|
+
When a form field errors:
|
|
78
|
+
|
|
79
|
+
```html
|
|
80
|
+
<label for="email">Email</label>
|
|
81
|
+
<input
|
|
82
|
+
id="email"
|
|
83
|
+
type="email"
|
|
84
|
+
aria-describedby="email-error"
|
|
85
|
+
aria-invalid="true"
|
|
86
|
+
/>
|
|
87
|
+
<span id="email-error" role="alert">
|
|
88
|
+
Email is required
|
|
89
|
+
</span>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Key points:
|
|
93
|
+
- `aria-invalid="true"` on the input.
|
|
94
|
+
- `aria-describedby` links the input to the error message.
|
|
95
|
+
- `role="alert"` on the error message announces it.
|
|
96
|
+
|
|
97
|
+
**Anti-pattern:** Red border + tooltip on hover. Screen reader users get nothing.
|
|
98
|
+
|
|
99
|
+
### 6. No keyboard traps
|
|
100
|
+
|
|
101
|
+
A "keyboard trap" is when focus enters a widget and can't leave with the keyboard. Most common cause: custom-built modals or carousels.
|
|
102
|
+
|
|
103
|
+
**Rule:** Every modal, drawer, or overlay must let Escape close it and return focus to the trigger element.
|
|
104
|
+
|
|
105
|
+
**Verification:**
|
|
106
|
+
- Open the widget with keyboard. Press Escape. Does focus return to where it was?
|
|
107
|
+
- Tab through the widget. After last element, does Tab cycle to first (within widget) instead of leaving the page?
|
|
108
|
+
|
|
109
|
+
### 7. Touch target size (mobile)
|
|
110
|
+
|
|
111
|
+
Minimum 24x24 CSS pixels (WCAG 2.2 AA minimum). Recommended 44x44 (Apple HIG / Android touch target).
|
|
112
|
+
|
|
113
|
+
**Pattern:** Even when the visual icon is 16x16, give the button 44x44 hit area:
|
|
114
|
+
|
|
115
|
+
```css
|
|
116
|
+
button {
|
|
117
|
+
width: 24px; height: 24px; /* WCAG 2.2 AA minimum */
|
|
118
|
+
/* Or */
|
|
119
|
+
width: 44px; height: 44px; /* Recommended */
|
|
120
|
+
padding: 14px; /* Visual icon inside */
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 8. Heading hierarchy
|
|
125
|
+
|
|
126
|
+
H1 → H2 → H3 etc. Don't skip levels. Don't use heading tags for visual sizing — use semantic level + CSS for size.
|
|
127
|
+
|
|
128
|
+
```html
|
|
129
|
+
<!-- BAD -->
|
|
130
|
+
<h2>Section A</h2>
|
|
131
|
+
<h4>Subsection</h4> <!-- skipped h3 -->
|
|
132
|
+
|
|
133
|
+
<!-- GOOD -->
|
|
134
|
+
<h2>Section A</h2>
|
|
135
|
+
<h3>Subsection</h3>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Screen readers navigate by heading; broken hierarchy disorients.
|
|
139
|
+
|
|
140
|
+
### 9. Language attribute
|
|
141
|
+
|
|
142
|
+
Root `<html lang="en">` (or correct ISO code) for primary language. Sections in other language need `lang` attribute too.
|
|
143
|
+
|
|
144
|
+
```html
|
|
145
|
+
<html lang="en">
|
|
146
|
+
<p>Welcome.</p>
|
|
147
|
+
<p lang="pt-br">Bem-vindo.</p>
|
|
148
|
+
</html>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Screen readers switch pronunciation based on this.
|
|
152
|
+
|
|
153
|
+
### 10. Reduced motion respect
|
|
154
|
+
|
|
155
|
+
Honor `prefers-reduced-motion`:
|
|
156
|
+
|
|
157
|
+
```css
|
|
158
|
+
@media (prefers-reduced-motion: reduce) {
|
|
159
|
+
*, *::before, *::after {
|
|
160
|
+
animation-duration: 0.01ms !important;
|
|
161
|
+
transition-duration: 0.01ms !important;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Or in Tailwind:
|
|
167
|
+
```html
|
|
168
|
+
<div className="transition-all motion-reduce:transition-none">
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Vestibular disorders trigger on parallax, large slide-ins, infinite scroll animations.
|
|
172
|
+
|
|
173
|
+
## Verification recipes
|
|
174
|
+
|
|
175
|
+
### Automated (CI)
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
// playwright + axe-core
|
|
179
|
+
import { test, expect } from '@playwright/test';
|
|
180
|
+
import AxeBuilder from '@axe-core/playwright';
|
|
181
|
+
|
|
182
|
+
test('homepage a11y', async ({ page }) => {
|
|
183
|
+
await page.goto('/');
|
|
184
|
+
const results = await new AxeBuilder({ page })
|
|
185
|
+
.withTags(['wcag2a', 'wcag2aa', 'wcag22a', 'wcag22aa'])
|
|
186
|
+
.analyze();
|
|
187
|
+
expect(results.violations).toEqual([]);
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Run on every PR. Block merge on violations.
|
|
192
|
+
|
|
193
|
+
### Manual (PR review)
|
|
194
|
+
|
|
195
|
+
1. Open the changed page.
|
|
196
|
+
2. Tab through with keyboard only. Verify focus visible at every stop.
|
|
197
|
+
3. Run axe DevTools, fix violations.
|
|
198
|
+
4. Toggle prefers-reduced-motion in DevTools rendering panel. Verify animations stop.
|
|
199
|
+
5. Test with screen reader (VoiceOver on Mac, NVDA on Windows) on at least one critical flow.
|
|
200
|
+
|
|
201
|
+
### Spot checks
|
|
202
|
+
|
|
203
|
+
Color contrast: chrome devtools → element inspector → "Contrast" indicator next to text color. Click for actual ratio.
|
|
204
|
+
|
|
205
|
+
Touch target: Inspect element → check computed width/height. If < 24, flag.
|
|
206
|
+
|
|
207
|
+
## When the floor bends
|
|
208
|
+
|
|
209
|
+
- **Iframes you don't control** — your wrapper still has to be accessible, the iframe is best-effort.
|
|
210
|
+
- **Third-party widgets** (analytics dashboards, payment SDKs) — wrap with ARIA landmarks; report violations upstream.
|
|
211
|
+
- **Legacy code being patched** — bring touched components up to floor; leave untouched ones for separate accessibility-debt work.
|
|
212
|
+
|
|
213
|
+
In all bend cases, file a tracking issue. Don't pretend the floor was met.
|
|
214
|
+
|
|
215
|
+
## Common AI-generated UI violations
|
|
216
|
+
|
|
217
|
+
LLM-produced UI commonly fails on:
|
|
218
|
+
- `<div onClick>` instead of `<button>` (not keyboard-accessible).
|
|
219
|
+
- Icon-only buttons with no aria-label.
|
|
220
|
+
- `text-gray-400` on white (contrast 2.8:1 — fail).
|
|
221
|
+
- `outline: none` with no replacement focus state.
|
|
222
|
+
- Modals that trap focus only on success path, not on error.
|
|
223
|
+
- Auto-playing carousels with no pause control.
|
|
224
|
+
|
|
225
|
+
`/dw-code-review` runs axe-style checks on the diff for these.
|