agentic-team 0.0.0-stage → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +184 -2
- package/bin/build-template.js +208 -0
- package/bin/index.js +1139 -0
- package/package.json +41 -4
- package/template/.claude-plugin/plugin.json +10 -0
- package/template/.orchestra/README.md +401 -0
- package/template/.orchestra/blueprints/README.md +87 -0
- package/template/.orchestra/blueprints/api-only.md +46 -0
- package/template/.orchestra/blueprints/component-crud-resource.md +25 -0
- package/template/.orchestra/blueprints/saas-starter.md +69 -0
- package/template/.orchestra/config.yml +277 -0
- package/template/.orchestra/roles/orchestrator.md +105 -0
- package/template/.orchestra/roles/product-manager.md +438 -0
- package/template/.orchestra/scripts/compact-diff.awk +45 -0
- package/template/.orchestra/stacks/README.md +81 -0
- package/template/.orchestra/stacks/_lib/common.sh +3095 -0
- package/template/.orchestra/stacks/_repo/setup.sh +1109 -0
- package/template/.orchestra/stacks/backend-hono/claude/commands/endpoint.md +13 -0
- package/template/.orchestra/stacks/backend-hono/claude/skills/api-endpoint/SKILL.md +111 -0
- package/template/.orchestra/stacks/backend-hono/claude/skills/testing/SKILL.md +75 -0
- package/template/.orchestra/stacks/backend-hono/setup.sh +5945 -0
- package/template/.orchestra/stacks/backend-hono/stack.md +195 -0
- package/template/.orchestra/stacks/mobile-android-native/claude/commands/component.md +13 -0
- package/template/.orchestra/stacks/mobile-android-native/claude/skills/design-component/SKILL.md +58 -0
- package/template/.orchestra/stacks/mobile-android-native/setup.sh +607 -0
- package/template/.orchestra/stacks/mobile-android-native/stack.md +138 -0
- package/template/.orchestra/stacks/mobile-expo/claude/commands/component.md +12 -0
- package/template/.orchestra/stacks/mobile-expo/claude/skills/design-component/SKILL.md +92 -0
- package/template/.orchestra/stacks/mobile-expo/setup.sh +470 -0
- package/template/.orchestra/stacks/mobile-expo/stack.md +141 -0
- package/template/.orchestra/stacks/mobile-ios-native/claude/commands/component.md +12 -0
- package/template/.orchestra/stacks/mobile-ios-native/claude/skills/design-component/SKILL.md +58 -0
- package/template/.orchestra/stacks/mobile-ios-native/setup.sh +468 -0
- package/template/.orchestra/stacks/mobile-ios-native/stack.md +130 -0
- package/template/.orchestra/stacks/web-nextjs/claude/commands/component.md +12 -0
- package/template/.orchestra/stacks/web-nextjs/claude/skills/design-component/SKILL.md +100 -0
- package/template/.orchestra/stacks/web-nextjs/claude/skills/design-tokens/SKILL.md +61 -0
- package/template/.orchestra/stacks/web-nextjs/setup.sh +128 -0
- package/template/.orchestra/stacks/web-nextjs/stack.md +170 -0
- package/template/.orchestra/stacks/web-vite-react/claude/commands/component.md +12 -0
- package/template/.orchestra/stacks/web-vite-react/claude/skills/design-component/SKILL.md +100 -0
- package/template/.orchestra/stacks/web-vite-react/claude/skills/design-tokens/SKILL.md +61 -0
- package/template/.orchestra/stacks/web-vite-react/setup.sh +185 -0
- package/template/.orchestra/stacks/web-vite-react/stack.md +177 -0
- package/template/.orchestra/trackers/github.sh +467 -0
- package/template/CLAUDE.md +54 -0
- package/template/agents/architect.md +83 -0
- package/template/agents/engineer-high.md +17 -0
- package/template/agents/engineer-low.md +17 -0
- package/template/agents/engineer-max.md +17 -0
- package/template/agents/engineer-medium.md +17 -0
- package/template/agents/engineer-xhigh.md +17 -0
- package/template/agents/lead.md +870 -0
- package/template/agents/milestone-reviewer.md +63 -0
- package/template/agents/orchestrator.md +105 -0
- package/template/agents/product-manager.md +438 -0
- package/template/agents/reviewer.md +301 -0
- package/template/agents/scaffolder.md +180 -0
- package/template/agents/tracker.md +202 -0
- package/template/commands/blueprint.md +23 -0
- package/template/commands/create-role.md +64 -0
- package/template/commands/export.md +45 -0
- package/template/commands/help.md +55 -0
- package/template/commands/hotfix.md +12 -0
- package/template/commands/orchestrator.md +12 -0
- package/template/commands/pm.md +18 -0
- package/template/commands/rewind.md +61 -0
- package/template/commands/setup.md +14 -0
- package/template/commands/start.md +29 -0
- package/template/commands/status.md +11 -0
- package/template/commands/sync.md +73 -0
- package/template/commands/verifier.md +53 -0
- package/template/hooks/audit-context.sh +153 -0
- package/template/hooks/enforce-role.sh +175 -0
- package/template/hooks/protect-system-files.sh +123 -0
- package/template/hooks/review-rounds.sh +172 -0
- package/template/rules/acceptance-check.orchestra.md +11 -0
- package/template/rules/analytics-kpi.orchestra.md +56 -0
- package/template/rules/code-standards.orchestra.md +34 -0
- package/template/rules/comments.orchestra.md +77 -0
- package/template/rules/commit-format.orchestra.md +20 -0
- package/template/rules/dependency-upgrade.orchestra.md +112 -0
- package/template/rules/design-parity.orchestra.md +170 -0
- package/template/rules/observability.orchestra.md +118 -0
- package/template/rules/performance.orchestra.md +186 -0
- package/template/rules/phase-limits.orchestra.md +3 -0
- package/template/rules/pull-request.orchestra.md +213 -0
- package/template/rules/role-boundaries.orchestra.md +117 -0
- package/template/rules/security.orchestra.md +106 -0
- package/template/rules/stuck-detection.orchestra.md +25 -0
- package/template/rules/testing-standards.orchestra.md +214 -0
- package/template/rules/tracker-authority.orchestra.md +190 -0
- package/template/rules/verification-gate.orchestra.md +56 -0
- package/template/skills/accessibility/SKILL.md +31 -0
- package/template/skills/android-testing/SKILL.md +112 -0
- package/template/skills/architecture/SKILL.md +175 -0
- package/template/skills/auth-setup/SKILL.md +105 -0
- package/template/skills/best-practices/SKILL.md +29 -0
- package/template/skills/code-optimizer/SKILL.md +27 -0
- package/template/skills/core-web-vitals/SKILL.md +32 -0
- package/template/skills/crud-api/SKILL.md +32 -0
- package/template/skills/debug/SKILL.md +28 -0
- package/template/skills/deployment/SKILL.md +104 -0
- package/template/skills/error-handling/SKILL.md +154 -0
- package/template/skills/frontend-design/SKILL.md +30 -0
- package/template/skills/fullstack-infrastructure/SKILL.md +108 -0
- package/template/skills/ios-testing/SKILL.md +100 -0
- package/template/skills/mobile-testing/SKILL.md +102 -0
- package/template/skills/observability/SKILL.md +91 -0
- package/template/skills/project-structure/SKILL.md +114 -0
- package/template/skills/push-notifications/SKILL.md +146 -0
- package/template/skills/react-best-practices/SKILL.md +31 -0
- package/template/skills/realtime/SKILL.md +146 -0
- package/template/skills/review/SKILL.md +82 -0
- package/template/skills/testing/SKILL.md +44 -0
- package/template/skills/web-quality-audit/SKILL.md +47 -0
- package/template/skills/web-testing/SKILL.md +120 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# React SPA on Vite. Tailwind v4 (CSS-first config), react-cosmos workbench,
|
|
3
|
+
# Vitest browser mode driven by Playwright, Playwright for 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
|
+
log "scaffolding vite + react-ts into $DIR"
|
|
12
|
+
npm create vite@latest "$DIR" -- --template react-ts </dev/null
|
|
13
|
+
assert_scaffolded "$DIR"
|
|
14
|
+
cd "$DIR"
|
|
15
|
+
|
|
16
|
+
setup_yarn
|
|
17
|
+
yarn_install
|
|
18
|
+
|
|
19
|
+
setup_typescript
|
|
20
|
+
setup_biome
|
|
21
|
+
setup_node_pin
|
|
22
|
+
setup_typecheck_script
|
|
23
|
+
|
|
24
|
+
add_latest tailwindcss @tailwindcss/vite
|
|
25
|
+
add_latest @sentry/react posthog-js
|
|
26
|
+
add_latest --dev react-cosmos
|
|
27
|
+
setup_vitest_browser
|
|
28
|
+
# The API seam, faked at the network layer. Mocking our own fetch wrapper
|
|
29
|
+
# instead would leave the wrapper — the thing that breaks when the API
|
|
30
|
+
# changes — untested.
|
|
31
|
+
add_latest --dev msw
|
|
32
|
+
setup_axe
|
|
33
|
+
setup_playwright_e2e "yarn build && yarn preview --port 4173" "http://localhost:4173"
|
|
34
|
+
setup_axe_e2e
|
|
35
|
+
setup_bundle_budget
|
|
36
|
+
|
|
37
|
+
pkg_set scripts.cosmos="cosmos"
|
|
38
|
+
|
|
39
|
+
log "writing design system skeleton"
|
|
40
|
+
mkdir -p src/design/helpers
|
|
41
|
+
|
|
42
|
+
# Tailwind v4 configures in CSS. @theme entries become real CSS custom
|
|
43
|
+
# properties, which is also the theming mechanism — one system, not two.
|
|
44
|
+
cat > src/design/tokens.css <<'CSS'
|
|
45
|
+
@import "tailwindcss";
|
|
46
|
+
|
|
47
|
+
/* Design tokens. Populated from the Claude Design source before any
|
|
48
|
+
component is written. Colors are only one axis — do not stop there. */
|
|
49
|
+
@theme {
|
|
50
|
+
/* color */
|
|
51
|
+
--color-brand-50: oklch(0.97 0.02 250);
|
|
52
|
+
--color-brand-500: oklch(0.62 0.19 250);
|
|
53
|
+
--color-brand-900: oklch(0.35 0.12 250);
|
|
54
|
+
|
|
55
|
+
/* spacing scale */
|
|
56
|
+
--spacing-gutter: 1rem;
|
|
57
|
+
|
|
58
|
+
/* radii */
|
|
59
|
+
--radius-control: 0.5rem;
|
|
60
|
+
--radius-surface: 0.75rem;
|
|
61
|
+
|
|
62
|
+
/* typography */
|
|
63
|
+
--font-sans: system-ui, sans-serif;
|
|
64
|
+
--text-caption: 0.75rem;
|
|
65
|
+
|
|
66
|
+
/* motion */
|
|
67
|
+
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
|
68
|
+
--duration-fast: 120ms;
|
|
69
|
+
|
|
70
|
+
/* elevation */
|
|
71
|
+
--shadow-surface: 0 1px 2px rgb(0 0 0 / 0.08);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Semantic layer. Components reference these, never raw palette values,
|
|
75
|
+
so a theme swap never requires touching a component. */
|
|
76
|
+
:root {
|
|
77
|
+
--surface: var(--color-white);
|
|
78
|
+
--on-surface: var(--color-brand-900);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
[data-theme="dark"] {
|
|
82
|
+
--surface: var(--color-brand-900);
|
|
83
|
+
--on-surface: var(--color-brand-50);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@media (prefers-color-scheme: dark) {
|
|
87
|
+
:root:not([data-theme="light"]) {
|
|
88
|
+
--surface: var(--color-brand-900);
|
|
89
|
+
--on-surface: var(--color-brand-50);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
CSS
|
|
93
|
+
|
|
94
|
+
cat > src/design/types.ts <<'TS'
|
|
95
|
+
// Shared design-system types only. Nothing app-specific, no business logic.
|
|
96
|
+
|
|
97
|
+
export type ThemeName = "light" | "dark";
|
|
98
|
+
|
|
99
|
+
export type Size = "sm" | "md" | "lg";
|
|
100
|
+
TS
|
|
101
|
+
|
|
102
|
+
cat > src/design/index.ts <<'TS'
|
|
103
|
+
// Barrel for the design system. Every component re-exports from here.
|
|
104
|
+
// Keep the build ESM and `sideEffects: false` so this stays tree-shakeable.
|
|
105
|
+
|
|
106
|
+
export * from "./types";
|
|
107
|
+
TS
|
|
108
|
+
|
|
109
|
+
# --json, or npm writes the string "false" and bundlers treat it as a glob.
|
|
110
|
+
pkg_set sideEffects=false
|
|
111
|
+
|
|
112
|
+
if [ -f src/index.css ]; then
|
|
113
|
+
printf '@import "./design/tokens.css";\n' | cat - src/index.css > src/index.css.tmp
|
|
114
|
+
mv src/index.css.tmp src/index.css
|
|
115
|
+
else
|
|
116
|
+
printf '@import "./design/tokens.css";\n' > src/index.css
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
node -e '
|
|
120
|
+
const fs = require("fs");
|
|
121
|
+
const f = fs.existsSync("vite.config.ts") ? "vite.config.ts" : "vite.config.js";
|
|
122
|
+
let s = fs.readFileSync(f, "utf8");
|
|
123
|
+
if (!s.includes("@tailwindcss/vite")) {
|
|
124
|
+
s = `import tailwindcss from "@tailwindcss/vite";\n` + s;
|
|
125
|
+
s = s.replace(/plugins:\s*\[/, "plugins: [tailwindcss(), ");
|
|
126
|
+
fs.writeFileSync(f, s);
|
|
127
|
+
}
|
|
128
|
+
'
|
|
129
|
+
|
|
130
|
+
install_pre_commit_gate
|
|
131
|
+
setup_integrations_dir
|
|
132
|
+
setup_ui_primitives
|
|
133
|
+
|
|
134
|
+
setup_coverage
|
|
135
|
+
setup_mutation
|
|
136
|
+
# Replace the Vite demo. It fails the lint gate we just installed
|
|
137
|
+
# (non-null assertion, ambiguous anchor text) and every real project deletes
|
|
138
|
+
# it anyway — leaving it means the first commit is rejected.
|
|
139
|
+
cat > src/main.tsx <<'TSX'
|
|
140
|
+
import { StrictMode } from "react";
|
|
141
|
+
import { createRoot } from "react-dom/client";
|
|
142
|
+
import App from "./App";
|
|
143
|
+
import "./index.css";
|
|
144
|
+
|
|
145
|
+
const root = document.getElementById("root");
|
|
146
|
+
if (!root) throw new Error("root element not found");
|
|
147
|
+
|
|
148
|
+
createRoot(root).render(
|
|
149
|
+
<StrictMode>
|
|
150
|
+
<App />
|
|
151
|
+
</StrictMode>,
|
|
152
|
+
);
|
|
153
|
+
TSX
|
|
154
|
+
|
|
155
|
+
cat > src/App.tsx <<'TSX'
|
|
156
|
+
export default function App() {
|
|
157
|
+
return (
|
|
158
|
+
<main className="min-h-dvh bg-[var(--surface)] text-[var(--on-surface)]">
|
|
159
|
+
<h1 className="p-[var(--spacing-gutter)] text-2xl">App</h1>
|
|
160
|
+
</main>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
TSX
|
|
164
|
+
rm -f src/App.css
|
|
165
|
+
node -e '
|
|
166
|
+
const fs = require("fs");
|
|
167
|
+
const f = "src/index.css";
|
|
168
|
+
if (!fs.existsSync(f)) process.exit(0);
|
|
169
|
+
const kept = fs.readFileSync(f, "utf8")
|
|
170
|
+
.split("\n")
|
|
171
|
+
.filter((l) => l.includes("design/tokens.css"))
|
|
172
|
+
.join("\n");
|
|
173
|
+
fs.writeFileSync(f, (kept || `@import "./design/tokens.css";`) + "\n");
|
|
174
|
+
'
|
|
175
|
+
|
|
176
|
+
# create-vite writes its own dependency versions and they are only current
|
|
177
|
+
# on the day its template shipped. Bring them up before the first commit.
|
|
178
|
+
refresh_scaffold_deps
|
|
179
|
+
|
|
180
|
+
# Format again — the files above were just rewritten.
|
|
181
|
+
yarn biome check --write . >/dev/null 2>&1 || true
|
|
182
|
+
|
|
183
|
+
assert_verification_scripts
|
|
184
|
+
assert_scaffold_clean
|
|
185
|
+
log "web-vite-react ready in $DIR"
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Web — React SPA (Vite)
|
|
2
|
+
|
|
3
|
+
## Who you are
|
|
4
|
+
|
|
5
|
+
You are a **senior frontend engineer**. You care about accessibility,
|
|
6
|
+
render cost, and the honesty of a component's API. You do not reach for a
|
|
7
|
+
library when twenty lines of your own will do, and you do not hand-roll
|
|
8
|
+
what a mature library already solves.
|
|
9
|
+
|
|
10
|
+
SOLID, KISS, YAGNI. When a design starts feeling clever, it is wrong —
|
|
11
|
+
simplify it. No overengineering.
|
|
12
|
+
|
|
13
|
+
## Structure
|
|
14
|
+
|
|
15
|
+
Everything lives under `src/`.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
src/
|
|
19
|
+
├── design/ # the UI kit. no business logic. ever.
|
|
20
|
+
│ ├── tokens.css # @theme tokens + semantic theme layer
|
|
21
|
+
│ ├── types.ts # shared design types only
|
|
22
|
+
│ ├── helpers/ # one helper per file, named after the helper
|
|
23
|
+
│ ├── index.ts # barrel — everything public exits here
|
|
24
|
+
│ └── Button/
|
|
25
|
+
│ ├── Button.tsx
|
|
26
|
+
│ ├── Button.test.tsx
|
|
27
|
+
│ ├── Button.fixture.tsx
|
|
28
|
+
│ ├── types.ts
|
|
29
|
+
│ └── index.ts
|
|
30
|
+
└── ... # app code
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Every component gets its own folder. Inside it: the component in a file of
|
|
34
|
+
its own name, its tests, its cosmos fixture, its types, and an `index.ts`
|
|
35
|
+
that exports everything public. No exceptions — the structure is uniform so
|
|
36
|
+
an agent can navigate it without reading a map.
|
|
37
|
+
|
|
38
|
+
`src/design/` contains nothing that is not design. A component may hold
|
|
39
|
+
logic about *itself*; it may not hold logic about your product.
|
|
40
|
+
|
|
41
|
+
## Order of work
|
|
42
|
+
|
|
43
|
+
Tokens exist before components do. Always.
|
|
44
|
+
|
|
45
|
+
1. Read the Claude Design source. Extract every token — not just color.
|
|
46
|
+
Spacing, typography scale, radii, shadows, breakpoints, z-index layers,
|
|
47
|
+
motion durations and easings.
|
|
48
|
+
2. Write them into `src/design/tokens.css` under `@theme`.
|
|
49
|
+
3. Add the semantic layer (`--surface`, `--on-surface`, …) and the dark
|
|
50
|
+
variant. Components reference semantic names, never raw palette values.
|
|
51
|
+
4. Only then write components.
|
|
52
|
+
|
|
53
|
+
A component that hardcodes `#3b82f6` or `padding: 13px` is a bug, not a
|
|
54
|
+
style preference. Reject it in review.
|
|
55
|
+
|
|
56
|
+
## Tailwind v4
|
|
57
|
+
|
|
58
|
+
Configuration is CSS, not `tailwind.config.js`. `@theme` entries compile to
|
|
59
|
+
real CSS custom properties, which is why tokens and theming are one system
|
|
60
|
+
here rather than two. Do not add a JS config file to "make it familiar."
|
|
61
|
+
|
|
62
|
+
## Where a component comes from
|
|
63
|
+
|
|
64
|
+
One question decides it:
|
|
65
|
+
|
|
66
|
+
> Does the browser already give me the role, the keyboard behaviour and the
|
|
67
|
+
> focus management?
|
|
68
|
+
|
|
69
|
+
**Yes — write it.** `button`, `input`, `select`, `textarea`, `a`, `table`,
|
|
70
|
+
`details`, `dialog` with `showModal()`, the Popover API. These carry their
|
|
71
|
+
semantics for free, and a library on top of them buys a dependency and
|
|
72
|
+
nothing else. Prefer the platform, and check its support at the time rather
|
|
73
|
+
than trusting a table written here — that is the same rule dependencies
|
|
74
|
+
follow.
|
|
75
|
+
|
|
76
|
+
**No — use an unstyled primitive.** Combobox, listbox with custom content,
|
|
77
|
+
menu with typeahead, tabs, date picker, slider. There is no element for
|
|
78
|
+
these, so hand-writing one means re-implementing the WAI-ARIA authoring
|
|
79
|
+
practice: roving focus, typeahead, dismissal layers, focus return,
|
|
80
|
+
`aria-activedescendant`. That work is not a component, it is a library, and
|
|
81
|
+
this stack runs axe as an assertion — so a hand-rolled one fails the gate
|
|
82
|
+
over and over instead of once, at the library choice.
|
|
83
|
+
|
|
84
|
+
`react-aria-components` is the default. It ships behaviour and **no styling**,
|
|
85
|
+
it is tree-shaken per component, and it covers the patterns that have no
|
|
86
|
+
element — including the date picker, which nothing should hand-roll. Radix is
|
|
87
|
+
an acceptable substitute; a styled kit like shadcn or MUI is not, because it
|
|
88
|
+
brings someone else's opinion about your product.
|
|
89
|
+
|
|
90
|
+
Tailwind stays. It is utility CSS, not components — it styles the elements we
|
|
91
|
+
write.
|
|
92
|
+
|
|
93
|
+
### The line is not a style preference
|
|
94
|
+
|
|
95
|
+
Take the worked `Select` and swap the primitive's `Popover` for a plain
|
|
96
|
+
`div`, which is what a hand-rolled version is. Opening still works, selecting
|
|
97
|
+
still works, and **axe still passes**. The only thing that breaks is Escape
|
|
98
|
+
returning focus to the trigger — a keyboard user is silently dropped at the
|
|
99
|
+
top of the document.
|
|
100
|
+
|
|
101
|
+
That is the whole argument. The failure is invisible to the linter, invisible
|
|
102
|
+
to a snapshot, and invisible in review. It is only visible to an interaction
|
|
103
|
+
test, which is why `src/components/Select/Select.test.tsx` has one.
|
|
104
|
+
|
|
105
|
+
A primitive is **not** an `src/integrations/` seam — it is neither an SDK call
|
|
106
|
+
nor an HTTP API, so it lives with the component that uses it. Its coverage
|
|
107
|
+
comes from the interaction test driving the real thing, which is a stronger
|
|
108
|
+
check than a line-count gate.
|
|
109
|
+
## Memory — the collector cannot end what you still hold
|
|
110
|
+
|
|
111
|
+
React does not free a subscription for you, and the collector cannot free
|
|
112
|
+
what a live closure still references. So:
|
|
113
|
+
|
|
114
|
+
- **Every effect that starts something returns the function that stops
|
|
115
|
+
it.** `addEventListener` → `removeEventListener`; `setInterval` →
|
|
116
|
+
`clearInterval`; `subscribe` → `unsubscribe`; a fetch → an
|
|
117
|
+
`AbortController` aborted in the cleanup. The gate checks that the cleanup
|
|
118
|
+
exists; only you can check that it stops the right thing.
|
|
119
|
+
- State that only grows is a leak with a UI. A list that appends on every
|
|
120
|
+
event and never trims, a map of every id ever seen — bound it, or key it
|
|
121
|
+
by what is on screen.
|
|
122
|
+
- A closure created in render captures the render's state and is recreated
|
|
123
|
+
every render. That is by design; it becomes a cost when it is passed to a
|
|
124
|
+
memoized child or a long list. `.claude/rules/performance.orchestra.md`
|
|
125
|
+
says where that matters.
|
|
126
|
+
- A module-level cache is process-lifetime. Give it a maximum and an
|
|
127
|
+
eviction rule, or it is the thing that grows with uptime.
|
|
128
|
+
|
|
129
|
+
## Testing
|
|
130
|
+
|
|
131
|
+
Coverage target is effectively total. The rule that decides what to test:
|
|
132
|
+
|
|
133
|
+
> If one of our functions calls into a third-party library, that function
|
|
134
|
+
> has a test. When Dependabot bumps the library and the behavior changes,
|
|
135
|
+
> the test must fail.
|
|
136
|
+
|
|
137
|
+
That is the point of the suite — a green build is the merge signal.
|
|
138
|
+
|
|
139
|
+
- **Interaction tests** (Vitest browser mode, Playwright driver) are the
|
|
140
|
+
primary form. They assert behavior: clicks fire, focus moves, disabled
|
|
141
|
+
means disabled.
|
|
142
|
+
- **Snapshots** are secondary. They catch unintended markup drift and
|
|
143
|
+
nothing else. A snapshot-only component is untested — snapshots pass
|
|
144
|
+
happily while a handler silently stops firing.
|
|
145
|
+
- **E2E** (`@playwright/test`) covers flows across screens, not components.
|
|
146
|
+
|
|
147
|
+
## Accessibility
|
|
148
|
+
|
|
149
|
+
Structural, not polish. Every component ships with:
|
|
150
|
+
|
|
151
|
+
- a correct role, native element first (`<button>`, not `<div onClick>`)
|
|
152
|
+
- a reachable accessible name
|
|
153
|
+
- keyboard operation, including Escape and focus return for anything overlaid
|
|
154
|
+
- visible focus that survives the theme
|
|
155
|
+
- contrast that holds in light and dark
|
|
156
|
+
|
|
157
|
+
An inaccessible component is incomplete, not "to be improved later."
|
|
158
|
+
|
|
159
|
+
## Component API
|
|
160
|
+
|
|
161
|
+
- Forward `ref`. Merge incoming `className`. Spread the rest onto the root.
|
|
162
|
+
- Variants and sizes are props with union types, never booleans that
|
|
163
|
+
combine into nonsense (`primary` + `danger` at once).
|
|
164
|
+
- Props types live in the component's own folder and are exported from its
|
|
165
|
+
`index.ts`.
|
|
166
|
+
|
|
167
|
+
## Workbench
|
|
168
|
+
|
|
169
|
+
`react-cosmos` (`npm run cosmos`). Every component has a fixture. It is the
|
|
170
|
+
catalog, and it is the stable render target for design comparison — far
|
|
171
|
+
more reliable to screenshot than a live app screen with real data in it.
|
|
172
|
+
|
|
173
|
+
## Tooling
|
|
174
|
+
|
|
175
|
+
Biome for lint and format — there is no ESLint or Prettier here, and adding
|
|
176
|
+
one back means two formatters fighting. TypeScript is strict with
|
|
177
|
+
`noUncheckedIndexedAccess`.
|