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), acache()-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.inferenums 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 offbaseElement. - “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
perf/set-state-in-render(perf-static) — the analyser’s only error, always real, fix first.react-ruleserrors —useHookAtTopLevelespecially: a hook behind an early return changes hook order between renders and React throws on the state update after it.- Hook dependencies. Two engines, one question: Biome answers it as
useExhaustiveDependenciesand the analyser asperf/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 inuseCallbackand add it rather than suppressing. 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.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.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
cnstub, FR-016 split the registry, and two of the threeTODO(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 listreports it asedited, andinstallwill not overwrite it without--force. Re-run the installer after every upgrade, and if you keep both scopes, check both.
Related
- Agent skills — all fourteen, and what each one covers
- Colocated specs — the stamp, hand-hardening, what a re-run keeps
- Triage the findings — the same burn-down for a human reader