agentic-team 0.0.0-stage → 6.2.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.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +189 -2
  3. package/bin/build-template.js +208 -0
  4. package/bin/index.js +1334 -0
  5. package/bin/platforms.js +510 -0
  6. package/package.json +41 -4
  7. package/template/.claude-plugin/plugin.json +10 -0
  8. package/template/.orchestra/README.md +391 -0
  9. package/template/.orchestra/blueprints/README.md +87 -0
  10. package/template/.orchestra/blueprints/api-only.md +46 -0
  11. package/template/.orchestra/blueprints/component-crud-resource.md +25 -0
  12. package/template/.orchestra/blueprints/saas-starter.md +69 -0
  13. package/template/.orchestra/config.yml +293 -0
  14. package/template/.orchestra/roles/orchestrator.md +105 -0
  15. package/template/.orchestra/roles/product-manager.md +439 -0
  16. package/template/.orchestra/scripts/compact-diff.awk +45 -0
  17. package/template/.orchestra/scripts/context.mjs +373 -0
  18. package/template/.orchestra/stacks/README.md +81 -0
  19. package/template/.orchestra/stacks/_lib/common.sh +3095 -0
  20. package/template/.orchestra/stacks/_repo/setup.sh +1113 -0
  21. package/template/.orchestra/stacks/backend-hono/claude/commands/endpoint.md +13 -0
  22. package/template/.orchestra/stacks/backend-hono/claude/skills/api-endpoint/SKILL.md +111 -0
  23. package/template/.orchestra/stacks/backend-hono/claude/skills/testing/SKILL.md +75 -0
  24. package/template/.orchestra/stacks/backend-hono/setup.sh +5945 -0
  25. package/template/.orchestra/stacks/backend-hono/stack.md +195 -0
  26. package/template/.orchestra/stacks/mobile-android-native/claude/commands/component.md +13 -0
  27. package/template/.orchestra/stacks/mobile-android-native/claude/skills/design-component/SKILL.md +58 -0
  28. package/template/.orchestra/stacks/mobile-android-native/setup.sh +607 -0
  29. package/template/.orchestra/stacks/mobile-android-native/stack.md +138 -0
  30. package/template/.orchestra/stacks/mobile-expo/claude/commands/component.md +12 -0
  31. package/template/.orchestra/stacks/mobile-expo/claude/skills/design-component/SKILL.md +92 -0
  32. package/template/.orchestra/stacks/mobile-expo/setup.sh +470 -0
  33. package/template/.orchestra/stacks/mobile-expo/stack.md +141 -0
  34. package/template/.orchestra/stacks/mobile-ios-native/claude/commands/component.md +12 -0
  35. package/template/.orchestra/stacks/mobile-ios-native/claude/skills/design-component/SKILL.md +58 -0
  36. package/template/.orchestra/stacks/mobile-ios-native/setup.sh +468 -0
  37. package/template/.orchestra/stacks/mobile-ios-native/stack.md +130 -0
  38. package/template/.orchestra/stacks/web-nextjs/claude/commands/component.md +12 -0
  39. package/template/.orchestra/stacks/web-nextjs/claude/skills/design-component/SKILL.md +100 -0
  40. package/template/.orchestra/stacks/web-nextjs/claude/skills/design-tokens/SKILL.md +61 -0
  41. package/template/.orchestra/stacks/web-nextjs/setup.sh +128 -0
  42. package/template/.orchestra/stacks/web-nextjs/stack.md +170 -0
  43. package/template/.orchestra/stacks/web-vite-react/claude/commands/component.md +12 -0
  44. package/template/.orchestra/stacks/web-vite-react/claude/skills/design-component/SKILL.md +100 -0
  45. package/template/.orchestra/stacks/web-vite-react/claude/skills/design-tokens/SKILL.md +61 -0
  46. package/template/.orchestra/stacks/web-vite-react/setup.sh +185 -0
  47. package/template/.orchestra/stacks/web-vite-react/stack.md +177 -0
  48. package/template/.orchestra/trackers/github.sh +467 -0
  49. package/template/CLAUDE.md +54 -0
  50. package/template/agents/architect.md +83 -0
  51. package/template/agents/engineer-high.md +17 -0
  52. package/template/agents/engineer-low.md +17 -0
  53. package/template/agents/engineer-max.md +17 -0
  54. package/template/agents/engineer-medium.md +17 -0
  55. package/template/agents/engineer-xhigh.md +17 -0
  56. package/template/agents/lead.md +925 -0
  57. package/template/agents/milestone-reviewer.md +65 -0
  58. package/template/agents/orchestrator.md +105 -0
  59. package/template/agents/product-manager.md +439 -0
  60. package/template/agents/re-reviewer.md +25 -0
  61. package/template/agents/reviewer.md +303 -0
  62. package/template/agents/scaffolder.md +206 -0
  63. package/template/agents/tracker.md +202 -0
  64. package/template/commands/blueprint.md +23 -0
  65. package/template/commands/create-role.md +64 -0
  66. package/template/commands/export.md +45 -0
  67. package/template/commands/help.md +56 -0
  68. package/template/commands/hotfix.md +14 -0
  69. package/template/commands/orchestrator.md +12 -0
  70. package/template/commands/pm.md +18 -0
  71. package/template/commands/rewind.md +63 -0
  72. package/template/commands/setup.md +15 -0
  73. package/template/commands/start.md +29 -0
  74. package/template/commands/status.md +11 -0
  75. package/template/commands/sync.md +76 -0
  76. package/template/commands/verifier.md +53 -0
  77. package/template/hooks/audit-context.sh +166 -0
  78. package/template/hooks/enforce-role.sh +193 -0
  79. package/template/hooks/protect-system-files.sh +123 -0
  80. package/template/hooks/review-rounds.sh +172 -0
  81. package/template/rules/acceptance-check.orchestra.md +11 -0
  82. package/template/rules/analytics-kpi.orchestra.md +56 -0
  83. package/template/rules/code-standards.orchestra.md +35 -0
  84. package/template/rules/comments.orchestra.md +77 -0
  85. package/template/rules/commit-format.orchestra.md +20 -0
  86. package/template/rules/dependency-upgrade.orchestra.md +112 -0
  87. package/template/rules/design-parity.orchestra.md +181 -0
  88. package/template/rules/observability.orchestra.md +121 -0
  89. package/template/rules/performance.orchestra.md +190 -0
  90. package/template/rules/phase-limits.orchestra.md +3 -0
  91. package/template/rules/pull-request.orchestra.md +218 -0
  92. package/template/rules/role-boundaries.orchestra.md +132 -0
  93. package/template/rules/security.orchestra.md +106 -0
  94. package/template/rules/stuck-detection.orchestra.md +25 -0
  95. package/template/rules/testing-standards.orchestra.md +219 -0
  96. package/template/rules/tracker-authority.orchestra.md +198 -0
  97. package/template/rules/verification-gate.orchestra.md +56 -0
  98. package/template/skills/accessibility/SKILL.md +31 -0
  99. package/template/skills/android-testing/SKILL.md +112 -0
  100. package/template/skills/architecture/SKILL.md +175 -0
  101. package/template/skills/auth-setup/SKILL.md +105 -0
  102. package/template/skills/best-practices/SKILL.md +29 -0
  103. package/template/skills/code-optimizer/SKILL.md +27 -0
  104. package/template/skills/core-web-vitals/SKILL.md +32 -0
  105. package/template/skills/crud-api/SKILL.md +32 -0
  106. package/template/skills/debug/SKILL.md +28 -0
  107. package/template/skills/deployment/SKILL.md +104 -0
  108. package/template/skills/error-handling/SKILL.md +154 -0
  109. package/template/skills/frontend-design/SKILL.md +30 -0
  110. package/template/skills/fullstack-infrastructure/SKILL.md +108 -0
  111. package/template/skills/ios-testing/SKILL.md +100 -0
  112. package/template/skills/mobile-testing/SKILL.md +102 -0
  113. package/template/skills/observability/SKILL.md +91 -0
  114. package/template/skills/project-structure/SKILL.md +114 -0
  115. package/template/skills/push-notifications/SKILL.md +146 -0
  116. package/template/skills/react-best-practices/SKILL.md +31 -0
  117. package/template/skills/realtime/SKILL.md +146 -0
  118. package/template/skills/review/SKILL.md +82 -0
  119. package/template/skills/testing/SKILL.md +45 -0
  120. package/template/skills/web-quality-audit/SKILL.md +47 -0
  121. package/template/skills/web-testing/SKILL.md +120 -0
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: design-component
3
+ description: "Author a component in the design system — folder layout, API shape, tests, fixture, a11y. Use when adding or changing anything under src/design/."
4
+ ---
5
+
6
+ # Authoring a design system component
7
+
8
+ ## Folder
9
+
10
+ ```
11
+ src/design/{Name}/
12
+ ├── {Name}.tsx # the component, nothing else
13
+ ├── {Name}.test.tsx # interaction tests + one snapshot
14
+ ├── {Name}.fixture.tsx # react-cosmos fixture
15
+ ├── types.ts # props and any type this component owns
16
+ └── index.ts # public exports
17
+ ```
18
+
19
+ Then add the component to `src/design/index.ts`.
20
+
21
+ ## Before writing
22
+
23
+ 1. Does a token exist for every value you are about to use? If not, add the
24
+ token first. Never hardcode a color, radius, spacing, or duration.
25
+ 2. Is there a native element with the right semantics? Use it. A `<div>`
26
+ with a click handler is a defect.
27
+ 3. Does this component need to know anything about the product? If yes, it
28
+ does not belong in `src/design/`.
29
+
30
+ ## API
31
+
32
+ - accept `ref` as a prop — React 19 forwards it, `forwardRef` is legacy
33
+ - merge `className` in, do not overwrite the caller's
34
+ - spread remaining props onto the root
35
+ - variants and sizes are unions (`"primary" | "danger"`), never independent
36
+ booleans that can contradict each other
37
+ - no default that hides a required decision
38
+
39
+ ## Tests
40
+
41
+ Interaction first, snapshot second.
42
+
43
+ Browser mode, verified working shape:
44
+
45
+ ```tsx
46
+ import { expect, test, vi } from "vitest";
47
+ import { page } from "vitest/browser";
48
+ import { render } from "vitest-browser-react";
49
+ import { Button } from "./Button";
50
+
51
+ test("fires when clicked", async () => {
52
+ const onClick = vi.fn();
53
+ await render(<Button onClick={onClick}>Save</Button>);
54
+ await page.getByRole("button", { name: "Save" }).click();
55
+ expect(onClick).toHaveBeenCalledTimes(1);
56
+ });
57
+
58
+ test("does not fire when disabled", async () => {
59
+ const onClick = vi.fn();
60
+ await render(
61
+ <Button disabled onClick={onClick}>
62
+ Save
63
+ </Button>,
64
+ );
65
+ await expect.element(page.getByRole("button", { name: "Save" })).toBeDisabled();
66
+ expect(onClick).not.toHaveBeenCalled();
67
+ });
68
+ ```
69
+
70
+ `render` is async — await it. Query through `page`. Import from
71
+ `vitest/browser`; `@vitest/browser/context` is deprecated.
72
+
73
+ Always query by role **and** accessible name. Role alone passes on a button
74
+ with no name, which is the defect a screen-reader user actually hits.
75
+
76
+ Also cover: focus returns to the trigger when an overlay closes, and one
77
+ snapshot per component for markup drift.
78
+
79
+ `src/components/Button` and `src/components/Select` are the two worked
80
+ examples — the native side of the line and the primitive side. Read them
81
+ before writing a third.
82
+
83
+ If the component wraps a third-party library, test *our* wrapper. A
84
+ Dependabot bump that changes the library's behavior has to turn this suite
85
+ red — that is the entire reason the suite exists.
86
+
87
+ ## Accessibility checklist
88
+
89
+ - [ ] correct role, native element preferred
90
+ - [ ] accessible name reachable by screen reader
91
+ - [ ] full keyboard operation
92
+ - [ ] Escape closes, focus returns to trigger (overlays)
93
+ - [ ] focus ring visible in both themes
94
+ - [ ] contrast holds in both themes
95
+
96
+ ## Done means
97
+
98
+ Component, types, tests, fixture, barrel export, and a11y checklist — all
99
+ of it. A component without a fixture is invisible to design comparison, and
100
+ a component without interaction tests is untested.
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: design-tokens
3
+ description: "Extract tokens from the design source into Tailwind v4 @theme and the semantic theme layer. Use before writing any component, or when the design changes."
4
+ ---
5
+
6
+ # Design tokens
7
+
8
+ Tokens come first. A component written before the token system is a
9
+ component that hardcodes values.
10
+
11
+ ## Extract every axis
12
+
13
+ Color is the one people remember and the least sufficient. Pull all of it:
14
+
15
+ | Axis | Examples |
16
+ |---|---|
17
+ | color | palette ramps, then semantic roles |
18
+ | spacing | the scale, not ad-hoc values |
19
+ | typography | family, size ramp, weight, line height |
20
+ | radii | control vs surface |
21
+ | shadow | elevation levels |
22
+ | breakpoints | the real ones from the design, not Tailwind defaults |
23
+ | z-index | named layers — overlay, modal, toast |
24
+ | motion | durations and easings |
25
+
26
+ ## Two layers
27
+
28
+ **Palette** goes in `@theme`. Raw values, no meaning attached.
29
+
30
+ ```css
31
+ @theme {
32
+ --color-brand-500: oklch(0.62 0.19 250);
33
+ }
34
+ ```
35
+
36
+ **Semantic** maps meaning onto palette, and is what components use.
37
+
38
+ ```css
39
+ :root { --surface: var(--color-white); }
40
+ [data-theme="dark"] { --surface: var(--color-brand-900); }
41
+ ```
42
+
43
+ A component references `--surface`. It never references `--color-brand-500`.
44
+ That is what makes a theme swap a one-file change.
45
+
46
+ ## Theming
47
+
48
+ Three states must work: explicit light, explicit dark, and system default.
49
+
50
+ - `[data-theme="dark"]` and `[data-theme="light"]` for an explicit choice
51
+ - `@media (prefers-color-scheme: dark)` guarded with
52
+ `:root:not([data-theme="light"])` for the system default
53
+
54
+ Never define a color only inside a media query — the explicit toggle will
55
+ have nothing to override.
56
+
57
+ ## Tailwind v4
58
+
59
+ There is no `tailwind.config.js`. `@theme` is the configuration, and its
60
+ entries become real CSS custom properties. Do not add a JS config to make
61
+ it feel familiar — you will end up with two sources of truth.
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env bash
2
+ # Next.js App Router. Biome via the native create-next-app flag, Tailwind v4,
3
+ # react-cosmos workbench, Vitest browser mode, Playwright e2e.
4
+ set -euo pipefail
5
+
6
+ DIR="${1:?usage: setup.sh <target-dir>}"
7
+ LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../_lib" && pwd)"
8
+ # shellcheck source=../_lib/common.sh
9
+ source "$LIB/common.sh"
10
+
11
+ # --biome is native now, so we never install eslint just to remove it.
12
+ # --disable-git because we are already inside a repo.
13
+ # create-next-app does not create intermediate directories, and when the
14
+ # parent is missing it reports "The application path is not writable, please
15
+ # check folder permissions" — which sends you after the wrong thing entirely.
16
+ mkdir -p "$(dirname "$DIR")"
17
+
18
+ log "scaffolding next.js into $DIR"
19
+ npx --yes create-next-app@latest "$DIR" \
20
+ --ts --app --src-dir --tailwind --biome --use-npm --skip-install --disable-git --yes </dev/null
21
+
22
+ assert_scaffolded "$DIR"
23
+ cd "$DIR"
24
+
25
+ setup_yarn
26
+ yarn_install
27
+
28
+ # create-next-app writes its own CLAUDE.md, which would collide with the
29
+ # stack's convention pack. AGENTS.md is kept — it does not clash.
30
+ rm -f CLAUDE.md
31
+
32
+ setup_typescript
33
+ setup_node_pin
34
+ setup_typecheck_script
35
+
36
+ add_latest @sentry/nextjs posthog-js
37
+ add_latest --dev react-cosmos
38
+ setup_vitest_browser
39
+ # The API seam, faked at the network layer. Mocking our own fetch wrapper
40
+ # instead would leave the wrapper — the thing that breaks when the API
41
+ # changes — untested.
42
+ add_latest --dev msw
43
+ setup_axe
44
+ setup_playwright_e2e "yarn build && yarn start --port 3000" "http://localhost:3000"
45
+ setup_axe_e2e
46
+ setup_bundle_budget
47
+
48
+ pkg_set scripts.cosmos="cosmos" scripts.lint="biome check ." scripts.format="biome format --write ."
49
+
50
+ # Next generates LayoutProps/PageProps into .next/types. On a clean checkout —
51
+ # which is what CI is — they do not exist yet and tsc fails on them, so
52
+ # generate before typechecking.
53
+ pkg_set scripts.typecheck="next typegen && tsc -b --noEmit"
54
+ # --json, or npm writes the string "false" and bundlers treat it as a glob.
55
+ pkg_set sideEffects=false
56
+
57
+ log "writing design system skeleton"
58
+ mkdir -p src/design/helpers
59
+ cat > src/design/tokens.css <<'CSS'
60
+ @import "tailwindcss";
61
+
62
+ /* Populate from the Claude Design source before writing any component.
63
+ Color is one axis of several — do not stop there. */
64
+ @theme {
65
+ --color-brand-50: oklch(0.97 0.02 250);
66
+ --color-brand-500: oklch(0.62 0.19 250);
67
+ --color-brand-900: oklch(0.35 0.12 250);
68
+ --radius-control: 0.5rem;
69
+ --font-sans: system-ui, sans-serif;
70
+ --ease-standard: cubic-bezier(0.2, 0, 0, 1);
71
+ --duration-fast: 120ms;
72
+ }
73
+
74
+ :root { --surface: var(--color-white); --on-surface: var(--color-brand-900); }
75
+ [data-theme="dark"] { --surface: var(--color-brand-900); --on-surface: var(--color-brand-50); }
76
+ @media (prefers-color-scheme: dark) {
77
+ :root:not([data-theme="light"]) {
78
+ --surface: var(--color-brand-900); --on-surface: var(--color-brand-50);
79
+ }
80
+ }
81
+ CSS
82
+
83
+ printf 'export type ThemeName = "light" | "dark";\nexport type Size = "sm" | "md" | "lg";\n' > src/design/types.ts
84
+ printf 'export * from "./types";\n' > src/design/index.ts
85
+
86
+ # create-next-app writes its own globals.css and imports it from the root
87
+ # layout. Without this the tokens file exists and is never loaded.
88
+ GLOBALS="$(find src/app -maxdepth 1 -name 'globals.css' | head -1)"
89
+ if [ -n "$GLOBALS" ]; then
90
+ # tokens.css already does `@import "tailwindcss"` — drop the duplicate.
91
+ node -e '
92
+ const fs = require("fs");
93
+ const f = process.argv[1];
94
+ let s = fs.readFileSync(f, "utf8");
95
+ s = s.replace(/^@import\s+["\x27]tailwindcss["\x27];\s*$/m, "");
96
+ if (!s.includes("design/tokens.css")) {
97
+ s = `@import "../design/tokens.css";\n` + s;
98
+ }
99
+ fs.writeFileSync(f, s.trimStart() + "\n");
100
+ ' "$GLOBALS"
101
+ log "design tokens imported from $GLOBALS"
102
+ else
103
+ warn "globals.css not found — import src/design/tokens.css manually or tokens will not load"
104
+ fi
105
+
106
+ # create-next-app --biome installs biome at whatever version its template
107
+ # pinned and writes a bare biome.json. Running the shared setup brings the
108
+ # version to current and applies the config the rest of the stack relies on
109
+ # — tailwindDirectives, and excludes so build output is not linted.
110
+ setup_biome
111
+
112
+ install_pre_commit_gate
113
+ setup_integrations_dir
114
+ setup_ui_primitives client
115
+
116
+ setup_coverage
117
+ setup_mutation
118
+
119
+ # create-next-app ships "@types/node": "^20" and friends. Nothing above
120
+ # touches them, so without this the repo is born several majors behind.
121
+ refresh_scaffold_deps
122
+ # Everything above was generated after setup_biome formatted the scaffold.
123
+ # Without this the first commit is rejected by the gate we just installed.
124
+ yarn biome check --write . >/dev/null 2>&1 || true
125
+
126
+ assert_verification_scripts
127
+ assert_scaffold_clean
128
+ log "web-nextjs ready in $DIR"
@@ -0,0 +1,170 @@
1
+ # Web — Next.js (App Router)
2
+
3
+ ## Who you are
4
+
5
+ You are a **senior frontend engineer** who understands where code runs.
6
+ Server Components by default; `"use client"` only where interactivity
7
+ genuinely requires it, and as far down the tree as possible. A page that
8
+ ships the whole app to the browser because one leaf needed state is a
9
+ defect.
10
+
11
+ SOLID, KISS, YAGNI. No overengineering — when it starts feeling clever,
12
+ simplify.
13
+
14
+ ## Structure
15
+
16
+ Everything under `src/`.
17
+
18
+ ```
19
+ src/
20
+ ├── app/ # routes, layouts, server components
21
+ ├── design/ # the UI kit. no business logic. ever.
22
+ │ ├── tokens.css
23
+ │ ├── types.ts
24
+ │ ├── helpers/
25
+ │ ├── index.ts
26
+ │ └── Button/
27
+ │ ├── Button.tsx
28
+ │ ├── Button.test.tsx
29
+ │ ├── Button.fixture.tsx
30
+ │ ├── types.ts
31
+ │ └── index.ts
32
+ └── ...
33
+ ```
34
+
35
+ Every component gets its own folder: component, tests, fixture, types,
36
+ `index.ts`. Uniform, no exceptions.
37
+
38
+ `src/design/` holds nothing that is not design. A component may contain
39
+ logic about itself; never about your product.
40
+
41
+ ## Server and client
42
+
43
+ - Design system components are presentational and mostly client-safe, but
44
+ do not add `"use client"` reflexively — only when the component actually
45
+ uses state, effects, or browser APIs.
46
+ - Data fetching belongs in Server Components, not in `useEffect`.
47
+ - Never leak a secret into a client component. It ships to the browser.
48
+
49
+ ## Order of work
50
+
51
+ Tokens before components. Always.
52
+
53
+ 1. Extract every token from the design source — color, spacing, typography,
54
+ radii, shadows, breakpoints, z-index, motion.
55
+ 2. Write them into `src/design/tokens.css` under `@theme`.
56
+ 3. Add the semantic layer and the dark variant.
57
+ 4. Then write components.
58
+
59
+ Hardcoded colors or spacing are bugs. Reject them in review.
60
+
61
+ ## Tailwind v4
62
+
63
+ Configuration is CSS, not `tailwind.config.js`. `@theme` entries become
64
+ real CSS custom properties, so tokens and theming are one system. Do not
65
+ add a JS config file.
66
+
67
+ ## Where a component comes from
68
+
69
+ One question decides it:
70
+
71
+ > Does the browser already give me the role, the keyboard behaviour and the
72
+ > focus management?
73
+
74
+ **Yes — write it.** `button`, `input`, `select`, `textarea`, `a`, `table`,
75
+ `details`, `dialog` with `showModal()`, the Popover API. These carry their
76
+ semantics for free, and a library on top of them buys a dependency and
77
+ nothing else. Prefer the platform, and check its support at the time rather
78
+ than trusting a table written here — that is the same rule dependencies
79
+ follow.
80
+
81
+ **No — use an unstyled primitive.** Combobox, listbox with custom content,
82
+ menu with typeahead, tabs, date picker, slider. There is no element for
83
+ these, so hand-writing one means re-implementing the WAI-ARIA authoring
84
+ practice: roving focus, typeahead, dismissal layers, focus return,
85
+ `aria-activedescendant`. That work is not a component, it is a library, and
86
+ this stack runs axe as an assertion — so a hand-rolled one fails the gate
87
+ over and over instead of once, at the library choice.
88
+
89
+ `react-aria-components` is the default. It ships behaviour and **no styling**,
90
+ it is tree-shaken per component, and it covers the patterns that have no
91
+ element — including the date picker, which nothing should hand-roll. Radix is
92
+ an acceptable substitute; a styled kit like shadcn or MUI is not, because it
93
+ brings someone else's opinion about your product.
94
+
95
+ Tailwind stays. It is utility CSS, not components — it styles the elements we
96
+ write.
97
+
98
+ ### The line is not a style preference
99
+
100
+ Take the worked `Select` and swap the primitive's `Popover` for a plain
101
+ `div`, which is what a hand-rolled version is. Opening still works, selecting
102
+ still works, and **axe still passes**. The only thing that breaks is Escape
103
+ returning focus to the trigger — a keyboard user is silently dropped at the
104
+ top of the document.
105
+
106
+ That is the whole argument. The failure is invisible to the linter, invisible
107
+ to a snapshot, and invisible in review. It is only visible to an interaction
108
+ test, which is why `src/components/Select/Select.test.tsx` has one.
109
+
110
+ A primitive is **not** an `src/integrations/` seam — it is neither an SDK call
111
+ nor an HTTP API, so it lives with the component that uses it. Its coverage
112
+ comes from the interaction test driving the real thing, which is a stronger
113
+ check than a line-count gate.
114
+ ## Memory — the collector cannot end what you still hold
115
+
116
+ React does not free a subscription for you, and the collector cannot free
117
+ what a live closure still references. So:
118
+
119
+ - **Every effect that starts something returns the function that stops
120
+ it.** `addEventListener` → `removeEventListener`; `setInterval` →
121
+ `clearInterval`; `subscribe` → `unsubscribe`; a fetch → an
122
+ `AbortController` aborted in the cleanup. The gate checks that the cleanup
123
+ exists; only you can check that it stops the right thing.
124
+ - State that only grows is a leak with a UI. A list that appends on every
125
+ event and never trims, a map of every id ever seen — bound it, or key it
126
+ by what is on screen.
127
+ - A closure created in render captures the render's state and is recreated
128
+ every render. That is by design; it becomes a cost when it is passed to a
129
+ memoized child or a long list. `.claude/rules/performance.orchestra.md`
130
+ says where that matters.
131
+ - A module-level cache is process-lifetime — and on the server it is shared
132
+ by every request. Give it a maximum and an eviction rule, and never put a
133
+ request's data in it.
134
+
135
+ ## Testing
136
+
137
+ Coverage target is effectively total. The deciding rule:
138
+
139
+ > If one of our functions calls into a third-party library, that function
140
+ > has a test. When Dependabot bumps the library and behavior changes, the
141
+ > test must fail.
142
+
143
+ - **Interaction tests** (Vitest browser mode, Playwright driver) — primary.
144
+ - **Snapshots** — secondary, markup drift only. Snapshot-only is untested.
145
+ - **E2E** (`@playwright/test`) — flows across routes.
146
+
147
+ Server Components need tests too: assert what they render given data, and
148
+ that they do not reach for browser APIs.
149
+
150
+ ## Accessibility
151
+
152
+ Structural. Every component: correct role and native element first,
153
+ reachable accessible name, full keyboard operation, Escape and focus return
154
+ for overlays, visible focus in both themes, contrast holding in both.
155
+
156
+ ## Component API
157
+
158
+ Forward `ref`, merge `className`, spread the rest. Variants and sizes are
159
+ unions, not booleans that can contradict. Props types live in the
160
+ component's folder and export from its `index.ts`.
161
+
162
+ ## Workbench
163
+
164
+ `react-cosmos` (`npm run cosmos`). Every component has a fixture — it is
165
+ the catalog and the stable render target for design comparison.
166
+
167
+ ## Tooling
168
+
169
+ Biome for lint and format, installed natively by `create-next-app --biome`.
170
+ No ESLint, no Prettier. TypeScript strict with `noUncheckedIndexedAccess`.
@@ -0,0 +1,12 @@
1
+ Scaffold a new design system component.
2
+
3
+ Usage: `/web:component {Name}`
4
+
5
+ 1. Load the `design-component` skill and follow it exactly.
6
+ 2. Confirm every value the component needs has a token. If one is missing,
7
+ add it to `src/design/tokens.css` first — do not hardcode.
8
+ 3. Create `src/design/{Name}/` with the component, types, tests, fixture,
9
+ and `index.ts`.
10
+ 4. Export it from `src/design/index.ts`.
11
+ 5. Run `npm run typecheck && npm test && npm run lint`.
12
+ 6. Report the a11y checklist with each item explicitly checked or flagged.
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: design-component
3
+ description: "Author a component in the design system — folder layout, API shape, tests, fixture, a11y. Use when adding or changing anything under src/design/."
4
+ ---
5
+
6
+ # Authoring a design system component
7
+
8
+ ## Folder
9
+
10
+ ```
11
+ src/design/{Name}/
12
+ ├── {Name}.tsx # the component, nothing else
13
+ ├── {Name}.test.tsx # interaction tests + one snapshot
14
+ ├── {Name}.fixture.tsx # react-cosmos fixture
15
+ ├── types.ts # props and any type this component owns
16
+ └── index.ts # public exports
17
+ ```
18
+
19
+ Then add the component to `src/design/index.ts`.
20
+
21
+ ## Before writing
22
+
23
+ 1. Does a token exist for every value you are about to use? If not, add the
24
+ token first. Never hardcode a color, radius, spacing, or duration.
25
+ 2. Is there a native element with the right semantics? Use it. A `<div>`
26
+ with a click handler is a defect.
27
+ 3. Does this component need to know anything about the product? If yes, it
28
+ does not belong in `src/design/`.
29
+
30
+ ## API
31
+
32
+ - accept `ref` as a prop — React 19 forwards it, `forwardRef` is legacy
33
+ - merge `className` in, do not overwrite the caller's
34
+ - spread remaining props onto the root
35
+ - variants and sizes are unions (`"primary" | "danger"`), never independent
36
+ booleans that can contradict each other
37
+ - no default that hides a required decision
38
+
39
+ ## Tests
40
+
41
+ Interaction first, snapshot second.
42
+
43
+ Browser mode, verified working shape:
44
+
45
+ ```tsx
46
+ import { expect, test, vi } from "vitest";
47
+ import { page } from "vitest/browser";
48
+ import { render } from "vitest-browser-react";
49
+ import { Button } from "./Button";
50
+
51
+ test("fires when clicked", async () => {
52
+ const onClick = vi.fn();
53
+ await render(<Button onClick={onClick}>Save</Button>);
54
+ await page.getByRole("button", { name: "Save" }).click();
55
+ expect(onClick).toHaveBeenCalledTimes(1);
56
+ });
57
+
58
+ test("does not fire when disabled", async () => {
59
+ const onClick = vi.fn();
60
+ await render(
61
+ <Button disabled onClick={onClick}>
62
+ Save
63
+ </Button>,
64
+ );
65
+ await expect.element(page.getByRole("button", { name: "Save" })).toBeDisabled();
66
+ expect(onClick).not.toHaveBeenCalled();
67
+ });
68
+ ```
69
+
70
+ `render` is async — await it. Query through `page`. Import from
71
+ `vitest/browser`; `@vitest/browser/context` is deprecated.
72
+
73
+ Always query by role **and** accessible name. Role alone passes on a button
74
+ with no name, which is the defect a screen-reader user actually hits.
75
+
76
+ Also cover: focus returns to the trigger when an overlay closes, and one
77
+ snapshot per component for markup drift.
78
+
79
+ `src/components/Button` and `src/components/Select` are the two worked
80
+ examples — the native side of the line and the primitive side. Read them
81
+ before writing a third.
82
+
83
+ If the component wraps a third-party library, test *our* wrapper. A
84
+ Dependabot bump that changes the library's behavior has to turn this suite
85
+ red — that is the entire reason the suite exists.
86
+
87
+ ## Accessibility checklist
88
+
89
+ - [ ] correct role, native element preferred
90
+ - [ ] accessible name reachable by screen reader
91
+ - [ ] full keyboard operation
92
+ - [ ] Escape closes, focus returns to trigger (overlays)
93
+ - [ ] focus ring visible in both themes
94
+ - [ ] contrast holds in both themes
95
+
96
+ ## Done means
97
+
98
+ Component, types, tests, fixture, barrel export, and a11y checklist — all
99
+ of it. A component without a fixture is invisible to design comparison, and
100
+ a component without interaction tests is untested.
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: design-tokens
3
+ description: "Extract tokens from the design source into Tailwind v4 @theme and the semantic theme layer. Use before writing any component, or when the design changes."
4
+ ---
5
+
6
+ # Design tokens
7
+
8
+ Tokens come first. A component written before the token system is a
9
+ component that hardcodes values.
10
+
11
+ ## Extract every axis
12
+
13
+ Color is the one people remember and the least sufficient. Pull all of it:
14
+
15
+ | Axis | Examples |
16
+ |---|---|
17
+ | color | palette ramps, then semantic roles |
18
+ | spacing | the scale, not ad-hoc values |
19
+ | typography | family, size ramp, weight, line height |
20
+ | radii | control vs surface |
21
+ | shadow | elevation levels |
22
+ | breakpoints | the real ones from the design, not Tailwind defaults |
23
+ | z-index | named layers — overlay, modal, toast |
24
+ | motion | durations and easings |
25
+
26
+ ## Two layers
27
+
28
+ **Palette** goes in `@theme`. Raw values, no meaning attached.
29
+
30
+ ```css
31
+ @theme {
32
+ --color-brand-500: oklch(0.62 0.19 250);
33
+ }
34
+ ```
35
+
36
+ **Semantic** maps meaning onto palette, and is what components use.
37
+
38
+ ```css
39
+ :root { --surface: var(--color-white); }
40
+ [data-theme="dark"] { --surface: var(--color-brand-900); }
41
+ ```
42
+
43
+ A component references `--surface`. It never references `--color-brand-500`.
44
+ That is what makes a theme swap a one-file change.
45
+
46
+ ## Theming
47
+
48
+ Three states must work: explicit light, explicit dark, and system default.
49
+
50
+ - `[data-theme="dark"]` and `[data-theme="light"]` for an explicit choice
51
+ - `@media (prefers-color-scheme: dark)` guarded with
52
+ `:root:not([data-theme="light"])` for the system default
53
+
54
+ Never define a color only inside a media query — the explicit toggle will
55
+ have nothing to override.
56
+
57
+ ## Tailwind v4
58
+
59
+ There is no `tailwind.config.js`. `@theme` is the configuration, and its
60
+ entries become real CSS custom properties. Do not add a JS config to make
61
+ it feel familiar — you will end up with two sources of truth.