Specs and findings

The six skills covering the output you edit — flawless-unit-browser, flawless-perf-harness, flawless-perf-static, flawless-a11y, flawless-react-rules and flawless-findings. Write-once fixtures, the mock allowlist, answering TODO(flawless), why a budget warns, which rule set produced a finding, and the unused-suppression trap.

The six skills that cover the output you edit

A skill is a markdown file an agent loads on demand when a task matches its description. These six cover the half of a run that arrives with work attached, split by which plane produced it: flawless-unit-browser for the specs unit-browser writes, flawless-perf-harness for the runtime plane those browser specs mount on, flawless-perf-static, flawless-a11y and flawless-react-rules one each for the three reporting tasks, and flawless-findings for the triage that spans all three.

That split is deliberate. Before it, one findings skill covered three rule sets and could not say which of them had produced the line on your screen — and the two errors it taught with confidence were exactly the two you would not catch yourself.

flawless-unit-browser

Loads when a generated unit or browser spec is red, when you are filling TODOs after a run, or when a browser spec reports unknown component — the three situations its description names, alongside the topics it covers: the provider wrapper’s write-once fixtures, the mock allowlist, test.skip and TODO(flawless), colocated specs, the browser host.

What it knows. unit-browser writes Vitest + Testing Library specs from static analysis; with perf-harness too you get Playwright browser specs that mount through the profiling host. Everything asserted was derived: the specs describe the render at the props they pass, never the last return in the body.

flawless-react "src/**/*.tsx" --tasks unit-browser,perf-harness --colocate --no-interactive

Fixtures are written once, and an empty cast is a vacuous pass

Unit specs mount inside flawless/provider.tsx, derived from the layout’s JSX chain around {children}. That file regenerates; the values live in flawless/provider.fixtures.ts, written once and never again — not even with --force. Filling it is the highest-value hardening move: replace the emitted empty cast in initialState with a real seeded shape mirroring your app’s seed data, so specs mount the way the app renders — authenticated, themed, populated.

In plain (non-Next) mode there is no provider.tsx: every render opens with a derived nest instead, imported under __Prov-prefixed bindings — the app’s own providers found by hook call, namespace compound, transitive requirement and dynamic render (a <Component /> whose tag a project hook handed back owes whatever that hook’s values can name), convention matches for bare-module hooks (useTheme → the exported ThemeProvider that renders that module’s binding), and generated shims for library contexts. --provider sits outside that nest.

initialState: {
  user: { id: "…", email: "trustee@demo.local", name: "…", /* seed persona */ },
  status: "authenticated",
} satisfies ComponentProps<typeof StoreProvider>["initialState"],

Pin any timestamp; Date.now() in a fixture makes specs nondeterministic. Tighten the assertion as you fill, too: a mocked building named "flawless-name" must reach the DOM (expect(screen.getByText("flawless-name")).toBeInTheDocument()), or the spec passes on an empty state — the vacuous pass this tool exists to prevent.

The mock allowlist is closed

Client specs mock only next/navigation, next/headers, server-only, next/link, next/font/*, and "use server" modules. Do not widen it: mocking a UI component replaces the element the interaction tests click. Never mock cn or clsx away entirely — classes vanish. One wart survives in suites generated before FR-027: a vi.mock of the utils module stubbing cn as vi.fn(async () => ({})), an async stub for a sync function. Regeneration emits the sync form; in place, swap it for args.filter(Boolean).join(" ").

Skips and TODOs are answers waiting, not defects

A test.skip with a named reason is intended output — never delete the assertion or hand-write one. Give the component what the reason asks for (a data-testid, a fixture) or extend the generator. TODO(flawless) markers come in three kinds:

  • “some return shapes could not be inferred.” The tool already tried the dep’s own declaration — a Promise<T> annotation, or a single-fetch body joined to the OpenAPI example, which emit an /* inferred from … */ fixture and no TODO. The marker means usage and declaration came up empty: a computed return, a generic, a dotted callee (db.query.findMany), an unannotated factory (getDb), a cache()-wrapped export, or a type hitting a construct zod inference bails on (.transform, a union, a record, a date; barrel-resolved workspace types work since B-8, z.infer enums and objects since B-9). Give the mocked data-dep a realistic resolved value — unless every consumer of the handle is itself mocked, where an opaque {} is the right fixture.
  • “wrap renderTarget() in the matching Root.” Automatic when the part’s own file exports the family (a Root-rendering sibling plus a Content sibling) and the family is open-state; it survives for rootless files, cross-file families, and value-driven ones (accordion, tabs). Mount the part inside its Root as call sites do — render(<AlertDialog><AlertDialogTrigger>Delete</AlertDialogTrigger></AlertDialog>) — then assert its content. Dialog-family parts need the content wrapper too (<Dialog open><DialogContent>…); portal content is read off baseElement.
  • “server action stubbed — give it a realistic return value.” Automatic when the action returns a single literal, redirect and notFound exits discounted; it survives for computed returns, two distinct shapes, or an unresolved sibling export. Read the action, pick the shape the component consumes, write it into the stub.

The colocated stamp, and where hand-hardening is safe

Colocated specs (--colocate, <file>.flawless.test.tsx beside the source) are kept on re-runs, and their first line is a provenance stamp — // @flawless-generated sha256:<hex16> flawless-react@<version>, hashing the rest of the file. --force rewrites a spec only while that stamp matches, so hand-hardening one protects it: kept, and listed in the summary as hand-edited. Deleting the stamp line demotes the file to pre-stamp rather than making it regenerable; deleting the whole file is how you hand a spec back. Specs inside the out dir are rewritten on any re-run — never hand-harden those.

Run them with bun run flawless:unit, whose Vitest config selects both src/**/*.flawless.test.* and flawless/unit/**. A bare bun test script collects colocated specs under the wrong runner — scope it to bun test tests.

The browser host registry, and unknown component

Browser specs mount through flawless/host/, and exist only when unit-browser and perf-harness both ran. The mount tables REGISTRY, FN_PROPS and WRAP_IN live in host/registry.ts, which regenerates on every run, so a newly analysed component arrives by a plain no-force re-run. Write-once host/main.tsx is only the wrapper; components you add by hand go in its ALL / ALL_FN_PROPS / ALL_WRAP_IN spreads, which survive regeneration.

So unknown component: <Name> means the re-run has not happened yet — unless the suite is pre-split, its main.tsx carrying no from "./registry" import, which owes the one-time migration the warning names: delete host/main.tsx and re-run, regenerating provider wiring from --provider. No flag rewrites main.tsx; write-once is absolute, --force included (findings-ledger FR-016). The host those specs mount — its boot file, budgets, snapshots and the port it shares with this plane — is flawless-perf-harness.

flawless-perf-harness

Loads when you are about to run flawless:perf, when a profile spec is red or a budget warns, when snapshots are missing, or before quoting a mount time to anyone. It is the runtime plane: a Bun-served host bundle, one Playwright spec per host-mountable component, React’s Profiler, and V8 heap snapshots.

What it knows. Which files are whose: host/registry.ts regenerates every run — so a component hand-added there is lost, and the place to add one is the ALL spread in write-once host/main.tsx. And budgets.json keeps whatever you wrote, which protects a tightened number and, in the other direction, means a deleted key silently reverts to the default at the next measurement because nothing rewrites the file to remind you.

flawless-react "src/app/**/page.tsx" --nextjs . --tasks unit-browser,perf-harness

What it stops you getting wrong. The headline is that budgets.json is not a baseline: the gates are warn-only and nothing is ever compared against a previous run, so “it passed the budget” means “it was under 50 ms and 16 renders”, not “it did not regress”. Baselines belong to the load plane, which has them. The rest is shared-state arithmetic: the browser specs and the perf specs both boot bun host/server.ts on 127.0.0.1:5173, so a stuck host makes two planes look broken at once, and --heaps defaults to stress — snapshots land in heaps/ only for the modes that ask for them.

flawless-perf-static

Loads when a perf/* finding is on screen, or when a clean report is about to be trusted. The analyser is the tool’s own, so this is the one skill whose rules are not borrowed from Biome.

What it knows. The nine rules and their severities — and there is exactly one error, perf/set-state-in-render, which is also the only one that is never a style preference. Everything else warns, including the whole hook-drift trio: perf/hook-missing-dependency, perf/hook-extraneous-dependency and perf/hook-missing-deps-array. It knows where the findings land — one reports/<Component>.perf-static.json per component that owns a finding, so per-component files from earlier runs linger and a glob over reports/ over-counts — and that the <Component>.flawless.json sidecar beside each source holds paths relative to itself.

What it stops you getting wrong. That perf/unresolved-prop is not a style note: it means the generated spec passes undefined for that prop, and the fix is --props or the spec’s DEFAULTS. And that a clean report is not proof, because the analyser abstains deliberately — it skips a hook whose deps contain member access or a spread, so the file it declines to judge is not a file it judged clean.

flawless-a11y

Loads when an a11y/* finding is on screen, on web or under --expo. One task, one aggregate report, two engines — which is the whole reason this skill exists.

What it knows. On web the engine is Biome’s a11y group, run through the same scratch config as react-rules, so a finding is suppressible the ordinary way with a reason on the line above. On native it is the tool’s own six rules — a11y/unnamed-control, a11y/missing-role, a11y/textinput-unlabelled, a11y/label-text-mismatch, a11y/invalid-role and one warning, a11y/icon-unhidden — and there is no suppression mechanism at all on that path.

What it stops you getting wrong. Reaching for a biome-ignore comment on a native finding: it does nothing, and the finding stays. And reading a11y/textinput-unlabelled as satisfied by a placeholder, which it is not.

flawless-react-rules

Loads when a Biome finding appears in reports/react-rules.json, or when the project’s own Biome run disagrees with the tool’s. The answer to the disagreement is in the report’s own note: local biome.json is bypassed, deliberately — the sweep asks “does this React code have a known defect”, not “does this repo agree with its own config”.

What it knows. The curated set: recommended: false, then correctness and suspicious enumerated rule by rule, errors except a warning tail (noArrayIndexKey, noExplicitAny, noDoubleEquals, noEmptyBlockStatements and friends). It knows useExhaustiveDependencies is the same question perf-static asks as perf/hook-missing-dependency, answered by an engine that shares no code with it — so a fix that silenced one is not evidence about the other. And that a parse/* diagnostic is pushed through the category filter on purpose: a file the analyser could not read is never a pass.

What it stops you getting wrong. Two attribution errors. useHookAtTopLevel findings are react-rules; the hook-dependency drift findings are perf-static and prefixed perf/. And when react-rules reports nothing while your own biome ci reports a suppressed site, the suppression lives in a config the sweep never read.

flawless-findings

Loads when you are triaging a findings list after a run, deciding fix-versus-suppress, or a suppression shows up as “unused” — and it advertises REPORT.md’s accounting, the reports/ JSON and sidecars, and the suppression rules that keep your Biome and the tool’s sweeps agreeing. It is deliberately only the triage: what a given rule means lives with the plane that emitted it, in flawless-perf-static, flawless-a11y or flawless-react-rules.

What it knows. Which task produced what, and how much to trust it. Three tasks only report: perf-static runs the tool’s own rules, a11y and react-rules are Biome sweeps with narrowed rule sets. Findings print as file:line:col rule message and land in flawless/reports/*.json — one aggregate per task plus per-component files, written only for components that own findings, so stale per-component reports from earlier runs linger. Trust the task summary counts, never a glob over reports/.

REPORT.md regenerates every run and describes the last run, not the suite on disk. Its components table puts every component in exactly one render-mode row (client, rsc, compound-part, document-root, null), split by mountable or not; “Not covered, and why” names each gap; the task table’s Status column says where perf-harness sat in the lifecycle, generated / refreshed / kept. A file count is not coverage.

Triage in order, and stop before the advisory tail

  1. perf/set-state-in-render (perf-static) — the analyser’s only error, always real, fix first.
  2. react-rules errors — useHookAtTopLevel especially: a hook behind an early return changes hook order between renders and React throws on the state update after it.
  3. Hook dependencies. Two engines, one question: Biome answers it as useExhaustiveDependencies and the analyser as perf/hook-missing-dependency / perf/hook-extraneous-dependency. They share no implementation, so a fix that silenced one is not evidence about the other. An extraneous dep usually means the dep is never read: remove it, or for a scroll-on-change effect make it real by reading it (if (messages.length === 0) return;). For a missing one, wrap the function in useCallback and add it rather than suppressing.
  4. perf/unstable-child-prop — a real win when the child is memoised or heavy (a third-party <Sonner>, a memo’d list): hoist static objects and arrays to module scope, and leave inline arrows to plain DOM elements alone.
  5. noArrayIndexKey (react-rules, a warning) — real for editable lists (a client-side id at creation, keyed on, stripped at submit); a reasoned suppression for server-static lists, or where the index is the identity.
  6. perf/unresolved-prop, no-memoisation-with-children, missing-memo-leaf — advisory. Fix where profiling agrees; chasing them to zero is churn, not performance.

A suppression is an answer — and it can be “unused” on the other side

Every suppression carries its reason, and none exists to make a batch green.

// biome-ignore lint/a11y/noLabelWithoutControl: generic wrapper — every call site associates the control

Biome attaches the comment to the next line’s diagnostic: for a multi-line JSX element whose finding anchors on the key= attribute, it goes inside the tag directly above that attribute, not above the element.

The cross-config trap: the sweeps run a scratch config holding only their own rules, your project’s Biome has its own set, and a rule enabled in only one of the two makes the suppression “unused” in the other. The tool’s side is handled — the sweeps ignore suppressions/unused. Yours is not: if the project config does not enable the rule you are suppressing, your own biome ci flags the comment. Enable it there — usually free, since the suppressed site was the only finding — and both runs agree.

Track the ledger across runs (the skill’s example: perf-static 246 / a11y 1 / react-rules 10) and explain every delta; a burn-down commit should say “a11y 1→0, react-rules 10→0” with one cause per line. New findings after a regeneration are usually the analyser seeing new components, not regressions — check the components count first.

Installing them

flawless-react skills install --select flawless-unit-browser,flawless-perf-harness
flawless-react skills install --select flawless-perf-static,flawless-a11y,flawless-react-rules
flawless-react skills list            # current / edited / missing, in both scopes

Each skill is written once, to .agents/skills/<name>/SKILL.md, with its own link at .claude/skills/<name>; flawless-findings and flawless-load install the same way. Project scope wins when both a project and a user copy exist.

A stale copy is worse than none. The repository copy is the source of truth and moves when the tool moves: FR-027 retired the async cn stub, FR-016 split the registry, and two of the three TODO(flawless) kinds are now answered automatically. A copy predating those loads with the same confidence as a current one and is wrong exactly where you reached for it — skills list reports it as edited, and install will not overwrite it without --force. Re-run the installer after every upgrade, and if you keep both scopes, check both.