feat(loop): wire ui_review drive + deck-fit to pass viewport/interaction to the state slug#1331
Conversation
There was a problem hiding this comment.
Pull request overview
This PR completes the Stage-4 ui_review wiring by ensuring production callers pass viewport and interactionState through to captureNamedUiState, so named-state artifact slugs (<state>-<viewport>-<interaction>) uniquely and correctly represent responsive/stateful renders outside of the test harness.
Changes:
- Extend the ui_review drive-step config schema to allow optional
viewport {width,height}andinteractionState. - Update the live UI-review drive to apply a declared viewport and forward
viewport/interactionStateintocaptureNamedUiState. - Remove the deck-fit “(mobile 390)” state-name hack and instead pass
viewport: MOBILE; update the recipe contract docs and schema-leaf count test.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
test/playwright/harness/deck-fit-harness.mjs |
Passes viewport: MOBILE so mobile/desktop captures disambiguate via slug rather than state-name text. |
test/loop/ui-review-drive.test.mjs |
Adds regression coverage for viewport/interactionState slugging in the production drive wiring. |
test/docs/ui-review-recipe-doc.test.mjs |
Updates the expected uiReview/worktree schema leaf count to reflect new keys. |
scripts/loop/ui-review-drive.mjs |
Applies step viewport before actions and forwards viewport/interactionState to the named-state capture seam. |
packages/core/test/config.test.mjs |
Adds schema validation tests for the new step leaves and fail-closed invalid values. |
packages/core/src/config/config.mjs |
Extends UiReviewFlowStepConfig with optional viewport and interactionState. |
docs/ui-review-recipe-contract.md |
Documents the new schema leaves and includes them in the canonical key list. |
Gate review:
|
Wire production callers to the Stage 4 slug discriminator. A drive flow step may now declare a viewport (resized before the step) and an interactionState; both are passed to captureNamedUiState so responsive/stateful renders land in distinct slugged directories instead of colliding. The deck-fit harness drops its "(mobile 390)" name hack and passes viewport: MOBILE, letting the slug carry the viewport. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177JAWNT67FY6d48qQJeDwo
…oc sticky viewport The source-pin regex only matched backtick template literals, so a name-hack reintroduced via normal quotes or concatenation would evade it; anchor on the stateName: assignment and forbid a viewport word on that line in any string form. Also document that a step viewport is sticky — it persists to later steps and an omitted viewport inherits the last set size rather than resetting. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177JAWNT67FY6d48qQJeDwo
Returning a later step to a different or the original size requires giving that step an explicit viewport with the exact dimensions; omitting it inherits the last set size. Removes the ambiguous 'return to the default dimensions' wording. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177JAWNT67FY6d48qQJeDwo
Gate review:
|
Summary
Wires the production ui_review callers to the Stage-4 named-state slug seam (
<state>-<viewport>-<interaction>). Stage 4 baked viewport + interaction-state into the slug and added a fail-closed duplicate-slug guard, but no production caller passed viewport/interaction — the discriminator was exercised only by the harness/unit contract. This ships the callers; it does not touch the seam or the slug format.Scope and context
packages/core/src/config/config.mjs: the drive-step schema gains optionalviewport({ width, height }) andinteractionState(route-named enum) leaves, so a flow can declare a responsive/stateful render.scripts/loop/ui-review-drive.mjs:makeRunStepresizes the page to a declaredviewport(viasetViewportSize) and passesviewport+interactionStatethrough tocaptureNamedUiState, so responsive/stateful renders become distinct slugged directories in production, not just in tests. Because the Playwright page stays resized across steps, an undeclared later step inherits the last explicitly-set viewport — so its slug faithfully reflects the size the page is actually at rather than mislabelling itdefault;defaultstill applies to a never-resized flow.test/playwright/harness/deck-fit-harness.mjs: drops the(mobile 390)name-hack from the mobile captures and passesviewport: MOBILEinstead — the slug now carries the viewport rather than the state-name string.test/contracts/deck-fit-harness-viewport-contract.test.mjs(new): a source-level pin (runs undertest:assets, so part ofverify) asserting the harness makes ≥2viewport: MOBILEcapture calls and embeds no viewport hack (mobile/390) in anystateName— so a revert of the name-hack removal is caught byverify, not only by a live browser spec.docs/ui-review-recipe-contract.md+test/docs/ui-review-recipe-doc.test.mjs: document the two new schema fields (three leaves:viewport.width,viewport.height,interactionState) and update the leaf count accordingly.Acceptance criteria
Satisfies #1321's ACs (checked off at merge): the live drive passes
viewport/interactionStatetocaptureNamedUiStateso responsive/stateful renders slug distinctly in production; the deck-fit harness passesviewportinstead of encoding it into the state name; harnesses that capture a state at multiple viewports use the slug discriminator, not a name hack;npm run verifygreen with existing named-state artifacts still validating.Non-goals
Auto-enumerating interaction states (the spec/route names them; #1300 non-goal). Changing the slug format or the fail-closed guards shipped in #1300.
Validation
node --test test/loop/ui-review-drive.test.mjs test/loop/ui-smoke-harness.test.mjs— 50 pass (a caller now emits distinct viewport-slugged directories; the deck-fit name hack is gone);packages/core/test/config.test.mjs— 313 pass;test/docs/ui-review-recipe-doc.test.mjs— 2 pass.npm run verify— green (test:assets 191, test:scripts 2700, test:extension 81, test:core, test:docs, test:pack, test:dev-loop 32);generate-claude-assets --checkclean.Closes #1321