docs: add frontend-review skill for pre-PR FE rule audits (#27408)

## What

Adds `.claude/skills/frontend-review`, a diff-scoped self-review skill
that audits changes under `site/src/` against the FE1 to FE10 rule
contract before a PR is created or updated. Each rule has a concrete
"what to look for in a diff" checklist, and the output format is a terse
per-rule PASS/FAIL table with `file:line` findings.

Wiring:

- `site/AGENTS.md` Pre-PR Checklist gains step 6: run the audit when the
diff touches `site/src/`.
- `.claude/skills/code-review` now cites FE rule IDs for frontend
findings.
- The deep-review `frontend-reviewer` role audits against the same
contract.

## Why

Most frontend review findings are semantic (missing interaction
coverage, clobbered form state, near-duplicate components) and cannot be
linted. Today they are caught post-hoc by reviewers and the review bot,
which is exactly the "PRs get complaints" experience. Running the same
rubric before the PR exists converts review rounds into pre-push fixes.

Stacked on #27407 (the rule contract). The deterministic-checks
follow-up (#27409) was closed; that subset will be enforced through
proper linting instead.

> This PR was written by Mux, an AI coding agent, on behalf of Mike.
This commit is contained in:
Michael Suchacz
2026-07-23 07:22:10 +02:00
committed by GitHub
parent 5565fcd03b
commit 52a687902f
5 changed files with 104 additions and 0 deletions
@@ -6,6 +6,7 @@
- Map every user-visible state: loading, polling, error, empty, abandoned, and the transitions between them. Find the gaps. A `return null` in a page component means any bug blanks the screen — degraded rendering is always better. Form state that vanishes on navigation is a lost route.
- Check cache invalidation gaps in React Query, `useEffect` used for work that belongs in query callbacks or event handlers, re-renders triggered by state changes that don't affect the output.
- Audit the diff against the FE rule contract in `.claude/docs/FRONTEND_PATTERNS.md` (FE1 to FE10) and cite rule IDs in findings.
- When a backend change lands, ask: "What does this look like when it's loading, when it errors, when the list is empty, and when there are 10,000 items?"
**Scope boundaries:** You review frontend code. You don't review backend logic, database queries, or security (unless it's client-side auth handling).
+1
View File
@@ -0,0 +1 @@
../../.claude/skills/frontend-review
+3
View File
@@ -38,6 +38,9 @@ quality problems.
- **Concurrency**: Race conditions, deadlocks, missing synchronization
- **Resources**: Leaks, unclosed handles, missing cleanup
- **Error handling**: Swallowed errors, missing validation, panic paths
- **Frontend** (`site/src/`): audit against the FE rule IDs in
[Frontend Patterns](../../docs/FRONTEND_PATTERNS.md) and cite the rule ID
in findings (for example, "FE7: re-typed query key")
## What NOT to Comment On
+94
View File
@@ -0,0 +1,94 @@
---
name: frontend-review
description: 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.
---
# Frontend Review
Audit the current branch diff against the frontend rule contract in
[`.claude/docs/FRONTEND_PATTERNS.md`](../../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
1. 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.
2. 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).
3. Report results as a per-rule verdict table (see Output format). Every FAIL
must carry `file:line` and a one-line reason.
4. Fix all FAIL findings with the smallest safe diff. Re-run the audit until
every rule passes or a remaining finding is explicitly justified.
5. 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.
- **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.
- **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-label`s 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.
- **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.
+5
View File
@@ -273,6 +273,11 @@ Debug logs and pprof dumps use the same job name and commit SHA convention.
3. `pnpm format` - Format code consistently
4. `pnpm test` - Run affected unit tests
5. Visual check in Storybook if component changes
6. If the diff touches `site/src/`, run the `frontend-review` skill
(discovered from `.agents/skills/`; canonical copy at
[.claude/skills/frontend-review](../.claude/skills/frontend-review/SKILL.md)):
audit the diff against FE1 to FE10 and fix every FAIL before creating
the PR
## Migration (MUI → shadcn) (Emotion → Tailwind)