codebyplan 1.5.1 → 1.9.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/dist/cli.js +4462 -748
- package/package.json +5 -1
- package/templates/.gitkeep +0 -0
- package/templates/README.md +20 -0
- package/templates/agents/cbp-cc-executor.md +213 -0
- package/templates/agents/cbp-database-agent.md +229 -0
- package/templates/agents/cbp-improve-claude.md +245 -0
- package/templates/agents/cbp-improve-round.md +284 -0
- package/templates/agents/cbp-mechanical-edits.md +111 -0
- package/templates/agents/cbp-research.md +282 -0
- package/templates/agents/cbp-round-executor.md +604 -0
- package/templates/agents/cbp-security-agent.md +134 -0
- package/templates/agents/cbp-task-check.md +213 -0
- package/templates/agents/cbp-task-planner.md +582 -0
- package/templates/agents/cbp-test-e2e-agent.md +363 -0
- package/templates/agents/cbp-testing-qa-agent.md +400 -0
- package/templates/context/mcp-docs.md +139 -0
- package/templates/hooks/README.md +236 -0
- package/templates/hooks/cbp-auto-test-hooks.sh +44 -0
- package/templates/hooks/cbp-lint-format-on-edit.sh +159 -0
- package/templates/hooks/cbp-maestro-yaml-validate.sh +100 -0
- package/templates/hooks/cbp-mcp-migration-guard.sh +32 -0
- package/templates/hooks/cbp-mcp-round-sync.sh +79 -0
- package/templates/hooks/cbp-mcp-worktree-inject.sh +76 -0
- package/templates/hooks/cbp-notify.sh +68 -0
- package/templates/hooks/cbp-plugin-dispatch.sh +29 -0
- package/templates/hooks/cbp-pre-commit-quality-gate.sh +204 -0
- package/templates/hooks/cbp-statusline.sh +347 -0
- package/templates/hooks/cbp-subagent-statusline.sh +182 -0
- package/templates/hooks/cbp-test-coverage-gate.sh +144 -0
- package/templates/hooks/cbp-test-hooks.sh +320 -0
- package/templates/hooks/hooks.json +85 -0
- package/templates/hooks/validate-context-usage.sh +59 -0
- package/templates/hooks/validate-git-commit.sh +78 -0
- package/templates/hooks/validate-git-stash-deny.sh +32 -0
- package/templates/hooks/validate-structure-lengths.sh +57 -0
- package/templates/hooks/validate-structure-lib.sh +104 -0
- package/templates/hooks/validate-structure-patterns.sh +54 -0
- package/templates/hooks/validate-structure-scope.sh +33 -0
- package/templates/hooks/validate-structure-smoke.sh +95 -0
- package/templates/hooks/validate-structure-templates.sh +34 -0
- package/templates/hooks/validate-structure.sh +69 -0
- package/templates/rules/.gitkeep +0 -0
- package/templates/rules/README.md +47 -0
- package/templates/rules/context-file-loading.md +52 -0
- package/templates/rules/scope-vocabulary.md +64 -0
- package/templates/rules/todo-backend.md +109 -0
- package/templates/settings.project.base.json +55 -0
- package/templates/settings.user.base.json +25 -0
- package/templates/skills/cbp-build-cc-agent/SKILL.md +139 -0
- package/templates/skills/cbp-build-cc-agent/examples/read-only-reviewer.md +32 -0
- package/templates/skills/cbp-build-cc-agent/examples/with-hooks.md +41 -0
- package/templates/skills/cbp-build-cc-agent/examples/with-skills-preload.md +25 -0
- package/templates/skills/cbp-build-cc-agent/reference/cbp-quality.md +153 -0
- package/templates/skills/cbp-build-cc-agent/reference/frontmatter-fields.md +37 -0
- package/templates/skills/cbp-build-cc-agent/reference/permission-modes.md +18 -0
- package/templates/skills/cbp-build-cc-agent/scripts/validate-agent.sh +67 -0
- package/templates/skills/cbp-build-cc-agent/templates/agent.md +66 -0
- package/templates/skills/cbp-build-cc-claude-file/SKILL.md +178 -0
- package/templates/skills/cbp-build-cc-claude-file/examples/minimal-project.md +33 -0
- package/templates/skills/cbp-build-cc-claude-file/examples/monorepo-with-imports.md +39 -0
- package/templates/skills/cbp-build-cc-claude-file/reference/imports.md +72 -0
- package/templates/skills/cbp-build-cc-claude-file/reference/what-belongs.md +39 -0
- package/templates/skills/cbp-build-cc-claude-file/templates/project-claude-md.md +48 -0
- package/templates/skills/cbp-build-cc-claude-file/templates/user-claude-md.md +22 -0
- package/templates/skills/cbp-build-cc-memory/SKILL.md +201 -0
- package/templates/skills/cbp-build-cc-memory/examples/feedback-memory.md +11 -0
- package/templates/skills/cbp-build-cc-memory/examples/project-memory.md +11 -0
- package/templates/skills/cbp-build-cc-memory/examples/reference-memory.md +13 -0
- package/templates/skills/cbp-build-cc-memory/examples/user-memory.md +14 -0
- package/templates/skills/cbp-build-cc-memory/reference/memory-types.md +59 -0
- package/templates/skills/cbp-build-cc-memory/reference/when-to-save.md +62 -0
- package/templates/skills/cbp-build-cc-memory/templates/MEMORY-index.md +4 -0
- package/templates/skills/cbp-build-cc-memory/templates/memory-entry.md +15 -0
- package/templates/skills/cbp-build-cc-mode/SKILL.md +99 -0
- package/templates/skills/cbp-build-cc-rule/SKILL.md +176 -0
- package/templates/skills/cbp-build-cc-rule/examples/global-rule.md +19 -0
- package/templates/skills/cbp-build-cc-rule/examples/scoped-rule.md +41 -0
- package/templates/skills/cbp-build-cc-rule/reference/paths-patterns.md +48 -0
- package/templates/skills/cbp-build-cc-rule/templates/rule.md +32 -0
- package/templates/skills/cbp-build-cc-settings/SKILL.md +220 -0
- package/templates/skills/cbp-build-cc-settings/examples/hooks-config.json +64 -0
- package/templates/skills/cbp-build-cc-settings/examples/permissions-config.json +34 -0
- package/templates/skills/cbp-build-cc-settings/examples/sandbox-config.json +42 -0
- package/templates/skills/cbp-build-cc-settings/reference/cbp-conventions.md +104 -0
- package/templates/skills/cbp-build-cc-settings/reference/permission-rules.md +61 -0
- package/templates/skills/cbp-build-cc-settings/reference/scope-precedence.md +73 -0
- package/templates/skills/cbp-build-cc-settings/reference/settings-fields.md +166 -0
- package/templates/skills/cbp-build-cc-settings/templates/settings.json +23 -0
- package/templates/skills/cbp-build-cc-settings/templates/settings.local.json +10 -0
- package/templates/skills/cbp-build-cc-skill/SKILL.md +154 -0
- package/templates/skills/cbp-build-cc-skill/examples/dynamic-context.md +31 -0
- package/templates/skills/cbp-build-cc-skill/examples/fork-skill.md +22 -0
- package/templates/skills/cbp-build-cc-skill/examples/knowledge-skill.md +25 -0
- package/templates/skills/cbp-build-cc-skill/examples/task-skill.md +29 -0
- package/templates/skills/cbp-build-cc-skill/reference/cbp-quality.md +157 -0
- package/templates/skills/cbp-build-cc-skill/reference/frontmatter-fields.md +35 -0
- package/templates/skills/cbp-build-cc-skill/reference/string-substitutions.md +60 -0
- package/templates/skills/cbp-build-cc-skill/scripts/validate-skill.sh +90 -0
- package/templates/skills/cbp-build-cc-skill/templates/skill.md +51 -0
- package/templates/skills/cbp-checkpoint-check/SKILL.md +156 -0
- package/templates/skills/cbp-checkpoint-complete/SKILL.md +109 -0
- package/templates/skills/cbp-checkpoint-create/SKILL.md +116 -0
- package/templates/skills/cbp-checkpoint-end/SKILL.md +241 -0
- package/templates/skills/cbp-checkpoint-plan/SKILL.md +137 -0
- package/templates/skills/cbp-checkpoint-plan/reference/alternative-comparison-template.md +54 -0
- package/templates/skills/cbp-checkpoint-plan/reference/dep-decision-rubric.md +50 -0
- package/templates/skills/cbp-checkpoint-plan/reference/e2e-discovery-probe.md +57 -0
- package/templates/skills/cbp-checkpoint-plan/reference/gap-analysis-playbook.md +47 -0
- package/templates/skills/cbp-checkpoint-start/SKILL.md +84 -0
- package/templates/skills/cbp-checkpoint-update/SKILL.md +115 -0
- package/templates/skills/cbp-frontend-a11y/SKILL.md +109 -0
- package/templates/skills/cbp-frontend-a11y/reference/aria-roles-states.md +130 -0
- package/templates/skills/cbp-frontend-a11y/reference/contrast-visual.md +122 -0
- package/templates/skills/cbp-frontend-a11y/reference/keyboard-patterns.md +154 -0
- package/templates/skills/cbp-frontend-a11y/reference/semantic-html.md +111 -0
- package/templates/skills/cbp-frontend-design/SKILL.md +145 -0
- package/templates/skills/cbp-frontend-design/reference/nextjs-scss.md +118 -0
- package/templates/skills/cbp-frontend-design/reference/rn-expo.md +101 -0
- package/templates/skills/cbp-frontend-design/reference/tauri-react.md +82 -0
- package/templates/skills/cbp-frontend-ui/SKILL.md +262 -0
- package/templates/skills/cbp-frontend-ui/reference/ui-label-maps.md +42 -0
- package/templates/skills/cbp-frontend-ui/reference/ui-layout-patterns.md +105 -0
- package/templates/skills/cbp-frontend-ui/reference/variant-defaults.md +149 -0
- package/templates/skills/cbp-frontend-ux/SKILL.md +181 -0
- package/templates/skills/cbp-git-branch-feat-create/SKILL.md +115 -0
- package/templates/skills/cbp-git-commit/SKILL.md +278 -0
- package/templates/skills/cbp-git-worktree-create/SKILL.md +226 -0
- package/templates/skills/cbp-git-worktree-remove/SKILL.md +145 -0
- package/templates/skills/cbp-merge-main/SKILL.md +228 -0
- package/templates/skills/cbp-round-check/SKILL.md +104 -0
- package/templates/skills/cbp-round-end/SKILL.md +183 -0
- package/templates/skills/cbp-round-end/reference/findings-presentation.md +44 -0
- package/templates/skills/cbp-round-end/reference/inline-fallback.md +35 -0
- package/templates/skills/cbp-round-execute/SKILL.md +211 -0
- package/templates/skills/cbp-round-execute/reference/inline-fallback.md +59 -0
- package/templates/skills/cbp-round-input/SKILL.md +165 -0
- package/templates/skills/cbp-round-start/SKILL.md +222 -0
- package/templates/skills/cbp-round-update/SKILL.md +163 -0
- package/templates/skills/cbp-session-end/SKILL.md +187 -0
- package/templates/skills/cbp-session-start/SKILL.md +155 -0
- package/templates/skills/cbp-ship/SKILL.md +332 -0
- package/templates/skills/cbp-ship/reference/changesets-overview.md +120 -0
- package/templates/skills/cbp-ship/reference/eas-cli-overview.md +60 -0
- package/templates/skills/cbp-ship/reference/gh-cli-overview.md +135 -0
- package/templates/skills/cbp-ship/reference/gh-cli-shipment-commands.md +283 -0
- package/templates/skills/cbp-ship/reference/npm-publish-monorepo.md +252 -0
- package/templates/skills/cbp-ship/reference/npm-publish-oidc-trusted.md +157 -0
- package/templates/skills/cbp-ship/reference/npm-publish-overview.md +171 -0
- package/templates/skills/cbp-ship/reference/preflight-checklist.md +88 -0
- package/templates/skills/cbp-ship/reference/railway-nestjs-deployment.md +169 -0
- package/templates/skills/cbp-ship/reference/railway-overview.md +120 -0
- package/templates/skills/cbp-ship/reference/railway-troubleshooting.md +168 -0
- package/templates/skills/cbp-ship/reference/release-please-overview.md +99 -0
- package/templates/skills/cbp-ship/reference/surface-expo-eas.md +155 -0
- package/templates/skills/cbp-ship/reference/surface-npm.md +180 -0
- package/templates/skills/cbp-ship/reference/surface-railway.md +152 -0
- package/templates/skills/cbp-ship/reference/surface-supabase.md +178 -0
- package/templates/skills/cbp-ship/reference/surface-tauri.md +138 -0
- package/templates/skills/cbp-ship/reference/surface-vercel.md +124 -0
- package/templates/skills/cbp-ship/reference/surface-vscode-ext.md +144 -0
- package/templates/skills/cbp-ship/reference/surfaces.md +60 -0
- package/templates/skills/cbp-ship/reference/testflight-automation.md +215 -0
- package/templates/skills/cbp-ship/reference/testflight-internal-vs-external.md +69 -0
- package/templates/skills/cbp-ship/reference/testflight-overview.md +98 -0
- package/templates/skills/cbp-ship/reference/versioning.md +116 -0
- package/templates/skills/cbp-ship/scripts/detect-surfaces.sh +217 -0
- package/templates/skills/cbp-ship/scripts/verify-expo-eas.sh +35 -0
- package/templates/skills/cbp-ship/scripts/verify-npm.sh +21 -0
- package/templates/skills/cbp-ship/scripts/verify-railway.sh +41 -0
- package/templates/skills/cbp-ship/scripts/verify-supabase.sh +19 -0
- package/templates/skills/cbp-ship/scripts/verify-tauri.sh +24 -0
- package/templates/skills/cbp-ship/scripts/verify-vercel.sh +32 -0
- package/templates/skills/cbp-ship/scripts/verify-vscode-ext.sh +25 -0
- package/templates/skills/cbp-ship/templates/eas.json +66 -0
- package/templates/skills/cbp-ship/templates/railway.toml +15 -0
- package/templates/skills/cbp-ship/templates/release-please-config.json +17 -0
- package/templates/skills/cbp-ship/templates/vercel.json +19 -0
- package/templates/skills/cbp-ship/templates/vscodeignore +21 -0
- package/templates/skills/cbp-ship/templates/workflow-changesets.yml +41 -0
- package/templates/skills/cbp-ship/templates/workflow-eas-submit.yml +53 -0
- package/templates/skills/cbp-ship/templates/workflow-npm-publish.yml +36 -0
- package/templates/skills/cbp-ship/templates/workflow-release-please.yml +21 -0
- package/templates/skills/cbp-ship/templates/workflow-tauri-release.yml +69 -0
- package/templates/skills/cbp-ship/templates/workflow-vsce-publish.yml +31 -0
- package/templates/skills/cbp-ship-configure/SKILL.md +296 -0
- package/templates/skills/cbp-ship-configure/reference/expo-mobile.md +204 -0
- package/templates/skills/cbp-ship-configure/reference/npm-package.md +165 -0
- package/templates/skills/cbp-ship-configure/reference/railway-backend.md +199 -0
- package/templates/skills/cbp-ship-configure/reference/supabase.md +200 -0
- package/templates/skills/cbp-ship-configure/reference/tauri-desktop.md +181 -0
- package/templates/skills/cbp-ship-configure/reference/vercel.md +117 -0
- package/templates/skills/cbp-ship-configure/reference/vscode-ext.md +155 -0
- package/templates/skills/cbp-ship-main/SKILL.md +65 -0
- package/templates/skills/cbp-supabase-branch-check/SKILL.md +337 -0
- package/templates/skills/cbp-supabase-branch-check/reference/dag-steps.md +29 -0
- package/templates/skills/cbp-supabase-migrate/SKILL.md +314 -0
- package/templates/skills/cbp-supabase-migrate/reference/advisor-triage.md +70 -0
- package/templates/skills/cbp-supabase-migrate/reference/cli-fallback.md +87 -0
- package/templates/skills/cbp-supabase-migrate/reference/preflight-dry-run.md +58 -0
- package/templates/skills/cbp-supabase-setup/SKILL.md +239 -0
- package/templates/skills/cbp-supabase-setup/reference/branching-setup.md +121 -0
- package/templates/skills/cbp-supabase-setup/reference/cli-fallback.md +109 -0
- package/templates/skills/cbp-task-check/SKILL.md +166 -0
- package/templates/skills/cbp-task-complete/SKILL.md +206 -0
- package/templates/skills/cbp-task-complete/reference/checkpoint-done-branching.md +48 -0
- package/templates/skills/cbp-task-complete/reference/next-step-heuristic.md +56 -0
- package/templates/skills/cbp-task-create/SKILL.md +167 -0
- package/templates/skills/cbp-task-start/SKILL.md +239 -0
- package/templates/skills/cbp-task-testing/SKILL.md +277 -0
- package/templates/skills/cbp-todo/SKILL.md +111 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Contrast and Visual Accessibility Reference
|
|
2
|
+
|
|
3
|
+
## WCAG 2.1 AA Contrast Requirements
|
|
4
|
+
|
|
5
|
+
All text and UI components must meet these minimum contrast ratios:
|
|
6
|
+
|
|
7
|
+
| Content type | Minimum ratio | Standard |
|
|
8
|
+
|-------------|--------------|---------|
|
|
9
|
+
| Normal text (< 18pt / < 14pt bold) | **4.5:1** | WCAG 2.1 AA SC 1.4.3 |
|
|
10
|
+
| Large text (≥ 18pt / ≥ 14pt bold) | **3:1** | WCAG 2.1 AA SC 1.4.3 |
|
|
11
|
+
| UI components (borders, icons, form controls) | **3:1** | WCAG 2.1 AA SC 1.4.11 |
|
|
12
|
+
| Graphical objects (data chart elements, icons conveying meaning) | **3:1** | WCAG 2.1 AA SC 1.4.11 |
|
|
13
|
+
|
|
14
|
+
**Decorative** elements (purely ornamental, no meaning) are EXEMPT.
|
|
15
|
+
|
|
16
|
+
### Verification
|
|
17
|
+
|
|
18
|
+
Use design tokens from `packages/design-tokens/` to derive hex values, then verify with a contrast checker:
|
|
19
|
+
|
|
20
|
+
- Browser DevTools Accessibility panel
|
|
21
|
+
- `npx @accessibility-checker/cli` against the rendered component
|
|
22
|
+
- Design-tool plugins (Figma Contrast, Stark)
|
|
23
|
+
|
|
24
|
+
If a token pair is new, record the ratio in a comment in the SCSS: `/* contrast: 5.2:1 vs --color-surface */`.
|
|
25
|
+
|
|
26
|
+
## Focus Visible
|
|
27
|
+
|
|
28
|
+
Focus indicators must NEVER be suppressed via `outline: none` or `outline: 0` without providing a replacement that meets **3:1 contrast** against adjacent colour:
|
|
29
|
+
|
|
30
|
+
```scss
|
|
31
|
+
// Anti-pattern — removes all visible focus indicator
|
|
32
|
+
&:focus {
|
|
33
|
+
outline: none; // WCAG failure
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Correct — custom focus ring that meets 3:1 contrast
|
|
37
|
+
&:focus-visible {
|
|
38
|
+
outline: 2px solid var(--color-focus-ring); // 3:1 minimum
|
|
39
|
+
outline-offset: 2px;
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Use `:focus-visible` (not `:focus`) for the custom ring — `:focus-visible` suppresses the ring for mouse clicks while preserving it for keyboard navigation.
|
|
44
|
+
|
|
45
|
+
The WCAG 2.2 enhanced criterion (SC 2.4.11, AAA) requires 3:1 contrast + 2px outline area. Target this for new components.
|
|
46
|
+
|
|
47
|
+
## Colour-Only State Communication
|
|
48
|
+
|
|
49
|
+
State conveyed through colour alone is a WCAG 2.1 AA failure (SC 1.4.1). Always pair colour with at least one of: icon, text label, pattern, or shape.
|
|
50
|
+
|
|
51
|
+
| Anti-pattern | Fix |
|
|
52
|
+
|-------------|-----|
|
|
53
|
+
| Red border = error (colour only) | Red border + error icon + "Invalid email" text |
|
|
54
|
+
| Green = online (colour only) | Green dot + "Online" text label |
|
|
55
|
+
| Yellow = warning (colour only) | Yellow background + warning icon + descriptive text |
|
|
56
|
+
| Active nav item is blue (colour only) | Blue + `aria-current="page"` + underline or bold weight |
|
|
57
|
+
|
|
58
|
+
## prefers-reduced-motion
|
|
59
|
+
|
|
60
|
+
Users with vestibular disorders may configure `prefers-reduced-motion: reduce` in their OS. Honour it:
|
|
61
|
+
|
|
62
|
+
```scss
|
|
63
|
+
@keyframes slideIn {
|
|
64
|
+
from { transform: translateX(-100%); }
|
|
65
|
+
to { transform: translateX(0); }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.panel {
|
|
69
|
+
animation: slideIn 300ms ease-out;
|
|
70
|
+
|
|
71
|
+
@media (prefers-reduced-motion: reduce) {
|
|
72
|
+
animation: none;
|
|
73
|
+
// Provide instant appearance or a fade (no translate/scale/spin)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
For JavaScript-driven animations:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
82
|
+
if (!prefersReduced) {
|
|
83
|
+
// run animation
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Transitions that solely affect `opacity` (fade) are generally safe — opacity changes do not trigger vestibular responses. Transforms (`translate`, `scale`, `rotate`) and large motion sweeps are the primary triggers.
|
|
88
|
+
|
|
89
|
+
## Touch Target Size
|
|
90
|
+
|
|
91
|
+
Interactive elements must have a minimum tap target of **44 × 44 CSS px** (Apple HIG / WCAG 2.5.5 AAA, de-facto standard):
|
|
92
|
+
|
|
93
|
+
```scss
|
|
94
|
+
.icon-button {
|
|
95
|
+
min-width: 44px;
|
|
96
|
+
min-height: 44px;
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
justify-content: center;
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
When the visible icon is smaller (e.g. 24px), expand the tap target with padding:
|
|
104
|
+
|
|
105
|
+
```scss
|
|
106
|
+
.icon-button {
|
|
107
|
+
padding: 10px; // 24px icon + 2×10px padding = 44px touch target
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
On mobile breakpoints specifically, verify that adjacent interactive elements have at least 8px spacing between tap target edges to prevent accidental activation.
|
|
112
|
+
|
|
113
|
+
## Text Resizing
|
|
114
|
+
|
|
115
|
+
Users who increase browser text size up to 200% must be able to read and use all content without horizontal scroll (WCAG 1.4.4). Use relative units:
|
|
116
|
+
|
|
117
|
+
- `font-size`: `rem` (relative to browser root, respects user preference)
|
|
118
|
+
- `line-height`: unitless (e.g. `1.5`) or `em`
|
|
119
|
+
- Container widths: `max-width` in `ch` or `%`, never fixed `px` for reading columns
|
|
120
|
+
- Spacing: `em` for component-internal spacing; `rem` for layout spacing
|
|
121
|
+
|
|
122
|
+
Avoid `px` for font sizes on text elements that users may need to scale.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Keyboard Interaction Patterns Reference
|
|
2
|
+
|
|
3
|
+
Every interactive component must be fully operable with a keyboard alone. Mouse-only patterns are WCAG 2.1 AA failures (Success Criterion 2.1.1).
|
|
4
|
+
|
|
5
|
+
## Focus Management
|
|
6
|
+
|
|
7
|
+
### On Mount (Dialog / Modal / Drawer)
|
|
8
|
+
|
|
9
|
+
When a dialog, modal, or drawer opens:
|
|
10
|
+
|
|
11
|
+
1. Move focus to the FIRST interactive element inside (or to the dialog container if no interactive element exists — ensure `tabindex="0"` on the container in that case)
|
|
12
|
+
2. For modals with a clear primary action: move focus to the primary action button
|
|
13
|
+
3. For forms: move focus to the first form field
|
|
14
|
+
|
|
15
|
+
```jsx
|
|
16
|
+
// Using useEffect + ref
|
|
17
|
+
const modalRef = useRef<HTMLDivElement>(null);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (isOpen) {
|
|
21
|
+
// Focus the first focusable element inside
|
|
22
|
+
const firstFocusable = modalRef.current?.querySelector<HTMLElement>(
|
|
23
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
24
|
+
);
|
|
25
|
+
firstFocusable?.focus();
|
|
26
|
+
}
|
|
27
|
+
}, [isOpen]);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### On Close (Dialog / Modal / Drawer)
|
|
31
|
+
|
|
32
|
+
When a dialog, modal, or drawer closes:
|
|
33
|
+
|
|
34
|
+
1. Return focus to the element that TRIGGERED the opening (save a ref to it before opening)
|
|
35
|
+
2. If the trigger no longer exists (deleted row), focus the nearest logical element
|
|
36
|
+
|
|
37
|
+
```jsx
|
|
38
|
+
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
39
|
+
|
|
40
|
+
const handleClose = () => {
|
|
41
|
+
setIsOpen(false);
|
|
42
|
+
// Return focus to trigger
|
|
43
|
+
triggerRef.current?.focus();
|
|
44
|
+
};
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Tab Order
|
|
48
|
+
|
|
49
|
+
- Tab order must follow the logical reading/interaction order — typically top-left to bottom-right
|
|
50
|
+
- Never use `tabindex > 0` — it creates a separate tab order before natural DOM order, confusing all keyboard users
|
|
51
|
+
- `tabindex="0"` adds a non-interactive element to natural tab order (use sparingly — prefer interactive elements)
|
|
52
|
+
- `tabindex="-1"` removes from natural tab order but allows programmatic `.focus()` (correct for modal containers)
|
|
53
|
+
|
|
54
|
+
## Esc Key
|
|
55
|
+
|
|
56
|
+
Any overlay, popover, dropdown, or modal MUST close on `Escape`:
|
|
57
|
+
|
|
58
|
+
```jsx
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
61
|
+
if (e.key === 'Escape') {
|
|
62
|
+
onClose();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
if (isOpen) {
|
|
66
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
67
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
68
|
+
}
|
|
69
|
+
}, [isOpen, onClose]);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Arrow Key Navigation
|
|
73
|
+
|
|
74
|
+
For composite widgets (menu, listbox, radio group, tabs, grid), arrow keys navigate BETWEEN items — Tab moves focus OUT of the widget entirely.
|
|
75
|
+
|
|
76
|
+
| Widget | Keys |
|
|
77
|
+
|--------|------|
|
|
78
|
+
| Menu / dropdown | `↑`/`↓` between items, `Home`/`End` to first/last |
|
|
79
|
+
| Tabs | `←`/`→` between tabs (horizontal) or `↑`/`↓` (vertical) |
|
|
80
|
+
| Listbox | `↑`/`↓` between options, `Home`/`End` |
|
|
81
|
+
| Grid | `↑`/`↓`/`←`/`→` between cells |
|
|
82
|
+
| Radio group | `↑`/`↓` or `←`/`→` between radios; selection follows focus |
|
|
83
|
+
|
|
84
|
+
## Focus Traps (Modal)
|
|
85
|
+
|
|
86
|
+
While a modal is open, Tab and Shift+Tab must cycle WITHIN the modal — not escape to the page behind:
|
|
87
|
+
|
|
88
|
+
```jsx
|
|
89
|
+
const FOCUSABLE_SELECTORS =
|
|
90
|
+
'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
91
|
+
|
|
92
|
+
const handleTabKey = (e: KeyboardEvent) => {
|
|
93
|
+
const focusableEls = Array.from(
|
|
94
|
+
modalRef.current?.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS) ?? []
|
|
95
|
+
);
|
|
96
|
+
const first = focusableEls[0];
|
|
97
|
+
const last = focusableEls[focusableEls.length - 1];
|
|
98
|
+
|
|
99
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
last.focus();
|
|
102
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
103
|
+
e.preventDefault();
|
|
104
|
+
first.focus();
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Library alternative: `focus-trap-react` handles this pattern with accessibility compliance. If it is already in `package.json`, use it.
|
|
110
|
+
|
|
111
|
+
## Type-Ahead (Listbox / Menu)
|
|
112
|
+
|
|
113
|
+
When a user types a character while focus is inside a listbox or menu, focus jumps to the first item whose label starts with that character. Implement only when `role="listbox"` or `role="menu"` is used with custom keyboard handling — native `<select>` has this built in.
|
|
114
|
+
|
|
115
|
+
## Enter and Space Activation
|
|
116
|
+
|
|
117
|
+
| Element | Enter | Space |
|
|
118
|
+
|---------|-------|-------|
|
|
119
|
+
| `<button>` | Activates | Activates |
|
|
120
|
+
| `<a href>` | Follows link | Scrolls page (native browser) |
|
|
121
|
+
| `<input type="checkbox">` | n/a | Toggles |
|
|
122
|
+
| `role="button"` | Must activate | Must activate |
|
|
123
|
+
| `role="menuitem"` | Activates | Activates |
|
|
124
|
+
|
|
125
|
+
For custom `role="button"` on a non-button element:
|
|
126
|
+
```jsx
|
|
127
|
+
<div
|
|
128
|
+
role="button"
|
|
129
|
+
tabIndex={0}
|
|
130
|
+
onKeyDown={(e) => {
|
|
131
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
132
|
+
e.preventDefault();
|
|
133
|
+
handleActivate();
|
|
134
|
+
}
|
|
135
|
+
}}
|
|
136
|
+
onClick={handleActivate}
|
|
137
|
+
>
|
|
138
|
+
Custom button
|
|
139
|
+
</div>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Prefer `<button>` — it handles Enter/Space natively and avoids this boilerplate.
|
|
143
|
+
|
|
144
|
+
## Skip Links
|
|
145
|
+
|
|
146
|
+
For pages with repeated navigation (header nav present on every page), provide a skip-to-main-content link as the first focusable element:
|
|
147
|
+
|
|
148
|
+
```jsx
|
|
149
|
+
<a href="#main-content" className={styles.skipLink}>
|
|
150
|
+
Skip to main content
|
|
151
|
+
</a>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Visible on focus (via CSS), hidden otherwise. The `<main id="main-content">` is the target.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Semantic HTML Reference
|
|
2
|
+
|
|
3
|
+
Use native HTML semantics first. ARIA is a gap-filler for when no native element meets the need — not a replacement.
|
|
4
|
+
|
|
5
|
+
## Landmark Roles
|
|
6
|
+
|
|
7
|
+
| Element | Role | Notes |
|
|
8
|
+
|---------|------|-------|
|
|
9
|
+
| `<main>` | `main` | Exactly ONE per page |
|
|
10
|
+
| `<nav>` | `navigation` | Multiple allowed; each needs an `aria-label` |
|
|
11
|
+
| `<header>` | `banner` (at page scope) | In a sectioning element it becomes generic |
|
|
12
|
+
| `<footer>` | `contentinfo` (at page scope) | |
|
|
13
|
+
| `<aside>` | `complementary` | |
|
|
14
|
+
| `<section>` | `region` (only when it has an `aria-labelledby`) | Without label, it's generic |
|
|
15
|
+
|
|
16
|
+
Anti-pattern: `<div role="main">` — use `<main>` instead.
|
|
17
|
+
|
|
18
|
+
## Heading Hierarchy
|
|
19
|
+
|
|
20
|
+
- One `<h1>` per page — the page title or primary content heading
|
|
21
|
+
- Never skip levels: `h1 → h2 → h3`, not `h1 → h3`
|
|
22
|
+
- Headings convey structure, not visual size — use CSS for size; use the correct level for hierarchy
|
|
23
|
+
- Empty headings (`<h2></h2>`) violate WCAG 2.4.6
|
|
24
|
+
|
|
25
|
+
## `<button>` vs `<a>`
|
|
26
|
+
|
|
27
|
+
| Use | Element |
|
|
28
|
+
|-----|---------|
|
|
29
|
+
| Triggers an action (submit, open modal, toggle) | `<button>` |
|
|
30
|
+
| Navigates to a URL | `<a href="...">` |
|
|
31
|
+
| Downloads a file | `<a href="..." download>` |
|
|
32
|
+
|
|
33
|
+
Anti-patterns:
|
|
34
|
+
- `<div onClick={doAction}>` — not keyboard accessible; use `<button>`
|
|
35
|
+
- `<button onClick={() => router.push('/path')}>` — use `<a href="/path">`; or `<Link href="/path">` in Next.js
|
|
36
|
+
- `<a href="#">` with an onClick — use `<button>` if no real URL
|
|
37
|
+
|
|
38
|
+
## Form Elements
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<!-- Correct: explicit association -->
|
|
42
|
+
<label htmlFor="email-input">Email</label>
|
|
43
|
+
<input id="email-input" type="email" name="email" />
|
|
44
|
+
|
|
45
|
+
<!-- Correct: implicit wrapping -->
|
|
46
|
+
<label>
|
|
47
|
+
Email
|
|
48
|
+
<input type="email" name="email" />
|
|
49
|
+
</label>
|
|
50
|
+
|
|
51
|
+
<!-- Anti-pattern: no association -->
|
|
52
|
+
<span>Email</span>
|
|
53
|
+
<input type="email" name="email" />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
For fieldsets with radio/checkbox groups:
|
|
57
|
+
```html
|
|
58
|
+
<fieldset>
|
|
59
|
+
<legend>Preferred contact method</legend>
|
|
60
|
+
<label><input type="radio" name="contact" value="email" /> Email</label>
|
|
61
|
+
<label><input type="radio" name="contact" value="phone" /> Phone</label>
|
|
62
|
+
</fieldset>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Lists
|
|
66
|
+
|
|
67
|
+
Use `<ul>/<ol>/<li>` for lists of items — screen readers announce item count and position.
|
|
68
|
+
|
|
69
|
+
Anti-pattern: `<div class="list"><div class="item">...</div></div>` — no count/position announced.
|
|
70
|
+
|
|
71
|
+
Exception: `list-style: none` on `<ul>` removes list semantics in Safari/VoiceOver. Add `role="list"` when list-style is suppressed:
|
|
72
|
+
```jsx
|
|
73
|
+
<ul role="list" style={{ listStyle: 'none' }}>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Tables
|
|
77
|
+
|
|
78
|
+
For data tables (not layout):
|
|
79
|
+
```html
|
|
80
|
+
<table>
|
|
81
|
+
<caption>Monthly sales by region</caption>
|
|
82
|
+
<thead>
|
|
83
|
+
<tr>
|
|
84
|
+
<th scope="col">Region</th>
|
|
85
|
+
<th scope="col">Sales</th>
|
|
86
|
+
</tr>
|
|
87
|
+
</thead>
|
|
88
|
+
<tbody>
|
|
89
|
+
<tr>
|
|
90
|
+
<th scope="row">North</th>
|
|
91
|
+
<td>$12,000</td>
|
|
92
|
+
</tr>
|
|
93
|
+
</tbody>
|
|
94
|
+
</table>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`<caption>` is the table's accessible name. `scope="col"/"row"` associates headers with cells.
|
|
98
|
+
|
|
99
|
+
Never use `<table>` for visual layout — use CSS Grid or Flexbox.
|
|
100
|
+
|
|
101
|
+
## Anti-Pattern Quick-Reference
|
|
102
|
+
|
|
103
|
+
| Anti-pattern | Fix |
|
|
104
|
+
|-------------|-----|
|
|
105
|
+
| `<div onClick>` | `<button>` or `<a>` |
|
|
106
|
+
| `<span onClick>` | `<button>` |
|
|
107
|
+
| `<img>` missing `alt` | Add `alt="description"` or `alt=""` for decorative |
|
|
108
|
+
| `<input>` missing label | Add `<label htmlFor>` or `aria-label` |
|
|
109
|
+
| `<h1>` used for styling | Use CSS; pick correct heading level |
|
|
110
|
+
| `<table>` for layout | Use CSS Grid/Flexbox |
|
|
111
|
+
| Empty `<button>` (icon only) | Add `aria-label="Close"` |
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
scope: org-shared
|
|
3
|
+
name: cbp-frontend-design
|
|
4
|
+
description: Up-front design playbook loaded BEFORE writing UI / styling code. Detects the stack, loads the matching reference file, commits to an aesthetic direction, and prevents generic AI-slop output. Modelled on Anthropic's frontend-design skill, adapted for CBP repos with existing design systems.
|
|
5
|
+
effort: xhigh
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Frontend Design (Pre-Implementation Playbook)
|
|
9
|
+
|
|
10
|
+
Loaded by `round-executor` Step 2.7 BEFORE any UI or styling file is written. Not a review skill — review happens later via the `frontend-ui` and `frontend-ux` skills, invoked inline by `round-executor` Step 3.8 after code is written. This is the up-front "think before you code" pass that produces good design at the first attempt instead of catching bad design after.
|
|
11
|
+
|
|
12
|
+
## When this skill fires
|
|
13
|
+
|
|
14
|
+
Round-executor invokes when the round's `files_to_modify` contains ANY of:
|
|
15
|
+
|
|
16
|
+
- `*.tsx`, `*.jsx` (React, RN, RN-Web components)
|
|
17
|
+
- `*.scss`, `*.css`, `*.module.{scss,css}`
|
|
18
|
+
- Files under `packages/design-tokens/`, `apps/*/styles/`, design-system folders
|
|
19
|
+
- A new page, screen, route, or component file
|
|
20
|
+
- Plan deliverables mentioning UI, layout, visual, screen, page, modal, button, form
|
|
21
|
+
|
|
22
|
+
If none match, skip — proceed to Step 3.
|
|
23
|
+
|
|
24
|
+
## Phase 1: Read the brand BEFORE choosing an aesthetic
|
|
25
|
+
|
|
26
|
+
In an existing app, the brand already exists. Discovery is mandatory before any creative choice:
|
|
27
|
+
|
|
28
|
+
1. Find the design tokens — `packages/design-tokens/`, `apps/*/styles/tokens.*`, `apps/*/src/theme/*`, or equivalent. Read what colours, type scale, spacing scale, radii, shadows, motion, breakpoints are defined.
|
|
29
|
+
2. Find sibling components — open 2 components in the same folder as the file you're about to write. Note their layout, density, type usage, motion presence.
|
|
30
|
+
3. Find the established aesthetic in 30 seconds: refined-minimal? Dense-data? Editorial? Brutalist? Soft-pastel? Industrial? Read 1 page-level component to feel the answer.
|
|
31
|
+
|
|
32
|
+
### Design source images (when provided)
|
|
33
|
+
|
|
34
|
+
If the requirements reference design sources OR the round is page-level:
|
|
35
|
+
|
|
36
|
+
- **Location**: `/docs/development/product/sources/design/*.png` — Glob by page or component name from requirements
|
|
37
|
+
- **Read** matching PNGs before writing `plan.ui_ux_considerations.visual_notes` (planner) or before any code (executor)
|
|
38
|
+
- **Extract**:
|
|
39
|
+
- Control shapes (flat buttons, pill buttons, toggles, steppers, dropdowns)
|
|
40
|
+
- Background colours within cards and sections
|
|
41
|
+
- Column layout and which column holds action controls
|
|
42
|
+
- Dividers, separators, row structure
|
|
43
|
+
- Spacing patterns between rows and groups
|
|
44
|
+
- Placeholder style, border, input shape — for embedded form controls
|
|
45
|
+
|
|
46
|
+
If no PNGs exist, skip silently and rely on sibling-component discovery (steps 1–3 above).
|
|
47
|
+
|
|
48
|
+
### Embedded controls also count
|
|
49
|
+
|
|
50
|
+
When a round adds a NEW interactive control (input, button, tag, toggle block) inside an EXISTING row or card component, the same design-source reading applies — even when:
|
|
51
|
+
|
|
52
|
+
- The parent component already exists
|
|
53
|
+
- The new control is "small" (e.g., an address input inside a row)
|
|
54
|
+
- The design change seems incremental
|
|
55
|
+
|
|
56
|
+
Iterative discovery of visual constraints causes 3–5 styling rework rounds. A single design-source read up-front prevents them.
|
|
57
|
+
|
|
58
|
+
The output of Phase 1 is a one-line **brand commitment**: "this app is X, so the new component must read as X." Skip Phase 1 only if the round is greenfield (no sibling components, no PNGs).
|
|
59
|
+
|
|
60
|
+
## Phase 2: Detect stack and load the reference file
|
|
61
|
+
|
|
62
|
+
Detect the host app's stack from the file paths in `files_to_modify`:
|
|
63
|
+
|
|
64
|
+
| Signal | Stack | Reference to load |
|
|
65
|
+
|--------|-------|-------------------|
|
|
66
|
+
| `next.config.ts` near target + `*.module.scss` | Next.js + SCSS Modules | `references/nextjs-scss.md` |
|
|
67
|
+
| `expo` in deps + `*.tsx` under `apps/mobile/` | React Native + Expo | `references/rn-expo.md` |
|
|
68
|
+
| `tauri.conf.json` + React frontend | Tauri + React | `references/tauri-react.md` |
|
|
69
|
+
|
|
70
|
+
Read the matching reference file with the Read tool — never paraphrase from memory. The reference encodes stack-idiomatic conventions (token usage, layout primitives, animation libraries, accessibility patterns).
|
|
71
|
+
|
|
72
|
+
If the stack does NOT match any reference, surface to the user via `AskUserQuestion` before guessing — don't ship generic CSS that ignores the host app's idioms.
|
|
73
|
+
|
|
74
|
+
## Phase 3: Commit to a direction
|
|
75
|
+
|
|
76
|
+
Greenfield: pick a BOLD aesthetic direction (one extreme — brutally minimal, maximalist, editorial, brutalist, soft-pastel, industrial, etc.) and execute it with precision. Bold maximalism and refined minimalism both work — the key is intentionality.
|
|
77
|
+
|
|
78
|
+
Existing app: the direction is already chosen. Match it exactly. Match the established density, type contrast, motion vocabulary, colour weighting. New components that look "AI-default" because they ignored the existing aesthetic are the most common round-rework cause.
|
|
79
|
+
|
|
80
|
+
Either way, write down the commitment in one sentence in the round notes: "Direction: {phrase}." This forces the choice to be conscious.
|
|
81
|
+
|
|
82
|
+
## Phase 4: Universal aesthetics guidelines
|
|
83
|
+
|
|
84
|
+
These apply regardless of stack. From Anthropic's frontend-design skill, adapted:
|
|
85
|
+
|
|
86
|
+
- **Typography**: distinctive, not generic. Pair a display font with a refined body font. Avoid Arial / Roboto / Inter unless the existing design system mandates them.
|
|
87
|
+
- **Colour & theme**: dominant colour with sharp accents beats a timid evenly-distributed palette. Use the design tokens.
|
|
88
|
+
- **Motion**: high-impact moments over scattered micro-interactions. One well-orchestrated entrance with staggered reveals beats a hover-effect on every button.
|
|
89
|
+
- **Spatial composition**: asymmetry, overlap, diagonal flow, grid-breaking. Generous negative space OR controlled density — pick.
|
|
90
|
+
- **Backgrounds & visual details**: atmosphere and depth, not solid colours. Gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, grain overlays — when the aesthetic calls for it.
|
|
91
|
+
|
|
92
|
+
Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code. Minimalist designs need restraint, precision, and careful spacing/typography.
|
|
93
|
+
|
|
94
|
+
## Phase 5: What NEVER to ship
|
|
95
|
+
|
|
96
|
+
- Generic AI defaults: Inter / Roboto / Arial / system-fonts when the design system has its own
|
|
97
|
+
- Cliché schemes (purple gradients on white, neon teal accent on dark navy)
|
|
98
|
+
- Predictable card-grid layouts when the design language is editorial
|
|
99
|
+
- Cookie-cutter component patterns that ignore the surrounding aesthetic
|
|
100
|
+
- "Safe" middle-ground choices that read as undecided
|
|
101
|
+
- The same aesthetic across two unrelated components in the same round
|
|
102
|
+
|
|
103
|
+
## Phase 6: Pre-write checklist
|
|
104
|
+
|
|
105
|
+
Before writing the first character of UI / styling code, confirm:
|
|
106
|
+
|
|
107
|
+
| Check | Pass condition |
|
|
108
|
+
|-------|----------------|
|
|
109
|
+
| Tokens located | Read tokens file path on disk |
|
|
110
|
+
| Sibling components read | At least 2 reference components opened |
|
|
111
|
+
| Stack reference loaded | One of `reference/*.md` read |
|
|
112
|
+
| Design sources reviewed | PNG location checked, visual constraints extracted (or "no PNGs found" recorded) |
|
|
113
|
+
| Direction committed | One-sentence aesthetic commitment written down |
|
|
114
|
+
| Repo-specific rule constraints | Auto-loaded rules for the file paths checked |
|
|
115
|
+
| Stack-specific rule constraints | See `reference/*.md` "Mandatory rules" sections |
|
|
116
|
+
|
|
117
|
+
### Floating panel close-on-deselect (cross-stack)
|
|
118
|
+
|
|
119
|
+
Any time a component declares `useState(false)` for a variable named `show*`, `open*`, `visible*`, or `expanded*` AND the component receives an `isSelected` / `isActive` prop:
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
const [showPanel, setShowPanel] = useState(false);
|
|
123
|
+
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (!isSelected) setShowPanel(false);
|
|
126
|
+
}, [isSelected]);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Adding the close-on-deselect effect at the moment the state is introduced is mandatory. Style toolbars, dropdowns, popovers, context menus, action sheets all share this lifecycle.
|
|
130
|
+
|
|
131
|
+
Unit tests for floating panels MUST cover: panel shows when toggled with isSelected=true; panel hides when isSelected transitions true→false; panel state does NOT persist across select/deselect cycles.
|
|
132
|
+
|
|
133
|
+
If any check fails, fix before proceeding to Step 3.
|
|
134
|
+
|
|
135
|
+
## Output back to the round-executor
|
|
136
|
+
|
|
137
|
+
After Phase 6, the executor proceeds to Step 3 with the brand commitment + stack reference + direction in working memory. Round-executor records `frontend_design_loaded: { stack, direction, tokens_path }` in `round.context` so `frontend-ui` (Step 3.8) and `cbp-improve-round` can verify the commitment was honoured.
|
|
138
|
+
|
|
139
|
+
## Integration
|
|
140
|
+
|
|
141
|
+
- **Loaded by**: `round-executor` Step 2.7 (mandatory when has_ui_work / UI-class files present)
|
|
142
|
+
- **Reads**: tokens files, sibling components, `references/{stack}.md`, auto-loaded UI rules
|
|
143
|
+
- **Writes**: nothing directly — output is in-memory direction + stack context for Step 3
|
|
144
|
+
- **Pattern references** (consulted during Phase 1 sibling discovery and Phase 6 pre-write checks): `frontend-ui/reference/ui-layout-patterns.md`, `frontend-ui/reference/variant-defaults.md`, `frontend-ui/reference/ui-label-maps.md`
|
|
145
|
+
- **Paired with (post-implementation)**: `frontend-ui` (visual self-review), `frontend-ux` (interaction self-review) — both invoked inline by `round-executor` Step 3.8. This skill prevents bad design; those skills catch what slipped through.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Next.js + SCSS Modules — Stack Reference
|
|
2
|
+
|
|
3
|
+
Loaded by `frontend-design` Phase 2 when the host app uses Next.js (App Router) with SCSS Modules. Applies to `codebyplan` (`apps/web`), `tarkur` (`apps/*/web`).
|
|
4
|
+
|
|
5
|
+
## Tokens
|
|
6
|
+
|
|
7
|
+
Single source of truth for design tokens — never hardcode colour, spacing, type, radius, shadow values in component SCSS:
|
|
8
|
+
|
|
9
|
+
| Path pattern | Purpose |
|
|
10
|
+
|--------------|---------|
|
|
11
|
+
| `packages/design-tokens/src/tokens.scss` | Canonical SCSS tokens — colours, spacing scale, radii, shadows, motion |
|
|
12
|
+
| `packages/design-tokens/src/tokens.ts` | TypeScript mirror for runtime token access |
|
|
13
|
+
| `apps/*/src/styles/_layout.scss` | App-specific layout variables (navbar, sidebar widths) |
|
|
14
|
+
| `apps/*/src/styles/_typography.scss` | Type scale + font-family vars |
|
|
15
|
+
|
|
16
|
+
Read the tokens file on disk before writing any component's SCSS. If a colour or spacing isn't already a token, surface the gap — don't introduce a hex literal.
|
|
17
|
+
|
|
18
|
+
## Layout primitives
|
|
19
|
+
|
|
20
|
+
| Tool | Use for |
|
|
21
|
+
|------|---------|
|
|
22
|
+
| `fluid(min, max)` mixin | Responsive sizes that interpolate between viewport widths — fonts, padding, gap, navbar height, etc. |
|
|
23
|
+
| `clamp(min, preferred, max)` | When `fluid` doesn't fit and you need browser-native interpolation |
|
|
24
|
+
| CSS Grid | Page-level layout, dashboard regions |
|
|
25
|
+
| Flexbox | Component-internal alignment |
|
|
26
|
+
|
|
27
|
+
`fluid` is the strong default for any size that should respond to viewport. Hardcoded `rem` / `px` is a yellow flag — ask "should this scale?"
|
|
28
|
+
|
|
29
|
+
## SCSS Modules conventions
|
|
30
|
+
|
|
31
|
+
- **BEM modifiers** for variants: `.button--primary`, `.card--compact`. The variant resolver pattern (see `frontend-ui/reference/variant-defaults.md`) produces these class names.
|
|
32
|
+
- **No global selectors** — always scoped via the module. Global resets live in `apps/*/src/app/globals.scss`.
|
|
33
|
+
- **Composition over override** — prefer multiple small modules and `composes:` over `!important` or specificity wars.
|
|
34
|
+
- **Co-locate styles** — `MyComponent.tsx` + `MyComponent.module.scss` in the same folder.
|
|
35
|
+
|
|
36
|
+
## Mandatory rules to load alongside this reference
|
|
37
|
+
|
|
38
|
+
When editing files matched here, these rules also apply:
|
|
39
|
+
|
|
40
|
+
- `rules/api-route-auth-enforcement.md` — for any new route handler with auth surface
|
|
41
|
+
- `rules/eslint-fix-scope.md` — never repo-wide `--fix`
|
|
42
|
+
- `rules/lint-warnings-greenfield.md` — new ESLint configs ship with zero warnings
|
|
43
|
+
|
|
44
|
+
Pattern references the executor consults when applicable (folded into the `frontend-ui` skill — see Step 3.8):
|
|
45
|
+
|
|
46
|
+
- `frontend-ui/reference/ui-layout-patterns.md` — navbar height, focus-visible, z-index layering, fluid heights, logo overflow
|
|
47
|
+
- `frontend-ui/reference/variant-defaults.md` — `createResolver`, `SIZE_VARIANTS`, `COLOR_VARIANTS`, `CUSTOM_VARIANTS`
|
|
48
|
+
- `frontend-ui/reference/ui-label-maps.md` — humanising DB column names + enum values for user-facing UI
|
|
49
|
+
|
|
50
|
+
## Fonts and typography
|
|
51
|
+
|
|
52
|
+
The host app declares fonts via `next/font/google` or `next/font/local` in `app/layout.tsx`. Read it before adding new font imports — duplicate imports cost network round-trips and the font loader's optimisation goes through the existing declaration.
|
|
53
|
+
|
|
54
|
+
For new pages, use the type scale defined in `_typography.scss`. New `font-size` literals are a yellow flag — match them to the scale or add a token.
|
|
55
|
+
|
|
56
|
+
## Motion
|
|
57
|
+
|
|
58
|
+
Prefer CSS-only motion for hover, focus, simple transitions. Reserve JS motion (Motion library / Framer Motion) for orchestrated multi-element entrances. Page-load entrance: stagger via CSS `animation-delay` on consecutive children.
|
|
59
|
+
|
|
60
|
+
## Split-layer CSS components
|
|
61
|
+
|
|
62
|
+
When a component renders an outer container `<div>` and an inner content `<div>`, properties MUST be assigned to the correct layer before coding starts.
|
|
63
|
+
|
|
64
|
+
| Property class | Outer container | Inner content div |
|
|
65
|
+
|----------------|-----------------|-------------------|
|
|
66
|
+
| Background colour / image | YES | NO |
|
|
67
|
+
| Borders + border-radius | YES | NO |
|
|
68
|
+
| Grid placement (`gridColumn`) | YES | NO |
|
|
69
|
+
| Drag transform (`CSS.Transform`) | YES | NO |
|
|
70
|
+
| Flex layout (`display:flex`, `flexDirection`) | NO | YES |
|
|
71
|
+
| Content alignment (`alignItems`, `justifyContent`) | NO | YES |
|
|
72
|
+
| Text alignment (`textAlign`) | NO | YES |
|
|
73
|
+
|
|
74
|
+
**Full-width children require `textAlign`**: when inner children span 100% width (block default), `alignItems` alone has no visible effect. Always pair horizontal alignment with `textAlign` for text content.
|
|
75
|
+
|
|
76
|
+
**Function naming**: split-CSS utility functions name their target layer:
|
|
77
|
+
- `blockContainerCSS()` — outer (background, borders, grid)
|
|
78
|
+
- `blockContentAlignmentCSS()` — inner (flex, alignment, textAlign)
|
|
79
|
+
|
|
80
|
+
A function named `blockStyleToCSS()` (singular) is a **red flag** — it implies both layers merged, which is wrong for split-layer components.
|
|
81
|
+
|
|
82
|
+
**Planning hand-off**: when a task involves styling a component with outer + inner layers, the plan's `ui_ux_considerations` MUST include a "CSS layer contract" naming the outer + inner elements and their property assignments.
|
|
83
|
+
|
|
84
|
+
## Pre-handoff checklist (Next.js-specific)
|
|
85
|
+
|
|
86
|
+
Before marking a round complete on a Next.js app, verify these eight points:
|
|
87
|
+
|
|
88
|
+
1. **External links**: every `<a>` with `href` starting with `http://` or `https://` has `target="_blank"` AND `rel="noopener noreferrer"`
|
|
89
|
+
2. **Navigation landmarks**: every `<nav>` has `aria-label` identifying its purpose
|
|
90
|
+
3. **Next.js metadata**: `app/layout.tsx` exports BOTH `export const metadata: Metadata = { ... }` (title + description minimum) AND `export const viewport: Viewport = { themeColor: [...] }` (required for PWA / mobile)
|
|
91
|
+
4. **ESLint greenfield severity**: when `eslint.config.mjs` is new, ALL rules are `"error"`, not `"warn"` (per `lint-warnings-greenfield.md`)
|
|
92
|
+
5. **Test assertions**: every unit test file contains at least one `expect(` call beyond `toBeInTheDocument()` smoke. A `render()` without `screen.getBy*` + `expect()` is a compile check, not a test
|
|
93
|
+
6. **API route SDK imports**: any API route that imports an SDK requiring a runtime env var (`@vercel/blob`, `@supabase/ssr`, `stripe`) MUST export `export const dynamic = 'force-dynamic'` at the top of the file. Without it, Next.js static analysis runs at build time when the env var is absent and the build fails
|
|
94
|
+
7. **Async client component test patterns**: when a component uses `useEffect` with `fetch`, every test assertion on state that depends on the fetch result MUST use `waitFor`. For accessible name resolution, `aria-label` overrides inner text — tests use `getByRole('link', { name: /aria-label-text/i })`, NOT `getByText` targeting `aria-hidden` inner text
|
|
95
|
+
8. **Config file ESLint coverage**: when adding Vitest with `@typescript-eslint/parser` `projectService: true`, verify `vitest.config.ts` is in tsconfig `include` OR added to ESLint `ignores`. Without this, a parse error appears at lint time
|
|
96
|
+
|
|
97
|
+
## Floating panel close-on-deselect
|
|
98
|
+
|
|
99
|
+
See parent SKILL.md Phase 6 — applies to React floating panels (style toolbars, dropdowns, popovers, context menus). The `useEffect` close-on-deselect is mandatory at the moment the panel state is introduced.
|
|
100
|
+
|
|
101
|
+
## Common defects this stack produces
|
|
102
|
+
|
|
103
|
+
- Hardcoded hex colours instead of token variables
|
|
104
|
+
- Margin / padding values that aren't on the spacing scale (e.g. `padding: 13px` when scale is 4 / 8 / 12 / 16)
|
|
105
|
+
- `font-family: Inter` literal because the design tokens already declare a different display font
|
|
106
|
+
- `z-index: 9999` without checking the layering rule in `frontend-ui/reference/ui-layout-patterns.md`
|
|
107
|
+
- Missing `aria-label` on `<nav>` and missing `target="_blank" rel="noopener noreferrer"` on external `<a>`
|
|
108
|
+
|
|
109
|
+
The pre-write checklist in the parent SKILL.md catches these. Run it.
|
|
110
|
+
|
|
111
|
+
## Greenfield page or screen
|
|
112
|
+
|
|
113
|
+
When a brand-new top-level page is added (no sibling page exists in this app yet):
|
|
114
|
+
|
|
115
|
+
1. Pick the BOLD aesthetic direction per Phase 3 of the parent skill
|
|
116
|
+
2. Set up the page-level layout primitives: hero zone, content rhythm, scroll-driven reveals
|
|
117
|
+
3. Use the design tokens for everything; if a token is missing, add it to `packages/design-tokens` in the same round (don't fork it inline)
|
|
118
|
+
4. One delight moment per page — use it where the user dwells longest, not where they bounce
|