Frontend Review
Audit the current branch diff against the frontend rule contract in
.claude/docs/FRONTEND_PATTERNS.md and fix
every violation before the PR is created or updated. This is a self-review
gate: its purpose is to catch the findings reviewers would otherwise post,
before they see the PR.
When to run
- Before creating a PR whose diff touches
site/src/.
- Before pushing significant new commits to an existing frontend PR.
- Skip only when the diff touches no files under
site/src/.
Workflow
- Collect the diff:
git diff --merge-base main -- site/src (add
site/e2e if touched). Use origin/main instead when the checkout has an
origin remote and the local main may be stale. List the changed files.
- For each changed file, audit against each FE rule using the checklist
below. Read the full file when the diff alone cannot answer a check (for
example, whether a story's
play function exercises the new behavior).
- Report results as a per-rule verdict table (see Output format). Every FAIL
must carry
file:line and a one-line reason.
- Fix all FAIL findings with the smallest safe diff. Re-run the audit until
every rule passes or a remaining finding is explicitly justified.
- Include unresolved justifications in the PR description so reviewers see
them up front.
Per-rule diff checklist
- FE1 (Storybook coverage): Does any changed component or page alter
user-visible behavior? Then a changed or added
.stories.tsx must exist,
and its play function must perform the new interaction (open the menu,
submit the form), not merely render. Interaction tests added to .test.tsx
files are a FAIL unless they cover pure logic; renderHook suites for
stateful UI hooks count as interaction tests and belong in the consuming
component's story.
- FE2 (types): Search the diff for
any, as unknown as, non-null
assertions in any form (x!.y, items[0]!, fn()!, value! as T), and
new as casts. Check that API data uses types from api/typesGenerated.ts.
- FE3 (reuse/scope): For each new component, hook, or helper, search
site/src/components/ and sibling folders for an existing equivalent.
Flag near-duplicates, hand-assembled versions of wrapped primitives, dead
branches, and unrelated changes bundled into the diff. Flag new React hooks
that an existing hook, a plain function, or component state could replace;
several new single-use hooks in one diff is a FAIL.
- FE4 (comments): Read every comment line the diff adds or edits. Flag
any comment that restates the identifier, assertion, or control flow.
Verify surviving comments are factually correct.
- FE5 (UI states): For each view rendering server data, confirm loading,
error, empty, and refetch handling. Flag form or selection state that a
background refetch would reset.
- FE6 (a11y): Flag interactive elements that are keyboard-unreachable,
aria-labels that replace visible label text, aria-* props that the
underlying primitive overwrites, and visually-hidden elements still in the
tab order.
- FE7 (react-query): Flag direct
API.*/fetch calls in components,
string-literal query keys (must import the constant from api/queries/),
isLoading || isFetching patterns, missing invalidation on mutation paths
(including partial failure), and mutateAsync in try/catch with an empty
catch.
- FE8 (effects): For every added or modified
useEffect, apply the
decision tree in FRONTEND_PATTERNS.md. Flag derived state via
setState-in-effect, fetches triggered by effects, new dependencies on
effects that own connections, and effects that only write refs nobody
reads.
- FE9 (fixtures): Flag inline entity literals that duplicate or deviate
from
Mock* fixtures in site/src/testHelpers/, and shared pre-wired
query objects instead of per-story inline { key, data } wiring. Flag any
Object.defineProperty replacement of a browser global in tests or
stories: unit tests stub with vi.stubGlobal, stories mock existing
globals with spyOn from storybook/test.
- FE10 (test queries): Flag
querySelector, class-name substring
matches, geometry assertions, behavior: "smooth" dependence, and
locale-less toLocaleString() in changed tests and stories.
Output format
FE1 PASS
FE2 FAIL site/src/pages/FooPage/FooPage.tsx:42 `as unknown as Workspace` cast
FE3 PASS
...
One line per rule. FAIL lines carry every finding (repeat the rule ID for
multiple findings). After fixes, print the re-run table. The audit is done
when all rules PASS or remaining FAILs have a written justification.
Notes
- This audit does not replace
pnpm check, pnpm lint, pnpm format, or
tests; run those too (see site/AGENTS.md Pre-PR Checklist).
- Report findings in the current diff only. Do not refactor pre-existing
violations in untouched code; note them at most.
1---2name: frontend-review3description: Diff-scoped self-review of frontend changes under site/src against the FE pattern rules (FE1-FE10) before creating or updating a PR. Use whenever a branch diff touches site/src files.4---5
6# Frontend Review
7
8Audit the current branch diff against the frontend rule contract in
9[`.claude/docs/FRONTEND_PATTERNS.md`](../../docs/FRONTEND_PATTERNS.md) and fix
10every violation before the PR is created or updated. This is a self-review
11gate: its purpose is to catch the findings reviewers would otherwise post,
12before they see the PR.
13
14## When to run
15
16- Before creating a PR whose diff touches `site/src/`.
17- Before pushing significant new commits to an existing frontend PR.
18- Skip only when the diff touches no files under `site/src/`.
19
20## Workflow
21
221. Collect the diff: `git diff --merge-base main -- site/src` (add
23 `site/e2e` if touched). Use `origin/main` instead when the checkout has an
24 `origin` remote and the local `main` may be stale. List the changed files.
252. For each changed file, audit against each FE rule using the checklist
26 below. Read the full file when the diff alone cannot answer a check (for
27 example, whether a story's `play` function exercises the new behavior).
283. Report results as a per-rule verdict table (see Output format). Every FAIL
29 must carry `file:line` and a one-line reason.
304. Fix all FAIL findings with the smallest safe diff. Re-run the audit until
31 every rule passes or a remaining finding is explicitly justified.
325. Include unresolved justifications in the PR description so reviewers see
33 them up front.
34
35## Per-rule diff checklist
36
37- **FE1 (Storybook coverage)**: Does any changed component or page alter
38 user-visible behavior? Then a changed or added `.stories.tsx` must exist,
39 and its `play` function must perform the new interaction (open the menu,
40 submit the form), not merely render. Interaction tests added to `.test.tsx`
41 files are a FAIL unless they cover pure logic; `renderHook` suites for
42 stateful UI hooks count as interaction tests and belong in the consuming
43 component's story.
44- **FE2 (types)**: Search the diff for `any`, `as unknown as`, non-null
45 assertions in any form (`x!.y`, `items[0]!`, `fn()!`, `value! as T`), and
46 new `as` casts. Check that API data uses types from `api/typesGenerated.ts`.
47- **FE3 (reuse/scope)**: For each new component, hook, or helper, search
48 `site/src/components/` and sibling folders for an existing equivalent.
49 Flag near-duplicates, hand-assembled versions of wrapped primitives, dead
50 branches, and unrelated changes bundled into the diff. Flag new React hooks
51 that an existing hook, a plain function, or component state could replace;
52 several new single-use hooks in one diff is a FAIL.
53- **FE4 (comments)**: Read every comment line the diff adds or edits. Flag
54 any comment that restates the identifier, assertion, or control flow.
55 Verify surviving comments are factually correct.
56- **FE5 (UI states)**: For each view rendering server data, confirm loading,
57 error, empty, and refetch handling. Flag form or selection state that a
58 background refetch would reset.
59- **FE6 (a11y)**: Flag interactive elements that are keyboard-unreachable,
60 `aria-label`s that replace visible label text, `aria-*` props that the
61 underlying primitive overwrites, and visually-hidden elements still in the
62 tab order.
63- **FE7 (react-query)**: Flag direct `API.*`/`fetch` calls in components,
64 string-literal query keys (must import the constant from `api/queries/`),
65 `isLoading || isFetching` patterns, missing invalidation on mutation paths
66 (including partial failure), and `mutateAsync` in `try/catch` with an empty
67 catch.
68- **FE8 (effects)**: For every added or modified `useEffect`, apply the
69 decision tree in FRONTEND_PATTERNS.md. Flag derived state via
70 `setState`-in-effect, fetches triggered by effects, new dependencies on
71 effects that own connections, and effects that only write refs nobody
72 reads.
73- **FE9 (fixtures)**: Flag inline entity literals that duplicate or deviate
74 from `Mock*` fixtures in `site/src/testHelpers/`, and shared pre-wired
75 query objects instead of per-story inline `{ key, data }` wiring. Flag any
76 `Object.defineProperty` replacement of a browser global in tests or
77 stories: unit tests stub with `vi.stubGlobal`, stories mock existing
78 globals with `spyOn` from `storybook/test`.
79- **FE10 (test queries)**: Flag `querySelector`, class-name substring
80 matches, geometry assertions, `behavior: "smooth"` dependence, and
81 locale-less `toLocaleString()` in changed tests and stories.
82
83## Output format
84
85```
86FE1 PASS
87FE2 FAIL site/src/pages/FooPage/FooPage.tsx:42 `as unknown as Workspace` cast
88FE3 PASS
89...
90```
91
92One line per rule. FAIL lines carry every finding (repeat the rule ID for
93multiple findings). After fixes, print the re-run table. The audit is done
94when all rules PASS or remaining FAILs have a written justification.
95
96## Notes
97
98- This audit does not replace `pnpm check`, `pnpm lint`, `pnpm format`, or
99 tests; run those too (see site/AGENTS.md Pre-PR Checklist).
100- Report findings in the current diff only. Do not refactor pre-existing
101 violations in untouched code; note them at most.