31 KiB
Scene 10 Guided Product Moment Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Make Scene 10 behave like a guided product flow: direct links are immediately ready, approval decisions clearly change state, and each beat has one dominant proof surface.
Architecture: Add presentation-owned beat readiness projection, extend useDemoTimeline with replay priming, and recompose Scene 10 around a guided product moment component. Replay priming is a display projection over the canonical recording; it must not fabricate live runtime evidence.
Tech Stack: React 19, TypeScript, Vitest, Testing Library, Playwright smoke, existing useDemoTimeline, existing SchemaApprovalSurface, existing presentation storyboard.
Global Constraints
- Do not add a new transport, store, graph engine, or agent runtime.
- Do not fabricate cancelled replay evidence; the canonical recording only contains the submitted branch.
- Direct hashes for approval, resume, output, and trace must be immediately informative.
- Live execution must still run sequentially when the chat action starts a live workflow.
/consolemust remain unaffected.- Add comments around replay priming because it is presentation projection, not runtime execution.
- Keep styling within existing presentation CSS files and tokens.
File Structure
- Create
web/apps/console/src/presentation/demo-beat-requirements.ts- Maps demo scene beats to the minimum replay event stage needed for direct-link readiness.
- Create
web/apps/console/src/presentation/demo-beat-requirements.test.ts- Pins requirement mapping for Scene 9 and Scene 10 beats.
- Modify
web/apps/console/src/demo/useDemoTimeline.ts- Adds
primeReplayToStage(stage)to project the canonical recording to a display-ready state.
- Adds
- Modify
web/apps/console/src/demo/useDemoTimeline.test.tsx- Tests replay priming and live-mode non-interference.
- Modify
web/apps/console/src/presentation/PresentationRoute.tsx- Calls replay priming when the current route enters a demo beat.
- Modify
web/apps/console/src/presentation/PresentationRoute.test.tsx- Tests immediate readiness and decision consequences from direct hashes.
- Create
web/apps/console/src/presentation/GuidedProductMoment.tsx- Owns Scene 10 hierarchy for approval/resume/output/trace.
- Create
web/apps/console/src/presentation/GuidedProductMoment.test.tsx- Pins hero surface selection and decision copy.
- Modify
web/apps/console/src/presentation/DemoWorkflowScene.tsx- Delegates Scene 10 beats to
GuidedProductMoment.
- Delegates Scene 10 beats to
- Modify
web/apps/console/src/presentation/styles/demo-workflow.css- Adds guided moment layout styles.
- Modify
docs/current_roadmap.md- Marks this plan active/completed at the right time.
Task 1: Add beat readiness requirements
Files:
- Create:
web/apps/console/src/presentation/demo-beat-requirements.ts - Create:
web/apps/console/src/presentation/demo-beat-requirements.test.ts
Interfaces:
-
Produces:
import type { DemoEvent } from "../demo/timeline/models.js"; export type DemoBeatRequirement = { readonly requiredStage: DemoEvent["stage"] | null; readonly reason: string; }; export const requirementForDemoBeat = ( sceneId: string, beatId: string, ): DemoBeatRequirement; -
Consumes:
DemoEvent["stage"]from existing replay model.
-
Step 1: Write failing model tests
Create
web/apps/console/src/presentation/demo-beat-requirements.test.ts:import { describe, expect, it } from "vitest"; import { requirementForDemoBeat } from "./demo-beat-requirements.js"; describe("requirementForDemoBeat", () => { it.each([ ["workflow-demo", "operation", "run_start"], ["workflow-demo", "graph", "run_start"], ["workflow-demo", "interrupt", "interrupt"], ["interrupt-evidence", "approval", "interrupt"], ["interrupt-evidence", "resume", "run_resume"], ["interrupt-evidence", "output", "run_resume"], ["interrupt-evidence", "trace", "trace_read"], ] as const)("maps %s/%s to %s", (sceneId, beatId, stage) => { expect(requirementForDemoBeat(sceneId, beatId).requiredStage).toBe(stage); }); it("returns no required stage for non-demo beats", () => { expect(requirementForDemoBeat("thesis", "title")).toEqual({ requiredStage: null, reason: "No demo replay state needed.", }); }); }); -
Step 2: Run test to verify it fails
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/demo-beat-requirements.test.tsExpected: FAIL because the file does not exist.
-
Step 3: Implement mapping
Create
web/apps/console/src/presentation/demo-beat-requirements.ts:import type { DemoEvent } from "../demo/timeline/models.js"; export type DemoBeatRequirement = { readonly requiredStage: DemoEvent["stage"] | null; readonly reason: string; }; const requirements: Readonly<Record<string, DemoBeatRequirement>> = { "workflow-demo/operation": { requiredStage: "run_start", reason: "Operation beat needs the recorded run start.", }, "workflow-demo/graph": { requiredStage: "run_start", reason: "Graph beat needs the persisted run id.", }, "workflow-demo/interrupt": { requiredStage: "interrupt", reason: "Interrupt beat needs the typed review payload.", }, "interrupt-evidence/approval": { requiredStage: "interrupt", reason: "Approval beat needs the typed review payload before controls can act.", }, "interrupt-evidence/resume": { requiredStage: "run_resume", reason: "Resume beat needs the recorded resume operation.", }, "interrupt-evidence/output": { requiredStage: "run_resume", reason: "Output beat needs the resumed run output.", }, "interrupt-evidence/trace": { requiredStage: "trace_read", reason: "Trace beat needs the recorded trace read.", }, }; export const requirementForDemoBeat = ( sceneId: string, beatId: string, ): DemoBeatRequirement => requirements[`${sceneId}/${beatId}`] ?? { requiredStage: null, reason: "No demo replay state needed.", }; -
Step 4: Run test to verify it passes
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/demo-beat-requirements.test.tsExpected: PASS.
-
Step 5: Commit
git add web/apps/console/src/presentation/demo-beat-requirements.ts web/apps/console/src/presentation/demo-beat-requirements.test.ts git commit -m "feat: map demo beats to replay readiness"
Task 2: Add replay priming to the demo timeline
Files:
- Modify:
web/apps/console/src/demo/useDemoTimeline.ts - Test:
web/apps/console/src/demo/useDemoTimeline.test.tsx
Interfaces:
-
Consumes:
DemoEvent["stage"]- canonical
DemoRecording
-
Produces:
readonly primeReplayToStage: (stage: DemoEvent["stage"] | null) => void; -
Step 1: Write failing timeline tests
In
web/apps/console/src/demo/useDemoTimeline.test.tsx, add:it("primes replay to the interrupt stage immediately", () => { const { result } = renderHook(() => useDemoTimeline("http://127.0.0.1:8765/rpc", vi.fn())); act(() => result.current.start("replay")); act(() => result.current.primeReplayToStage("interrupt")); expect(result.current.state.mode).toBe("replay"); expect(result.current.state.phase).toBe("review"); expect(result.current.interruptPayload).not.toBeNull(); expect(result.current.state.events[result.current.state.appliedCount - 1]?.stage).toBe("interrupt"); }); it("primes replay to the resume stage with output projection", () => { const { result } = renderHook(() => useDemoTimeline("http://127.0.0.1:8765/rpc", vi.fn())); act(() => result.current.start("replay")); act(() => result.current.primeReplayToStage("run_resume")); expect(result.current.state.mode).toBe("replay"); expect(result.current.state.phase).toBe("paused"); expect(result.current.output?.created_issues).toHaveLength(1); expect(result.current.state.events[result.current.state.appliedCount - 1]?.stage).toBe("run_resume"); }); it("does not prime live timelines", () => { const { result } = renderHook(() => useDemoTimeline("http://127.0.0.1:8765/rpc", vi.fn())); act(() => result.current.start("live")); act(() => result.current.primeReplayToStage("interrupt")); expect(result.current.state.mode).toBe("live"); expect(result.current.state.events).toEqual([]); expect(result.current.interruptPayload).toBeNull(); }); -
Step 2: Run tests to verify they fail
Run:
pnpm --dir web --filter @lda/console test -- src/demo/useDemoTimeline.test.tsxExpected: FAIL because
primeReplayToStagedoes not exist. -
Step 3: Extend controller type
In
web/apps/console/src/demo/useDemoTimeline.ts, import the event type if not already imported as a value type:import type { DemoEvent, DemoRecording } from "./timeline/models.js";Add to
DemoTimelineController:readonly primeReplayToStage: (stage: DemoEvent["stage"] | null) => void; -
Step 4: Add projection helpers
In
useDemoTimeline.ts, add helper functions nearderiveMissingMessage:const phaseForPrimedStage = ( stage: DemoEvent["stage"] | null, ): DemoTimelineState["phase"] => { if (stage === "interrupt") return "review"; if (stage === "completed") return "completed"; if (stage === "failed") return "failed"; return "paused"; }; const appliedCountForStage = ( events: ReadonlyArray<DemoEvent>, stage: DemoEvent["stage"], ): number => { const index = events.findIndex((event) => event.stage === stage); return index === -1 ? 0 : index + 1; };Add a local helper inside the hook after state setters are declared:
const projectTransientState = useCallback((events: ReadonlyArray<DemoEvent>, appliedCount: number) => { const appliedEvents = events.slice(0, appliedCount); const interruptEvent = appliedEvents.find((event) => event.stage === "interrupt"); const resumeEvent = appliedEvents.find((event) => event.stage === "run_resume"); const traceEvent = appliedEvents.find((event) => event.stage === "trace_read"); const completedEvent = appliedEvents.find((event) => event.stage === "completed"); if (interruptEvent?.interpreted) { const interpreted = interruptEvent.interpreted as { payload: LdaReportInterruptPayload }; setInterruptPayload(parseLdaReportInterruptPayload(interpreted.payload)); } if (resumeEvent?.interpreted) { const interpreted = resumeEvent.interpreted as { output: LdaReportOutput }; setOutput(parseLdaReportOutput(interpreted.output)); } if (traceEvent?.interpreted) { setTrace(decodeTracePage(traceEvent.interpreted)); } if (completedEvent?.interpreted) { const interpreted = completedEvent.interpreted as { output: LdaReportOutput; trace: TracePage }; setOutput(parseLdaReportOutput(interpreted.output)); setTrace(decodeTracePage(interpreted.trace)); } }, []);If
parseLdaReportInterruptPayloadis not currently used in this file, use it here instead of a raw cast so malformed replay data fails visibly in tests. -
Step 5: Add reducer action for priming
In
web/apps/console/src/demo/timeline/reducer.ts, add action:| { readonly type: "prime_replay"; readonly events: ReadonlyArray<DemoEvent>; readonly appliedCount: number; readonly phase: DemoTimelinePhase; }Add reducer case:
case "prime_replay": return { mode: "replay", phase: action.phase, events: action.events, appliedCount: action.appliedCount, autoplay: false, error: null, }; -
Step 6: Implement
primeReplayToStageIn
useDemoTimeline.ts, add:const primeReplayToStage = useCallback((stage: DemoEvent["stage"] | null) => { if (stage === null || state.mode !== "replay") return; const recording = activeRecording.current; if (!recording) return; const appliedCount = appliedCountForStage(recording.events, stage); if (appliedCount === 0) return; // Presentation priming projects the reviewed recording to the current beat. // It is not runtime execution and must never run while the live timeline is active. resetRuntime(); projectTransientState(recording.events, appliedCount); dispatch({ type: "prime_replay", events: recording.events, appliedCount, phase: phaseForPrimedStage(stage), }); }, [projectTransientState, resetRuntime, state.mode]);Return it from the controller.
-
Step 7: Run tests
Run:
pnpm --dir web --filter @lda/console test -- src/demo/useDemoTimeline.test.tsxExpected: PASS.
-
Step 8: Commit
git add web/apps/console/src/demo/useDemoTimeline.ts web/apps/console/src/demo/useDemoTimeline.test.tsx web/apps/console/src/demo/timeline/reducer.ts git commit -m "feat: prime replay timeline for demo beats"
Task 3: Prime direct demo routes from PresentationRoute
Files:
- Modify:
web/apps/console/src/presentation/PresentationRoute.tsx - Test:
web/apps/console/src/presentation/PresentationRoute.test.tsx
Interfaces:
-
Consumes:
requirementForDemoBeat(sceneId, beatId)demo.primeReplayToStage(stage)
-
Produces:
- Direct demo hashes are immediately display-ready.
-
Step 1: Add failing direct-route readiness tests
In
web/apps/console/src/presentation/PresentationRoute.test.tsx, add:it("opens approval with enabled approval controls immediately after priming", async () => { window.location.hash = "#scene/interrupt-evidence/approval"; const { PresentationRoute } = await import("./PresentationRoute.js"); render(<PresentationRoute />); expect(await screen.findByRole("button", { name: "Submit" })).toBeEnabled(); expect(screen.getByRole("button", { name: "Cancel" })).toBeEnabled(); }); it("opens resume with resume operation proof immediately after priming", async () => { window.location.hash = "#scene/interrupt-evidence/resume"; const { PresentationRoute } = await import("./PresentationRoute.js"); render(<PresentationRoute />); expect(await screen.findByLabelText("workflow.runs.resume operation")).toBeInTheDocument(); });Do not use arbitrary timeouts in these tests.
findBy*is enough for React effects; the UI should not wait for the replay autoplay timer. -
Step 2: Run tests to verify they fail
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/PresentationRoute.test.tsxExpected: FAIL because direct routes still depend on autoplay progression.
-
Step 3: Wire route priming
In
PresentationRoute.tsx, import:import { requirementForDemoBeat } from "./demo-beat-requirements.js";Add effect after replay start effect:
useEffect(() => { if (state.location.kind !== "main") return; if (demo.state.mode !== "replay") return; const requirement = requirementForDemoBeat( state.location.sceneId, state.location.beatId, ); demo.primeReplayToStage(requirement.requiredStage); }, [demo.state.mode, demo.primeReplayToStage, state.location]);Keep this effect separate from the replay
start("replay")effect. The separation makes the unusual presentation projection easier to reason about. -
Step 4: Run route tests
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/PresentationRoute.test.tsxExpected: PASS.
-
Step 5: Commit
git add web/apps/console/src/presentation/PresentationRoute.tsx web/apps/console/src/presentation/PresentationRoute.test.tsx git commit -m "feat: prime presentation demo routes"
Task 4: Add guided Scene 10 product moment component
Files:
- Create:
web/apps/console/src/presentation/GuidedProductMoment.tsx - Create:
web/apps/console/src/presentation/GuidedProductMoment.test.tsx - Modify:
web/apps/console/src/presentation/DemoWorkflowScene.tsx
Interfaces:
-
Consumes:
SceneBeatDefinitionDemoTimelineControllerInterruptContractPresentation | nullOperationPresentation | nullDemoApprovalActions | undefinedopenEvidence: () => void
-
Produces:
export type GuidedProductMomentProps = { readonly beat: SceneBeatDefinition; readonly demo: DemoTimelineController; readonly contract: InterruptContractPresentation | null; readonly operation: OperationPresentation | null; readonly approvalActions?: DemoApprovalActions | undefined; readonly openEvidence: () => void; }; -
Step 1: Write failing component tests
Create
web/apps/console/src/presentation/GuidedProductMoment.test.tsx:import { cleanup, render, screen } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { loadCanonicalDemoRecording } from "../demo/timeline/replay.js"; import type { DemoTimelineController } from "../demo/useDemoTimeline.js"; import { projectInterruptContract, projectOperationPresentation } from "./demo-workflow-model.js"; import { GuidedProductMoment } from "./GuidedProductMoment.js"; import { findBeat } from "./storyboard.js"; afterEach(() => cleanup()); const recording = loadCanonicalDemoRecording(); const runStart = recording.events.find((event) => event.stage === "run_start")!; const runResume = recording.events.find((event) => event.stage === "run_resume")!; const contract = projectInterruptContract(runStart, runResume); const resumeOperation = projectOperationPresentation(runResume); const demo = { state: { mode: "replay", phase: "review", events: recording.events, appliedCount: 3, autoplay: false, error: null }, inFlight: false, } as DemoTimelineController; describe("GuidedProductMoment", () => { it("makes approval the primary product decision", () => { render( <GuidedProductMoment beat={findBeat("interrupt-evidence", "approval")!} demo={demo} contract={contract} operation={null} approvalActions={{ state: "ready", canSubmit: true, canCancel: true, submit: vi.fn(async () => {}), cancel: vi.fn(async () => {}), }} openEvidence={vi.fn()} />, ); expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "approval"); expect(screen.getByText(/Run is paused/i)).toBeInTheDocument(); expect(screen.getByRole("button", { name: "Submit" })).toBeEnabled(); }); it("makes resume operation proof primary on resume beat", () => { render( <GuidedProductMoment beat={findBeat("interrupt-evidence", "resume")!} demo={demo} contract={contract} operation={resumeOperation} openEvidence={vi.fn()} />, ); expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "resume"); expect(screen.getByLabelText("workflow.runs.resume operation")).toBeInTheDocument(); }); }); -
Step 2: Run tests to verify they fail
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/GuidedProductMoment.test.tsxExpected: FAIL because the component does not exist.
-
Step 3: Implement
GuidedProductMomentCreate
web/apps/console/src/presentation/GuidedProductMoment.tsx:import type { DemoTimelineController } from "../demo/useDemoTimeline.js"; import type { DemoApprovalActions } from "./demo-approval-actions.js"; import type { InterruptContractPresentation, OperationPresentation, } from "./demo-workflow-model.js"; import { DemoOutcomePanel } from "./DemoOutcomePanel.js"; import { InterruptContractPreview } from "./InterruptContractPreview.js"; import { OperationBlock } from "./OperationBlock.js"; import type { SceneBeatDefinition } from "./storyboard.js"; import { demoBeatLensForBeat } from "./demo-workflow-model.js"; export type GuidedProductMomentProps = { readonly beat: SceneBeatDefinition; readonly demo: DemoTimelineController; readonly contract: InterruptContractPresentation | null; readonly operation: OperationPresentation | null; readonly approvalActions?: DemoApprovalActions | undefined; readonly openEvidence: () => void; }; const momentForBeat = (beatId: string): "approval" | "resume" | "output" | "trace" => beatId === "resume" || beatId === "output" || beatId === "trace" ? beatId : "approval"; const statusCopy = ( moment: ReturnType<typeof momentForBeat>, approvalActions?: DemoApprovalActions, ): string => { if (moment !== "approval") return "Same persisted run; inspect the proof below."; if (approvalActions?.state === "submitted") return "Submitted. Same run resumed."; if (approvalActions?.state === "cancelled") { return "Cancelled in presentation replay. No resume evidence is shown."; } return "Run is paused. Submit resumes this same run."; }; export const GuidedProductMoment = ({ beat, demo, contract, operation, approvalActions, openEvidence, }: GuidedProductMomentProps) => { const moment = momentForBeat(beat.id); const lens = demoBeatLensForBeat(beat.id); return ( <section className="guided-product-moment" aria-label="current product moment" data-moment={moment}> <header className="guided-product-moment__header"> <span>{lens.eyebrow}</span> <strong>{lens.headline}</strong> <p>{statusCopy(moment, approvalActions)}</p> </header> <div className="guided-product-moment__primary"> {moment === "approval" && contract ? ( <InterruptContractPreview contract={contract} mode="approval" hero approvalActions={approvalActions} /> ) : null} {moment === "resume" && demo.state.events.find((event) => event.stage === "run_resume") ? ( <OperationBlock event={demo.state.events.find((event) => event.stage === "run_resume")!} variant="expanded" openEvidence={openEvidence} /> ) : null} {(moment === "output" || moment === "trace") ? ( <DemoOutcomePanel beatId={beat.id} lens={lens} operation={operation} contract={contract} /> ) : null} </div> </section> ); };After writing this, remove duplicate
find()calls by assigningrunResumebefore the return if the linter or reviewer flags it. -
Step 4: Delegate Scene 10 beats
In
DemoWorkflowScene.tsx, import:import { GuidedProductMoment } from "./GuidedProductMoment.js";Before the existing return, add:
const isGuidedScene10 = scene.id === "interrupt-evidence";Inside the stage div, render the guided moment for Scene 10 beats:
{isGuidedScene10 ? ( <GuidedProductMoment beat={beat} demo={demo} contract={contract} operation={currentOperation} approvalActions={approvalActions} openEvidence={openEvidence} /> ) : ( <> {/* existing graph/operation/outcome rendering for Scene 9 */} </> )}Keep the continuity rail and discussion links unchanged.
-
Step 5: Run tests
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/GuidedProductMoment.test.tsx src/presentation/DemoWorkflowScene.test.tsxExpected: PASS. Update existing Scene 10 tests only where they assert old layout internals that are intentionally replaced.
-
Step 6: Commit
git add web/apps/console/src/presentation/GuidedProductMoment.tsx web/apps/console/src/presentation/GuidedProductMoment.test.tsx web/apps/console/src/presentation/DemoWorkflowScene.tsx web/apps/console/src/presentation/DemoWorkflowScene.test.tsx git commit -m "feat: stage scene 10 as guided product moment"
Task 5: Style the guided moment and pin visual hierarchy
Files:
- Modify:
web/apps/console/src/presentation/styles/demo-workflow.css - Test:
web/apps/console/src/presentation/GuidedProductMoment.test.tsx
Interfaces:
-
Consumes:
.guided-product-momentdata-moment="approval" | "resume" | "output" | "trace"
-
Produces:
- One dominant primary surface per beat.
-
Step 1: Add class contract tests
In
GuidedProductMoment.test.tsx, add:it("marks the primary surface for visual hierarchy", () => { render( <GuidedProductMoment beat={findBeat("interrupt-evidence", "approval")!} demo={demo} contract={contract} operation={null} approvalActions={{ state: "ready", canSubmit: true, canCancel: true, submit: vi.fn(async () => {}), cancel: vi.fn(async () => {}), }} openEvidence={vi.fn()} />, ); expect(screen.getByRole("region", { name: /current product moment/i })).toHaveClass("guided-product-moment"); expect(screen.getByLabelText("typed interrupt contract")).toHaveAttribute("data-hero", "true"); }); -
Step 2: Run test
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/GuidedProductMoment.test.tsxExpected: PASS if Task 4 already produced the class and hero attribute.
-
Step 3: Add CSS
In
web/apps/console/src/presentation/styles/demo-workflow.css, add:.guided-product-moment { display: grid; grid-template-rows: auto minmax(0, 1fr); gap: 0.75rem; min-height: 0; } .guided-product-moment__header { display: grid; grid-template-columns: minmax(0, 0.55fr) minmax(0, 1fr); gap: 0.35rem 1rem; align-items: end; padding: 0.75rem 0.9rem; border: 1px solid var(--stage-line); border-radius: 0.75rem; background: color-mix(in oklch, var(--stage-surface) 86%, transparent); } .guided-product-moment__header span { font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-muted); } .guided-product-moment__header strong { color: var(--text-primary); font-size: 1.2rem; line-height: 1.05; } .guided-product-moment__header p { grid-column: 2; margin: 0; color: var(--text-muted); } .guided-product-moment__primary { min-height: 0; display: grid; align-items: stretch; } .guided-product-moment[data-moment="approval"] .interrupt-contract-preview { max-width: min(58rem, 100%); justify-self: center; } .guided-product-moment[data-moment="resume"] .operation-block { max-width: min(62rem, 100%); justify-self: center; }Adjust class names to match current CSS tokens if the file uses different variables. Do not add a third presentation palette.
-
Step 4: Run CSS-related tests
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/GuidedProductMoment.test.tsx src/presentation/DemoWorkflowScene.test.tsxExpected: PASS.
-
Step 5: Commit
git add web/apps/console/src/presentation/styles/demo-workflow.css web/apps/console/src/presentation/GuidedProductMoment.test.tsx git commit -m "style: focus scene 10 product moment hierarchy"
Task 6: Docs, verification, and plan archive
Files:
- Modify:
docs/current_roadmap.md - Move:
docs/superpowers/plans/2026-07-09-scene-10-guided-product-moment.mdtodocs/historical/superpowers/plans/2026-07-09-scene-10-guided-product-moment.md
Interfaces:
-
Produces:
- Roadmap completion entry.
- Archived plan after implementation.
-
Step 1: Run focused tests
Run:
pnpm --dir web --filter @lda/console test -- src/demo/useDemoTimeline.test.tsx src/presentation/demo-beat-requirements.test.ts src/presentation/PresentationRoute.test.tsx src/presentation/GuidedProductMoment.test.tsx src/presentation/DemoWorkflowScene.test.tsxExpected: PASS.
-
Step 2: Run typecheck
Run:
pnpm --dir web --filter @lda/console typecheckExpected: PASS.
-
Step 3: Run build
Run:
pnpm --dir web --filter @lda/console buildExpected: PASS. Existing chunk-size warning is acceptable.
-
Step 4: Browser smoke
With
pnpm devrunning, inspect or screenshot:http://127.0.0.1:5173/present#scene/interrupt-evidence/approval http://127.0.0.1:5173/present#scene/interrupt-evidence/resume http://127.0.0.1:5173/present#scene/interrupt-evidence/output http://127.0.0.1:5173/present#scene/interrupt-evidence/traceRequired checks:
- Approval controls are enabled without waiting for autoplay.
- Resume operation is visible immediately on the resume hash.
- Output beat shows result proof, not approval controls.
- Trace beat shows trace/evidence proof, not approval controls.
- Cancel still does not show resume evidence in replay.
-
Step 5: Update roadmap
In
docs/current_roadmap.md, mark this slice completed:18. Completed: Scene 10 guided product moment primes replay state for direct hashes and stages approval, resume, output, and trace as one readable product flow. Design: [`Scene 10 guided product moment`](superpowers/specs/2026-07-09-scene-10-guided-product-moment-design.md). Implementation: [`Scene 10 guided product moment plan`](historical/superpowers/plans/2026-07-09-scene-10-guided-product-moment.md).Renumber following items.
-
Step 6: Archive plan
Run:
git mv docs/superpowers/plans/2026-07-09-scene-10-guided-product-moment.md docs/historical/superpowers/plans/2026-07-09-scene-10-guided-product-moment.md -
Step 7: Diff hygiene
Run:
git diff --check git status --shortExpected: no whitespace errors; only intended files listed.
-
Step 8: Commit
git add docs/current_roadmap.md docs/historical/superpowers/plans/2026-07-09-scene-10-guided-product-moment.md git commit -m "docs: complete scene 10 guided product moment"
Self-Review
- Spec coverage: Direct readiness, approval consequences, replay truthfulness, hierarchy, tests, docs, and smoke verification are covered.
- Placeholder scan: No TODO/TBD placeholders remain.
- Type consistency:
DemoBeatRequirement,requirementForDemoBeat,primeReplayToStage, andGuidedProductMomentsignatures are consistent. - Scope check: This is one focused slice. Chat replacement and presenter companion remain future work.