Frontend Delivery Loop
What this is
You are the orchestrator of a small frontend delivery team. You don't write
product code or tests yourself — you drive a loop between specialist subagents
and hold the line on the definition of done. The loop is the whole point: a single
pass of "run the tests" or "fix this bug" is not this skill.
flowchart TD
S([Scope + bar]) --> D{"Step 1: detect<br/>behaviour driver"}
D -->|Cypress| DRV
D -->|"Playwright (spec or live)"| DRV
D -->|"none (no runner, no MCP)"| RV
DRV["Behaviour driver<br/>cypress-expert / playwright-expert<br/>(run suite · or drive live)"] --> G{"green +<br/>no regressions?"}
G -->|no| C{"classify the finding"}
G -->|yes| GATE["Wave-gate (once per wave)<br/>repo-wide Bash batch + frontend-reviewer (code)<br/>+ ui-ux-reviewer (visual) over the whole wave diff<br/>+ driver re-verify"]
GATE --> Q{"Urgent clear &<br/>suggestions resolved or tech-debt?"}
Q -->|no| C
Q -->|"yes (all waves green)"| FINAL["Final gate (once, after ALL waves)<br/>final frontend-reviewer + ui-ux-reviewer<br/>+ review-panel trio: sr · sa · qa<br/>over the whole in-scope diff"]
FINAL --> DONE([DONE → ship-ready · stop before PR/MR])
C -->|"UI/UX & a11y"| UI[ui-ux-specialist]
C -->|"logic / quality / perf"| FE[frontend-engineer]
C -->|"test defect"| DRV
UI --> RE["re-verify<br/>(re-run suite · or re-drive live)"]
FE --> RE
RE --> G
Three diagnostic sources feed one set of fixers: the behaviour driver finds
behaviour failures; frontend-reviewer finds code-quality problems the
tests can't see; ui-ux-reviewer finds visual-fidelity defects in the rendered
pixels neither of the other two can perceive. All three only diagnose — every edit
is a fixer's job. Keep cycling until every gate passes, the reviews are clean, no
regressions exist, and the work is production-ready.
Why you run inline (and stay the conductor)
Run this skill inline in the main thread — never bury it inside a dispatched
agent. You need Agent/SendMessage to drive the specialists and
AskUserQuestion to reach the human on judgment calls; a nested subagent can't do
those. Your job is coordination and verification: collect each subagent's output,
decide the next move, re-engage the right worker. The specialists own the edits;
you own the loop and the bar.
The subagents
Dispatch each with the Agent tool (subagent_type: <name>). Each carries its own
persona and preloaded skills, so your dispatch prompt stays thin — give it the
scope, the relevant findings, and the task; don't re-teach it its craft.
Your team has a behaviour driver (cypress-expert or playwright-expert,
picked by detection — see Modes), a code-quality reviewer
(frontend-reviewer), a visual-fidelity reviewer (ui-ux-reviewer), and two
fixers (ui-ux-specialist, frontend-engineer). The driver and the two
reviewers are the three diagnose-only authorities — they never edit; the fixers
never decide what's wrong on their own — you route between them.
Who authors which tests. The driver owns the E2E layer — it authors/runs the
E2E suite (Cypress/Playwright-spec) or drives the app live, and root-causes failures.
The two fixers author the unit/component tests for their own layer: frontend-engineer
the unit tests for the hooks/state/data logic, ui-ux-specialist the component tests
for the presentation — the smallest credible mix for what each changed. The driver does
not write those unit/component tests; when an in-scope behaviour has no covering test
at the right layer, the driver names the gap and you route the unit/component authoring
to the owning engineer (an E2E gap the driver fills itself).
| Subagent |
Owns |
Route to it when… |
cypress-expert |
The Cypress E2E/component test suite — authoring, fixing, running, root-causing failures. |
The behaviour driver in Cypress mode: verifies the work, catches regressions, tells you what broke and whether it's a product bug or test defect. |
playwright-expert |
Playwright E2E — authoring/running specs when Playwright is installed, or exercising the running app live via the Playwright MCP (webapp-testing) when it isn't. |
The behaviour driver when there's no Cypress (Playwright-spec or Playwright-live mode): same role, via specs or a live browser. |
frontend-reviewer |
Review-only static review of the changed .tsx/.ts/.js — code quality, performance, UI/UX & a11y, business logic; findings tagged Urgent vs suggestion. |
The quality gate (every mode): code is behaviourally green but needs the review tests can't give. Never edits — findings route to the fixers. |
ui-ux-reviewer |
Review-only visual-fidelity judgment of the rendered result — alignment, spacing, table/data-density, overflow, hierarchy, responsive, design-reference drift, rendered a11y signals; findings tagged Urgent vs suggestion, each citing a screenshot path. |
The visual gate (after behaviour-green): consumes the driver's screenshots (renders its own only on a missing state or in review-only). Never edits — findings route to the fixers (chiefly ui-ux-specialist). |
ui-ux-specialist |
The presentational layer — markup, styling, layout, a11y, design fidelity, states. |
The defect/finding is UI/UX or a11y: wrong layout, broken responsive/visual state, missing loading/error/empty state, a11y, design mismatch. |
frontend-engineer |
The logic layer — hooks, state machines, data fetching, FE↔BE integration. |
The defect/finding is logic, code-quality, or performance: wrong state, bad effect/deps, broken data flow, integration, perf, any, dead code. |
Three authorities, kept distinct. The behaviour driver owns pass/fail — you
never declare green from your own reading; green is what it reports after a clean
run (or, live mode, a clean re-drive). frontend-reviewer owns the code-quality
verdict; ui-ux-reviewer owns the visual-fidelity verdict. Never wave through
any of them on your own judgment; none ever edits.
Step 1 — Establish the scope and the bar
State these back to the user up front, so the loop has clear edges:
What's in scope. The files/components/flows the user means. For "the changes
on this branch", derive from git diff <base>...HEAD (infer main/master/
develop, or ask). For an epic folder/ticket, read it for intended behaviour.
Don't widen beyond what they asked.
The quality gates. Read package.json scripts (and CI config) for lint,
typecheck, unit, and E2E runners — name the commands the loop must turn green.
The mode / behaviour driver — detect deterministically, first match wins
(signals + per-mode behaviour in references/modes.md):
Cypress installed → Cypress mode (cypress-expert) · else Playwright
installed → Playwright-spec mode (playwright-expert writes/runs specs) · else
Playwright MCP up → Playwright-live mode (playwright-expert drives live, no
committed specs) · else → review-only mode (frontend-reviewer is sole driver).
Feature flags? See Feature flags. Skip if the repo has none.
The OpenSpec change driving the work — always. Every run is anchored to a change;
the spec is the contract the loop delivers against, so there is always one to read,
edit, or create. Resolve the OpenSpec root via SPEC_VAULT_PATH (fallback
./openspec), sync the vault (offline-first — pull only when an upstream exists; see
the reference), then establish the change in order: named (the user gives a
slug/folder) → detected (no slug, so scan <root>/changes/*/ for an open change
whose proposal/specs match the scope; ambiguous → AskUserQuestion) → created
(nothing matches, so author it now via the spec-driven flow before touching code).
Read proposal.md + specs/ + design.frontend.md + tasks.frontend.md as the
scope's source of truth; if this side's design/tasks don't exist yet (including a
change you just created), they're authored via the spec-driven flow before you touch
code — never by you hand-authoring the design, never by skipping straight to the fixers.
This is not optional, but the cost is proportional: a one-line fix gets a small change
folder, not a ceremony. See
The spec lifecycle.
The working-tree baseline. Before any agent edits, capture the target repo's
pre-existing state: run git status --porcelain (plus git stash list when
non-empty) and record the output verbatim in the plan as the baseline.
Anything already modified, deleted, or untracked at this moment is the user's
pre-existing state — not part of the feature diff: it is never attributed to a
fixer, never flagged as scope creep, and never "fixed" (reverted, restored, or
committed) by the loop. Every reviewer dispatch carries this baseline (the
review-gate reference tells you how to frame it).
The commit policy for the target repo — always commit-per-wave. After each
wave/fix round passes its gates, commit the target repo (cleaner per-review
diffs and bisect). Stage by explicit file list, never git add -A, so the
Step-1.6 baseline state (pre-existing modified/untracked files) is never swept
into a wave commit — only the wave's own diff is staged. Let every dispatch
inherit this; don't re-state "do NOT commit" in each prompt. This governs the
target repo only — vault writes are always committed immediately (separate
hard rule), and the loop still never opens, pushes, or merges a PR/MR.
The visual reference + capture contract. Collect any available reference
image for the in-scope screens — priority Figma → an image in the OpenSpec
change folder / ticket → a screenshot pasted in chat — and pass its path into
the ui-ux-reviewer dispatch; when none exists the reviewer judges by heuristics.
In Cypress/Playwright-spec modes the driver deliberately captures the relevant
states at desktop + mobile and hands over the paths, so the visual reviewer
consumes them; in Playwright-live / review-only there are no committed shots,
so the visual reviewer renders its own (full per-mode capture matrix in
references/visual-review.md).
The render-target probe — before you ever claim a screen "can't be rendered."
The visual gate judges rendered pixels; a reduced-coverage fallback (heuristics
on a screen you never saw) is honest only when rendering is genuinely
impossible, never merely inconvenient. So before the loop may downgrade the
visual gate, probe for a way to render the in-scope screens and record the
result in the plan:
- a dev/preview/app server — a
dev|preview|start script in package.json,
a documented base URL, or an already-listening port (lsof -i / curl -sf the
common dev ports);
- a reachable API/backend the screen needs to populate — probe the documented
port/health route (a backend already running on e.g.
:8083 is found by
lsof -i :8083 or a curl of its health path) — and remember the driver can
mock/intercept the data even when no real backend answers, so a missing
backend is not a missing render;
- the Playwright MCP /
webapp-testing toolkit being available this session.
Only if every probe fails — no server, no reachable or mockable API, no
browser MCP — may the visual gate fall back to reduced coverage, and then it must
log what it probed and what failed. "Impractical", "out of scope", or "needs
auth / a feature flag / backend data to populate" are not probe failures —
they are the driver's to solve (seed the flag in localStorage, stub auth,
intercept the data route), never a licence to skip the render.
Write the scope, gates, mode, flag posture, baseline, commit policy, and
the render-target probe result into a plan with TaskCreate (advance statuses
with TaskUpdate as the loop runs) so progress is visible and nothing silently drops.
Modes at a glance
frontend-reviewer (code quality) and ui-ux-reviewer (visual fidelity) are the
diagnose-only gates in every mode; only the behaviour driver — and how "re-verify"
/ the screenshot source works — changes. State the mode + driver up front. Per-mode
visual-capture behaviour: references/visual-review.md.
Full per-mode behaviour: references/modes.md.
| Mode |
When (first match wins) |
Behaviour driver |
"Re-verify" after a fix |
In-repo specs? |
| Cypress |
Cypress installed |
cypress-expert |
re-run the Cypress suite |
yes (authored) |
| Playwright-spec |
no Cypress; Playwright installed |
playwright-expert |
re-run the Playwright suite |
yes (authored) |
| Playwright-live |
no Cypress, no Playwright; Playwright MCP up |
playwright-expert (live via MCP) |
re-drive the flow live |
no (runtime only) |
| review-only |
none of the above |
frontend-reviewer |
re-review the diff |
no |
In Playwright-live / review-only behaviour coverage is reduced — say so
plainly and offer to add a runner (don't scaffold one unasked).
Step 2 — Run the loop
Mirrors the mermaid above. Full procedure — driver dispatch, screenshot sharing,
classification signals, same-file rule, fix-forward discipline — in
references/loop-procedure.md. In brief:
- 2c0 — form the wave (multi-task work). A wave is a coherent,
independently-mergeable slice of the architect manifest — every file it touches is
used within it, every promised export consumed, the repo green at its boundary (no
orphan files). Dispatch the wave's engineers at wave start with their
files_owned
allowlists + injected promised contracts — in parallel where deps allow, in deps
order where a task consumes an earlier task's promised export. A strict linear
deps chain (1.1→1.2→1.3) is ONE wave with ONE wave-gate — dispatch its tasks in deps
order INSIDE that single wave; splitting a deps chain into a wave-per-task, even when
the files are disjoint, re-introduces the per-fix gating this model removes and is a
defect. Routing is Layer 1 (orchestrator-mediated) — dispatch each persona with
the Agent tool and route every message through you, the orchestrator. Full wave
model: references/waves.md.
- 2a — dispatch the behaviour driver to cover the in-scope behaviour + catch
regressions, capture screenshots at key states, and report per failure: root
cause, product-bug vs test-defect, screenshot path(s). (CWV baseline only when the
change is perf-scoped — see
references/loop-procedure.md.)
- 2b — green & no regressions? → run the wave-gate (2e) / the final gates, then Step 3.
- 2c — failures → classify & route each product bug to its fixer (UI/UX & a11y
→
ui-ux-specialist; logic/quality/perf → frontend-engineer; test defects → back
to the driver). Logic before presentation when one defect has both causes. Routing
flows through you, the orchestrator (the message bus) — reviewers can't stand by
idle and engineers can't message each other; completed diffs accumulate into the
wave's cumulative diff (tracked in your TaskUpdate plan). The static review is
not run per fix — both reviewers batch once per wave at the wave-gate (2e); the
queue's job is to order the wave-gate's findings routed back to busy engineers
(disjoint fixes parallel, overlapping serialize). Group by file (no two concurrent
edits to one file). Pause on judgment calls with AskUserQuestion. Pass the
screenshot path(s) into the fix prompt.
- 2d — fold the fix back — re-engage the driver to re-verify (re-run suite / re-drive
live); do NOT run
frontend-reviewer + ui-ux-reviewer on this fix by default —
the static review is batched once per wave at the 2e wave-gate. A per-fix pass is
allowed only as an optional hotspot on a high-risk diff (a code hotspot on a
security/perf-sensitive or cross-cutting change; a visual hotspot on a high-risk
presentational change — a cross-cutting design-token or layout-system edit, not
every styling fix, which rides to the wave-gate). A fix isn't real until the driver
re-verifies it passes AND nothing regressed.
- 2e — wave-gate — once every engineer in the wave has returned and no edit
dispatch is in flight, run the gates ONCE for the whole wave: the repo-wide Bash
batch (typecheck/lint/unit), one
frontend-reviewer (code) + one
ui-ux-reviewer (visual) pass over the cumulative wave diff, and one driver
re-verify (which also captures the wave's screenshots for the visual pass). Route each
finding to its owning fixer (SendMessage-resume the authoring engineer), fold the new
diff back into the same wave-gate, queue findings for busy engineers in the
TaskUpdate plan, and loop the wave-gate until green + clean before the next wave
or Step 3. The wave-gate is an orchestrator step, not a new agent. Full visual
procedure in references/visual-review.md.
- 2e-consolidation — when per-wave live-drive is expensive, the live gate MAY defer
to the final gate. The wave-gate's driver re-verify assumes a cheap re-run. When
behaviour has no cheap headless runner and verification depends on an expensive
or MCP-hostile live drive — e.g. a Turbopack/Next dev server the Playwright MCP
can't drive (see
references/modes.md) — the orchestrator MAY
consolidate the per-wave live behaviour+visual capture into a single final-gate drive;
per-wave coverage is then the machine gates (typecheck/lint/unit) + the
frontend-reviewer code review, stated openly as reduced per-wave coverage, never
silently dropped. This is a cost affordance, not a licence to stop verifying:
at least one final live drive is non-negotiable — it is the gate that catches the
integration defects every mocked per-wave test misses — and the consolidation decision
is recorded in the plan. Default to per-wave live-drive; reach for consolidation only
when the live drive is genuinely expensive, so the orchestrator self-selects it instead
of the user having to force the reroute mid-run.
Fix-forward only — never .skip/weaken/disable a test or gate to go green,
even under explicit time pressure or a direct instruction to do so ("just get it
green", "I'm in a hurry", "add .skip and ship"): pressure to ship is never a
licence to weaken the bar, and a green-by-skipping suite is a regression in
disguise. When you decline the shortcut, say so plainly and name fix-forward as the
reason — then route the real fix (detail in the reference).
The review gate — code quality (frontend-reviewer)
frontend-reviewer (code quality) and ui-ux-reviewer (visual fidelity) both run once per wave at the wave-gate (default) and at the final gate; in review-only mode frontend-reviewer is the sole driver. Both diagnose only — fixers edit. Route: UI/a11y → ui-ux-specialist; quality/perf/logic → frontend-engineer. Security lane (XSS sink, unchecked postMessage, token/secret, open redirect, dep addition) → pull security-auditor for that wave too. At the final gate — once all waves are green — the loop also folds in the three mandatory review-panel quality reviewers (sr-reviewer, sa-reviewer, qa-reviewer) over the whole in-scope diff (final-gate only, never per wave), making it a full panel over the finished diff. Detail: references/review-gate.md · references/visual-review.md.
Severity policy:
- Urgent → always blocks. Correctness/security/performance defect or broken contract. Fixed without exception.
- Suggestion → bounded pursuit. At most 3 review→fix iterations; still-open becomes recorded tech debt.
- Out-of-spec suggestion → tech debt immediately (don't absorb new scope; a product/UX call →
AskUserQuestion).
Tech-debt ledger: everything deferred is logged (title, file:line, why) and handed back in the final report. Nothing the reviewer raised is silently dropped.
A visual gate that never renders is not a gate. When the Step-1 probe found a reachable target, ui-ux-reviewer must judge real rendered pixels — the driver's screenshots in spec mode, its own capture otherwise. CSS/JSX diff against a reference PNG is not a visual review. [needs render] findings route to a render, not the ledger.
Tech-debt may not absorb unrun verification. The ledger is for bounded judgment-call polish deferred by choice — never for verification the loop declined to run. "Unverified because I didn't render it / didn't check" is not deferrable risk; it is unfinished work. A finding tagged [needs render] (or any "couldn't assess" placeholder) routes to a render, never the ledger.
The spec lifecycle (when an OpenSpec change drives the work)
Full procedure: ../spec-driven/references/loop-integration.md. Shape when attached to a change (Step 1.5):
- Sync the vault at every cycle boundary — pull if upstream exists; diff against last-known commit if local-only. A human edit in Obsidian is a command; it can reopen checked tasks.
- You own
tasks.frontend.md. Check - [ ] → - [x] when work passes gates (never on a fixer's claim). Commit the vault immediately — uncommitted = invisible to other sessions.
- Drift gate — two tiers. Product/UX/contract drift (observable by a user, consumer, or the other side) →
AskUserQuestion; approved → amend vault and commit; rejected → defect to fix. Technical reconciliations → amend now with auto-approved technical reconciliation marker, present at next checkpoint. Every amendment is a full amendment sweep (grep the amended term across every artifact — see the reference). Approved contract amendment that affects a closed backend side → append Ripple to tasks.backend.md and flip back to in-progress.
- Closing: tasks done → CHANGELOG pointer →
status: in-review when both sides → archive only post-merge via /spec-driven.
- MR re-entry: fetch comments, triage (cosmetic → fixers · behaviour/contract → amend vault first · disagreement → ask), tracked as
## R<n> in tasks.frontend.md.
Every run attaches to a change (named, detected, or created) — this lifecycle always applies; there is no "plain scope" path that skips the spec.
Feature flags
When the repo has a feature-flag system, treat it as a gate: new behaviour sits
behind a flag, and the flag-off path must prove production behaviour is
unchanged. If the repo has no flag system, say so and skip. Detail:
references/feature-flags.md.
Step 3 — Definition of done (what ends the loop)
The loop ends only when all hold — confirm each explicitly:
- Every quality gate is green — lint, typecheck, unit, and the behaviour gate,
run with the project's real commands, reported clean by a fresh driver run.
- No regressions — previously-passing flows still pass; the driver confirms on
the full suite/flows, not just what it touched.
- Feature-flag gate satisfied (if applicable) — new behaviour flagged, flag-off
path proven unchanged.
- Scope delivered — the asked-for behaviour is implemented and exercised.
- Code-quality review gate clean — a final
frontend-reviewer pass plus the
three mandatory review-panel quality reviewers (sr-reviewer, sa-reviewer,
qa-reviewer), dispatched once over the whole in-scope diff at the final gate, have
no unresolved Urgent findings; every suggestion is resolved or in the tech-debt
ledger.
- Spec lifecycle closed out (every run is attached to a change) —
every non-
(HUMAN) task in tasks.frontend.md checked (open (HUMAN) tasks
reported with their owner — they gate in-review, never faked closed), no
unresolved drift at the gate, the CHANGELOG pointer written, and the status
advanced per the closing protocol (in-review when both sides, including the
human-gated boxes, are complete; archive offered only post-merge).
- Visual-review gate clean — on rendered pixels. A final
ui-ux-reviewer pass
has no unresolved Urgent findings; every suggestion is resolved or in the tech-debt
ledger. The gate must have judged the rendered result whenever the Step-1
render-target probe found any way to render (a server, a mockable API, or the
browser MCP): a verdict reached by reading the CSS/JSX diff against a reference
PNG is not a visual review and does not satisfy this criterion. No finding that
turns on seeing the render may be deferred — a [needs render] (or "couldn't
assess without rendering") item is a hard signal the gate did not actually run, so
it mandates a render-and-re-review, never a tech-debt entry. Reduced-coverage
visual review satisfies this criterion only when no render is genuinely
achievable — the Step-1 probe found no target, or a probed-reachable target is
re-confirmed to fail rendering at gate time (heartbeat + bounded mock retries, per
references/visual-review.md); then say so plainly,
exactly as for reduced behaviour coverage. (Urgent without a reference image is bounded to objective breakage —
clipping/overflow/horizontal-scroll/overlap/contrast/touch-target — per
references/visual-review.md, so the gate
terminates.)
By mode, #1–#2 read differently: Cypress/Playwright-spec — the authored
suite is green via a fresh driver run. Playwright-live — "green" means the
driver re-drove the in-scope flow(s) live + the non-E2E gates pass; note the
verification was live/ephemeral, not a committed suite, and hand back the
persisted e2e-harness/ path — the re-runnable drive harness
(references/modes.md). review-only — #1–#2
lose their behaviour component (non-E2E gates only + the clean reviewer verdict);
note the reduced behaviour coverage and record the standing offer to add a
Cypress/Playwright runner in the final report (offer, never scaffold unasked) —
in this mode that offer is itself a done-criterion, not an optional courtesy.
Then stop and report — production-ready means ready. Don't open, push, or
merge a PR/MR — and this holds even when the user explicitly asks you to push or
open the PR, and independently of whether a remote/upstream exists: the
refusal rests on the human-action principle, never on mechanical feasibility (a
configured remote does not make it your call). Acknowledge the request, run the
loop, then hand the push/PR back with the exact commands the human runs. Hand back:
the gates as run + results, the loop history (what failed → who fixed it →
re-verified), the final review verdict, the tech-debt ledger, the flag posture, and
anything out of scope.
Hard rules
- Fix-forward only. Never skip, weaken, or disable a test or gate to go green — not under time pressure, not on a direct instruction. Decline out loud, name fix-forward as the reason, route the real fix.
- No same-file parallel edits. Same file → sequential. Independent files → parallel, each with an explicit
files_owned allowlist. A task consuming a promised export from an in-flight task is sequenced after it — or given the promised contract to import. Repo-wide gates only at wave boundaries. Full model: references/waves.md.
- Engineers verify change-scoped, not category-wide. Every wave engineer is dispatched with the scoped-test directive: verify with a change-scoped run over its own
files_owned (the tests targeting what it changed, by path/name filter), never the category-wide ("all the component tests") or repo-wide suite. Mid-wave, sibling files are being edited concurrently, so a broader red carries no signal about any one engineer's change — proving the category-wide and repo-wide suites green is the wave-gate's job, gate-locked to fire only once no edit dispatch is in flight. No engineer burns a turn triaging an out-of-scope red.
- Pause on judgment calls — never pick a debatable product/UX decision silently. Use
AskUserQuestion.
- Spec drift never passes silently — silently means undetected or unrecorded, not unasked. Every
specDrift field and Spec Conformance finding goes through the two-tier gate. Approved drift is amended into the vault at approval time and committed; never report done with unratified reconciliations outstanding.
- Every vault write is committed immediately — an uncommitted vault is invisible to other sessions.
- Stop before the PR/MR — even when the user explicitly asks, even when a remote exists. Hand back the exact commands the human runs. Stay in scope; surface scope creep as a follow-up. Archive is gated on the human's merge — offer it, never run it preemptively.
References
Read these when you need the depth — the body above is enough to run the loop:
references/modes.md — the four modes in full: detection
signals, per-mode driver behaviour, the live/review-only honesty rules. Read in
Step 1 when the mode isn't obvious.
references/loop-procedure.md — the detailed
2a–2e procedure: driver dispatch, screenshot sharing, classification signals,
same-file rule, the review queue, the wave-gate, fix-forward discipline. Read in
Step 2.
references/waves.md — the wave model: what makes a wave
coherent/mergeable (no orphan files), dispatch-at-wave-start, the orchestrator-held
review queue, and the Layer-1-vs-Agent-Teams routing choice. Read in Step 2 when
running a multi-task wave.
references/review-gate.md — the review gate in
full: when it runs, thin dispatch, category routing, the cross-lane security-auditor
pull, the final-gate review-panel trio (sr/sa/qa), the severity policy, the
tech-debt ledger. Read when running the quality gate.
../review-panel/references/classification.md
— the diff→reviewer signal tables (shared with the review-panel skill); the
security-lane section is what drives the wave-gate's conditional security-auditor
pull. Read when a wave touches auth/crypto/injection/redirect/deps.
references/visual-review.md — the visual gate in
full: the defect taxonomy, the consume-vs-render rule, reference comparison,
per-mode capture behaviour, and the Urgent-without-reference bound. Read when
running the visual gate.
references/feature-flags.md — the feature-flag
gate: both-path proof, the flag-off no-leak check. Read in Step 1 when a flag
system exists.
../spec-driven/references/loop-integration.md
— the full spec-lifecycle procedure: attach, vault sync, task tracking, the drift
gate, ripple, closing, MR re-entry. Read when attached to an OpenSpec change.
1---2name: frontend-delivery-loop3description: Continuous frontend delivery loop — cycles subagents through test → diagnose → fix → review → re-test until E2E and review are clean. Use to develop, fix, harden, or finish a frontend feature/epic verified by tests.4---56# Frontend Delivery Loop78## What this is910You are the **orchestrator** of a small frontend delivery team. You don't write11product code or tests yourself — you **drive a loop** between specialist subagents12and hold the line on the definition of done. The loop is the whole point: a single13pass of "run the tests" or "fix this bug" is not this skill.1415```mermaid16flowchart TD17 S([Scope + bar]) --> D{"Step 1: detect<br/>behaviour driver"}18 D -->|Cypress| DRV19 D -->|"Playwright (spec or live)"| DRV20 D -->|"none (no runner, no MCP)"| RV21 DRV["Behaviour driver<br/>cypress-expert / playwright-expert<br/>(run suite · or drive live)"] --> G{"green +<br/>no regressions?"}22 G -->|no| C{"classify the finding"}23 G -->|yes| GATE["Wave-gate (once per wave)<br/>repo-wide Bash batch + frontend-reviewer (code)<br/>+ ui-ux-reviewer (visual) over the whole wave diff<br/>+ driver re-verify"]24 GATE --> Q{"Urgent clear &<br/>suggestions resolved or tech-debt?"}25 Q -->|no| C26 Q -->|"yes (all waves green)"| FINAL["Final gate (once, after ALL waves)<br/>final frontend-reviewer + ui-ux-reviewer<br/>+ review-panel trio: sr · sa · qa<br/>over the whole in-scope diff"]27 FINAL --> DONE([DONE → ship-ready · stop before PR/MR])28 C -->|"UI/UX & a11y"| UI[ui-ux-specialist]29 C -->|"logic / quality / perf"| FE[frontend-engineer]30 C -->|"test defect"| DRV31 UI --> RE["re-verify<br/>(re-run suite · or re-drive live)"]32 FE --> RE33 RE --> G34```3536**Three diagnostic sources feed one set of fixers:** the behaviour driver finds37**behaviour** failures; `frontend-reviewer` finds **code-quality** problems the38tests can't see; `ui-ux-reviewer` finds **visual-fidelity** defects in the rendered39pixels neither of the other two can perceive. All three only diagnose — every edit40is a fixer's job. Keep cycling until **every gate passes, the reviews are clean, no41regressions exist, and the work is production-ready.**4243## Why you run inline (and stay the conductor)4445Run this skill **inline in the main thread** — never bury it inside a dispatched46agent. You need `Agent`/`SendMessage` to drive the specialists and47`AskUserQuestion` to reach the human on judgment calls; a nested subagent can't do48those. Your job is coordination and verification: collect each subagent's output,49decide the next move, re-engage the right worker. The specialists own the edits;50you own the loop and the bar.5152## The subagents5354Dispatch each with the `Agent` tool (`subagent_type: <name>`). Each carries its own55persona and preloaded skills, so your dispatch prompt stays **thin** — give it the56scope, the relevant findings, and the task; don't re-teach it its craft.5758Your team has a **behaviour driver** (`cypress-expert` **or** `playwright-expert`,59picked by detection — see [Modes](#modes-at-a-glance)), a **code-quality reviewer**60(`frontend-reviewer`), a **visual-fidelity reviewer** (`ui-ux-reviewer`), and two61**fixers** (`ui-ux-specialist`, `frontend-engineer`). The driver and the two62reviewers are the **three diagnose-only authorities** — they never edit; the fixers63never decide what's wrong on their own — you route between them.6465**Who authors which tests.** The **driver owns the E2E layer** — it authors/runs the66E2E suite (Cypress/Playwright-spec) or drives the app live, and root-causes failures.67The **two fixers author the unit/component tests for their own layer**: `frontend-engineer`68the unit tests for the hooks/state/data logic, `ui-ux-specialist` the component tests69for the presentation — the smallest credible mix for what each changed. The driver does70**not** write those unit/component tests; when an in-scope behaviour has no covering test71at the right layer, the driver names the gap and you route the unit/component authoring72to the owning engineer (an **E2E** gap the driver fills itself).7374| Subagent | Owns | Route to it when… |75| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |76| `cypress-expert` | The Cypress E2E/component test suite — authoring, fixing, running, root-causing failures. | The **behaviour driver in Cypress mode**: verifies the work, catches regressions, tells you _what_ broke and whether it's a product bug or test defect. |77| `playwright-expert` | Playwright E2E — authoring/running specs when Playwright is installed, **or** exercising the running app **live** via the Playwright MCP (`webapp-testing`) when it isn't. | The **behaviour driver when there's no Cypress** (Playwright-spec or Playwright-live mode): same role, via specs or a live browser. |78| `frontend-reviewer` | **Review-only** static review of the changed `.tsx/.ts/.js` — code quality, performance, UI/UX & a11y, business logic; findings tagged **Urgent** vs **suggestion**. | The **quality gate** (every mode): code is behaviourally green but needs the review tests can't give. Never edits — findings route to the fixers. |79| `ui-ux-reviewer` | **Review-only** visual-fidelity judgment of the **rendered** result — alignment, spacing, table/data-density, overflow, hierarchy, responsive, design-reference drift, rendered a11y signals; findings tagged **Urgent** vs **suggestion**, each citing a screenshot path. | The **visual gate** (after behaviour-green): consumes the driver's screenshots (renders its own only on a missing state or in review-only). Never edits — findings route to the fixers (chiefly `ui-ux-specialist`). |80| `ui-ux-specialist` | The presentational layer — markup, styling, layout, a11y, design fidelity, states. | The defect/finding is **UI/UX or a11y**: wrong layout, broken responsive/visual state, missing loading/error/empty state, a11y, design mismatch. |81| `frontend-engineer` | The logic layer — hooks, state machines, data fetching, FE↔BE integration. | The defect/finding is **logic, code-quality, or performance**: wrong state, bad effect/deps, broken data flow, integration, perf, `any`, dead code. |8283**Three authorities, kept distinct.** The behaviour driver owns **pass/fail** — you84never declare green from your own reading; green is what it reports after a clean85run (or, live mode, a clean re-drive). `frontend-reviewer` owns the **code-quality86verdict**; `ui-ux-reviewer` owns the **visual-fidelity verdict**. Never wave through87any of them on your own judgment; none ever edits.8889## Step 1 — Establish the scope and the bar9091State these back to the user up front, so the loop has clear edges:92931. **What's in scope.** The files/components/flows the user means. For "the changes94 on this branch", derive from `git diff <base>...HEAD` (infer `main`/`master`/95 `develop`, or ask). For an epic folder/ticket, read it for intended behaviour.96 Don't widen beyond what they asked.972. **The quality gates.** Read `package.json` scripts (and CI config) for lint,98 typecheck, unit, and E2E runners — name the commands the loop must turn green.993. **The mode / behaviour driver** — detect deterministically, **first match wins**100 (signals + per-mode behaviour in [`references/modes.md`](references/modes.md)):101 **Cypress installed** → Cypress mode (`cypress-expert`) · else **Playwright102 installed** → Playwright-spec mode (`playwright-expert` writes/runs specs) · else103 **Playwright MCP up** → Playwright-live mode (`playwright-expert` drives live, no104 committed specs) · else → review-only mode (`frontend-reviewer` is sole driver).1054. **Feature flags?** See [Feature flags](#feature-flags). Skip if the repo has none.1065. **The OpenSpec change driving the work — always.** Every run is anchored to a change;107 the spec is the contract the loop delivers against, so there is always one to read,108 edit, or create. Resolve the OpenSpec root via `SPEC_VAULT_PATH` (fallback109 `./openspec`), sync the vault (offline-first — pull only when an upstream exists; see110 the reference), then establish the change in order: **named** (the user gives a111 slug/folder) → **detected** (no slug, so scan `<root>/changes/*/` for an open change112 whose proposal/specs match the scope; ambiguous → `AskUserQuestion`) → **created**113 (nothing matches, so author it now via the `spec-driven` flow before touching code).114 Read `proposal.md` + `specs/` + `design.frontend.md` + `tasks.frontend.md` as the115 scope's source of truth; if this side's design/tasks don't exist yet (including a116 change you just created), they're authored via the `spec-driven` flow before you touch117 code — never by you hand-authoring the design, never by skipping straight to the fixers.118 This is not optional, but the cost is proportional: a one-line fix gets a small change119 folder, not a ceremony. See120 [The spec lifecycle](#the-spec-lifecycle-when-an-openspec-change-drives-the-work).1211226. **The working-tree baseline.** Before any agent edits, capture the target repo's123 pre-existing state: run `git status --porcelain` (plus `git stash list` when124 non-empty) and record the output verbatim in the plan as the **baseline**.125 Anything already modified, deleted, or untracked at this moment is the **user's126 pre-existing state — not part of the feature diff**: it is never attributed to a127 fixer, never flagged as scope creep, and never "fixed" (reverted, restored, or128 committed) by the loop. Every reviewer dispatch carries this baseline (the129 review-gate reference tells you how to frame it).1307. **The commit policy for the target repo — always commit-per-wave.** After each131 wave/fix round passes its gates, **commit the target repo** (cleaner per-review132 diffs and bisect). **Stage by explicit file list, never `git add -A`**, so the133 Step-1.6 baseline state (pre-existing modified/untracked files) is never swept134 into a wave commit — only the wave's own diff is staged. Let every dispatch135 inherit this; don't re-state "do NOT commit" in each prompt. This governs the136 **target repo** only — vault writes are always committed immediately (separate137 hard rule), and the loop still never opens, pushes, or merges a PR/MR.1381398. **The visual reference + capture contract.** Collect any available **reference140 image** for the in-scope screens — priority **Figma → an image in the OpenSpec141 change folder / ticket → a screenshot pasted in chat** — and pass its path into142 the `ui-ux-reviewer` dispatch; when none exists the reviewer judges by heuristics.143 In **Cypress/Playwright-spec** modes the driver deliberately captures the relevant144 states at **desktop + mobile** and hands over the paths, so the visual reviewer145 consumes them; in **Playwright-live / review-only** there are no committed shots,146 so the visual reviewer renders its own (full per-mode capture matrix in147 [`references/visual-review.md`](references/visual-review.md)).1489. **The render-target probe — before you ever claim a screen "can't be rendered."**149 The visual gate judges *rendered pixels*; a reduced-coverage fallback (heuristics150 on a screen you never saw) is honest only when rendering is genuinely151 **impossible**, never merely **inconvenient**. So before the loop may downgrade the152 visual gate, probe for a way to render the in-scope screens and **record the153 result in the plan**:154 - a **dev/preview/app server** — a `dev`|`preview`|`start` script in `package.json`,155 a documented base URL, or an already-listening port (`lsof -i` / `curl -sf` the156 common dev ports);157 - a reachable **API/backend** the screen needs to populate — probe the documented158 port/health route (a backend already running on e.g. `:8083` is found by159 `lsof -i :8083` or a `curl` of its health path) — and remember the driver can160 **mock/intercept** the data even when no real backend answers, so a missing161 backend is not a missing render;162 - the **Playwright MCP / `webapp-testing`** toolkit being available this session.163 Only if **every** probe fails — no server, no reachable *or mockable* API, no164 browser MCP — may the visual gate fall back to reduced coverage, and then it must165 log *what it probed and what failed*. "Impractical", "out of scope", or "needs166 auth / a feature flag / backend data to populate" are **not** probe failures —167 they are the driver's to solve (seed the flag in `localStorage`, stub auth,168 intercept the data route), never a licence to skip the render.169170Write the scope, gates, **mode**, flag posture, **baseline**, **commit policy**, and171the **render-target probe result** into a plan with `TaskCreate` (advance statuses172with `TaskUpdate` as the loop runs) so progress is visible and nothing silently drops.173174## Modes at a glance175176`frontend-reviewer` (code quality) **and `ui-ux-reviewer` (visual fidelity)** are the177diagnose-only gates in **every** mode; only the behaviour driver — and how "re-verify"178/ the screenshot source works — changes. State the mode + driver up front. Per-mode179visual-capture behaviour: [`references/visual-review.md`](references/visual-review.md).180**Full per-mode behaviour: [`references/modes.md`](references/modes.md).**181182| Mode | When (first match wins) | Behaviour driver | "Re-verify" after a fix | In-repo specs? |183| ------------------- | --------------------------------------------- | --------------------------------- | --------------------------- | -------------- |184| **Cypress** | Cypress installed | `cypress-expert` | re-run the Cypress suite | yes (authored) |185| **Playwright-spec** | no Cypress; Playwright installed | `playwright-expert` | re-run the Playwright suite | yes (authored) |186| **Playwright-live** | no Cypress, no Playwright; Playwright MCP up | `playwright-expert` (live via MCP)| re-drive the flow live | **no** (runtime only) |187| **review-only** | none of the above | `frontend-reviewer` | re-review the diff | no |188189In **Playwright-live / review-only** behaviour coverage is reduced — say so190plainly and offer to add a runner (don't scaffold one unasked).191192## Step 2 — Run the loop193194Mirrors the mermaid above. **Full procedure — driver dispatch, screenshot sharing,195classification signals, same-file rule, fix-forward discipline — in196[`references/loop-procedure.md`](references/loop-procedure.md).** In brief:197198- **2c0 — form the wave** (multi-task work). A **wave** is a coherent,199 independently-mergeable slice of the architect manifest — every file it touches is200 used within it, every promised export consumed, the repo green at its boundary (**no201 orphan files**). Dispatch the wave's engineers at wave start with their `files_owned`202 allowlists + injected promised contracts — **in parallel where `deps` allow, in deps203 order where a task consumes an earlier task's promised export**. **A strict linear204 deps chain (1.1→1.2→1.3) is ONE wave with ONE wave-gate — dispatch its tasks in deps205 order INSIDE that single wave; splitting a deps chain into a wave-per-task, even when206 the files are disjoint, re-introduces the per-fix gating this model removes and is a207 defect.** Routing is **Layer 1 (orchestrator-mediated)** — dispatch each persona with208 the `Agent` tool and route every message through you, the orchestrator. **Full wave209 model: [`references/waves.md`](references/waves.md).**210- **2a — dispatch the behaviour driver** to cover the in-scope behaviour + catch211 regressions, capture screenshots at key states, and report per failure: root212 cause, product-bug vs test-defect, screenshot path(s). (CWV baseline only when the213 change is perf-scoped — see [`references/loop-procedure.md`](references/loop-procedure.md).)214- **2b — green & no regressions?** → run the wave-gate (2e) / the final gates, then Step 3.215- **2c — failures → classify & route** each product bug to its fixer (UI/UX & a11y216 → `ui-ux-specialist`; logic/quality/perf → `frontend-engineer`; test defects → back217 to the driver). Logic before presentation when one defect has both causes. Routing218 flows **through you, the orchestrator (the message bus)** — reviewers can't stand by219 idle and engineers can't message each other; completed diffs **accumulate** into the220 wave's cumulative diff (tracked in your `TaskUpdate` plan). The static review is221 **not** run per fix — both reviewers batch **once per wave at the wave-gate (2e)**; the222 queue's job is to order the wave-gate's findings routed back to busy engineers223 (disjoint fixes parallel, overlapping serialize). Group by file (no two concurrent224 edits to one file). Pause on judgment calls with `AskUserQuestion`. Pass the225 screenshot path(s) into the fix prompt.226- **2d — fold the fix back** — re-engage the driver to re-verify (re-run suite / re-drive227 live); **do NOT run `frontend-reviewer` + `ui-ux-reviewer` on this fix by default** —228 the static review is batched **once per wave at the 2e wave-gate**. A per-fix pass is229 allowed **only as an optional hotspot** on a high-risk diff (a code hotspot on a230 security/perf-sensitive or cross-cutting change; a visual hotspot on a **high-risk**231 presentational change — a cross-cutting design-token or layout-system edit, **not**232 every styling fix, which rides to the wave-gate). A fix isn't real until the driver233 re-verifies it passes AND nothing regressed.234- **2e — wave-gate** — once **every** engineer in the wave has returned and **no edit235 dispatch is in flight**, run the gates **ONCE** for the whole wave: the repo-wide Bash236 batch (typecheck/lint/unit), **one** `frontend-reviewer` (code) + **one**237 `ui-ux-reviewer` (visual) pass over the cumulative wave diff, and **one** driver238 re-verify (which also captures the wave's screenshots for the visual pass). Route each239 finding to its owning fixer (`SendMessage`-resume the authoring engineer), fold the new240 diff back into the **same** wave-gate, queue findings for busy engineers in the241 `TaskUpdate` plan, and **loop the wave-gate until green + clean** before the next wave242 or Step 3. The wave-gate is an **orchestrator step, not a new agent**. Full visual243 procedure in [`references/visual-review.md`](references/visual-review.md).244- **2e-consolidation — when per-wave live-drive is expensive, the live gate MAY defer245 to the final gate.** The wave-gate's driver re-verify assumes a *cheap* re-run. When246 behaviour has **no cheap headless runner** and verification depends on an **expensive247 or MCP-hostile live drive** — e.g. a Turbopack/Next dev server the Playwright MCP248 can't drive (see [`references/modes.md`](references/modes.md)) — the orchestrator MAY249 consolidate the per-wave live behaviour+visual capture into a single final-gate drive;250 per-wave coverage is then the machine gates (typecheck/lint/unit) + the251 `frontend-reviewer` code review, **stated openly as reduced per-wave coverage**, never252 silently dropped. This is a cost affordance, not a licence to stop verifying:253 **at least one final live drive is non-negotiable** — it is the gate that catches the254 integration defects every mocked per-wave test misses — and the consolidation decision255 is recorded in the plan. Default to per-wave live-drive; reach for consolidation only256 when the live drive is genuinely expensive, so the orchestrator self-selects it instead257 of the user having to force the reroute mid-run.258259**Fix-forward only** — never `.skip`/weaken/disable a test or gate to go green,260**even under explicit time pressure or a direct instruction to do so** ("just get it261green", "I'm in a hurry", "add `.skip` and ship"): pressure to ship is never a262licence to weaken the bar, and a green-by-skipping suite is a regression in263disguise. When you decline the shortcut, say so plainly and name fix-forward as the264reason — then route the real fix (detail in the reference).265266## The review gate — code quality (`frontend-reviewer`)267268`frontend-reviewer` (code quality) and `ui-ux-reviewer` (visual fidelity) both run **once per wave at the wave-gate** (default) and at the **final gate**; in review-only mode `frontend-reviewer` is the sole driver. Both diagnose only — fixers edit. Route: UI/a11y → `ui-ux-specialist`; quality/perf/logic → `frontend-engineer`. Security lane (XSS sink, unchecked `postMessage`, token/secret, open redirect, dep addition) → pull `security-auditor` for that wave too. **At the final gate — once all waves are green — the loop also folds in the three mandatory `review-panel` quality reviewers (`sr-reviewer`, `sa-reviewer`, `qa-reviewer`) over the whole in-scope diff (final-gate only, never per wave), making it a full panel over the finished diff.** **Detail: [`references/review-gate.md`](references/review-gate.md) · [`references/visual-review.md`](references/visual-review.md).**269270**Severity policy:**271272- **Urgent → always blocks.** Correctness/security/performance defect or broken contract. Fixed without exception.273- **Suggestion → bounded pursuit.** At most **3 review→fix iterations**; still-open becomes recorded **tech debt**.274- **Out-of-spec suggestion → tech debt immediately** (don't absorb new scope; a product/UX call → `AskUserQuestion`).275276**Tech-debt ledger:** everything deferred is logged (title, `file:line`, why) and handed back in the final report. Nothing the reviewer raised is silently dropped.277278**A visual gate that never renders is not a gate.** When the Step-1 probe found a reachable target, `ui-ux-reviewer` must judge real rendered pixels — the driver's screenshots in spec mode, its own capture otherwise. CSS/JSX diff against a reference PNG is not a visual review. `[needs render]` findings route to a render, not the ledger.279280**Tech-debt may not absorb unrun verification.** The ledger is for *bounded judgment-call polish deferred by choice* — never for *verification the loop declined to run*. "Unverified because I didn't render it / didn't check" is not deferrable risk; it is unfinished work. A finding tagged `[needs render]` (or any "couldn't assess" placeholder) routes to a render, never the ledger.281282## The spec lifecycle (when an OpenSpec change drives the work)283284Full procedure: [`../spec-driven/references/loop-integration.md`](../spec-driven/references/loop-integration.md). Shape when attached to a change (Step 1.5):285286- **Sync the vault** at every cycle boundary — pull if upstream exists; diff against last-known commit if local-only. A human edit in Obsidian is a command; it can reopen checked tasks.287- **You own `tasks.frontend.md`.** Check `- [ ]` → `- [x]` when work passes gates (never on a fixer's claim). Commit the vault immediately — uncommitted = invisible to other sessions.288- **Drift gate — two tiers.** Product/UX/contract drift (observable by a user, consumer, or the other side) → `AskUserQuestion`; approved → amend vault and commit; rejected → defect to fix. Technical reconciliations → amend now with `auto-approved technical reconciliation` marker, present at next checkpoint. Every amendment is a full **amendment sweep** (grep the amended term across every artifact — see the reference). Approved contract amendment that affects a closed backend side → append Ripple to `tasks.backend.md` and flip back to `in-progress`.289- **Closing:** tasks done → CHANGELOG pointer → `status: in-review` when both sides → archive only post-merge via `/spec-driven`.290- **MR re-entry:** fetch comments, triage (cosmetic → fixers · behaviour/contract → amend vault first · disagreement → ask), tracked as `## R<n>` in `tasks.frontend.md`.291292Every run attaches to a change (named, detected, or created) — this lifecycle always applies; there is no "plain scope" path that skips the spec.293294## Feature flags295296When the repo has a feature-flag system, treat it as a gate: new behaviour sits297**behind a flag**, and the **flag-off path** must prove production behaviour is298unchanged. If the repo has no flag system, say so and skip. **Detail:299[`references/feature-flags.md`](references/feature-flags.md).**300301## Step 3 — Definition of done (what ends the loop)302303The loop ends **only** when all hold — confirm each explicitly:3043051. **Every quality gate is green** — lint, typecheck, unit, and the behaviour gate,306 run with the project's real commands, reported clean by a fresh driver run.3072. **No regressions** — previously-passing flows still pass; the driver confirms on308 the full suite/flows, not just what it touched.3093. **Feature-flag gate satisfied** (if applicable) — new behaviour flagged, flag-off310 path proven unchanged.3114. **Scope delivered** — the asked-for behaviour is implemented and exercised.3125. **Code-quality review gate clean** — a final `frontend-reviewer` pass **plus the313 three mandatory `review-panel` quality reviewers (`sr-reviewer`, `sa-reviewer`,314 `qa-reviewer`), dispatched once over the whole in-scope diff at the final gate,** have315 no unresolved Urgent findings; every suggestion is resolved or in the tech-debt316 ledger.3176. **Spec lifecycle closed out** (every run is attached to a change) —318 every non-`(HUMAN)` task in `tasks.frontend.md` checked (open `(HUMAN)` tasks319 reported with their owner — they gate `in-review`, never faked closed), no320 unresolved drift at the gate, the CHANGELOG pointer written, and the status321 advanced per the closing protocol (`in-review` when both sides, including the322 human-gated boxes, are complete; archive offered only post-merge).3237. **Visual-review gate clean — on rendered pixels.** A final `ui-ux-reviewer` pass324 has no unresolved Urgent findings; every suggestion is resolved or in the tech-debt325 ledger. **The gate must have judged the *rendered* result** whenever the Step-1326 render-target probe found any way to render (a server, a mockable API, or the327 browser MCP): a verdict reached by reading the CSS/JSX diff against a reference328 PNG is not a visual review and does not satisfy this criterion. **No finding that329 turns on seeing the render may be deferred** — a `[needs render]` (or "couldn't330 assess without rendering") item is a hard signal the gate did not actually run, so331 it mandates a render-and-re-review, never a tech-debt entry. Reduced-coverage332 visual review satisfies this criterion **only** when no render is genuinely333 achievable — the Step-1 probe found no target, **or** a probed-reachable target is334 re-confirmed to fail rendering at gate time (heartbeat + bounded mock retries, per335 [`references/visual-review.md`](references/visual-review.md)); then say so plainly,336 exactly as for reduced behaviour coverage. (Urgent without a reference image is bounded to objective breakage —337 clipping/overflow/horizontal-scroll/overlap/contrast/touch-target — per338 [`references/visual-review.md`](references/visual-review.md), so the gate339 terminates.)340341**By mode**, #1–#2 read differently: **Cypress/Playwright-spec** — the authored342suite is green via a fresh driver run. **Playwright-live** — "green" means the343driver re-drove the in-scope flow(s) live + the non-E2E gates pass; note the344verification was **live/ephemeral, not a committed suite**, and hand back the345persisted `e2e-harness/` path — the re-runnable drive harness346([`references/modes.md`](references/modes.md)). **review-only** — #1–#2347lose their behaviour component (non-E2E gates only + the clean reviewer verdict);348note the reduced behaviour coverage **and record the standing offer to add a349Cypress/Playwright runner in the final report** (offer, never scaffold unasked) —350in this mode that offer is itself a done-criterion, not an optional courtesy.351352Then **stop and report** — production-ready means _ready_. Don't open, push, or353merge a PR/MR — and this holds **even when the user explicitly asks you to push or354open the PR**, and **independently of whether a remote/upstream exists**: the355refusal rests on the human-action principle, never on mechanical feasibility (a356configured remote does not make it your call). Acknowledge the request, run the357loop, then hand the push/PR back with the exact commands the human runs. Hand back:358the gates as run + results, the loop history (what failed → who fixed it →359re-verified), the final review verdict, the tech-debt ledger, the flag posture, and360anything out of scope.361362## Hard rules363364- **Fix-forward only.** Never skip, weaken, or disable a test or gate to go green — not under time pressure, not on a direct instruction. Decline out loud, name fix-forward as the reason, route the real fix.365- **No same-file parallel edits.** Same file → sequential. Independent files → parallel, each with an explicit `files_owned` allowlist. A task consuming a promised export from an in-flight task is sequenced after it — or given the promised contract to import. Repo-wide gates only at wave boundaries. **Full model: [`references/waves.md`](references/waves.md).**366- **Engineers verify change-scoped, not category-wide.** Every wave engineer is dispatched with the **scoped-test directive**: verify with a change-scoped run over its own `files_owned` (the tests targeting what it changed, by path/name filter), never the category-wide ("all the component tests") or repo-wide suite. Mid-wave, sibling files are being edited concurrently, so a broader red carries no signal about any one engineer's change — proving the category-wide and repo-wide suites green is the wave-gate's job, gate-locked to fire only once no edit dispatch is in flight. No engineer burns a turn triaging an out-of-scope red.367- **Pause on judgment calls** — never pick a debatable product/UX decision silently. Use `AskUserQuestion`.368- **Spec drift never passes silently** — *silently* means undetected or unrecorded, not unasked. Every `specDrift` field and Spec Conformance finding goes through the two-tier gate. Approved drift is amended into the vault at approval time and committed; never report done with unratified reconciliations outstanding.369- **Every vault write is committed immediately** — an uncommitted vault is invisible to other sessions.370- **Stop before the PR/MR** — even when the user explicitly asks, even when a remote exists. Hand back the exact commands the human runs. Stay in scope; surface scope creep as a follow-up. Archive is gated on the human's merge — offer it, never run it preemptively.371372## References373374Read these when you need the depth — the body above is enough to run the loop:375376- [`references/modes.md`](references/modes.md) — the four modes in full: detection377 signals, per-mode driver behaviour, the live/review-only honesty rules. Read in378 **Step 1** when the mode isn't obvious.379- [`references/loop-procedure.md`](references/loop-procedure.md) — the detailed380 2a–2e procedure: driver dispatch, screenshot sharing, classification signals,381 same-file rule, the review queue, the wave-gate, fix-forward discipline. Read in382 **Step 2**.383- [`references/waves.md`](references/waves.md) — the wave model: what makes a wave384 coherent/mergeable (no orphan files), dispatch-at-wave-start, the orchestrator-held385 review queue, and the Layer-1-vs-Agent-Teams routing choice. Read in **Step 2** when386 running a multi-task wave.387- [`references/review-gate.md`](references/review-gate.md) — the review gate in388 full: when it runs, thin dispatch, category routing, the **cross-lane `security-auditor`389 pull**, the **final-gate `review-panel` trio (`sr`/`sa`/`qa`)**, the severity policy, the390 tech-debt ledger. Read when running the **quality gate**.391- [`../review-panel/references/classification.md`](../review-panel/references/classification.md)392 — the diff→reviewer signal tables (shared with the `review-panel` skill); the393 **security-lane** section is what drives the wave-gate's conditional `security-auditor`394 pull. Read when a wave touches auth/crypto/injection/redirect/deps.395- [`references/visual-review.md`](references/visual-review.md) — the visual gate in396 full: the defect taxonomy, the consume-vs-render rule, reference comparison,397 per-mode capture behaviour, and the Urgent-without-reference bound. Read when398 running the **visual gate**.399- [`references/feature-flags.md`](references/feature-flags.md) — the feature-flag400 gate: both-path proof, the flag-off no-leak check. Read in **Step 1** when a flag401 system exists.402- [`../spec-driven/references/loop-integration.md`](../spec-driven/references/loop-integration.md)403 — the full spec-lifecycle procedure: attach, vault sync, task tracking, the drift404 gate, ripple, closing, MR re-entry. Read when **attached to an OpenSpec change**.