# Scene 10 Factual Run State 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 show factual workflow run state: workflow input, interrupt payload/state, resume decision, output, and trace frame facts, instead of abstract labels or `"not provided"` placeholders. **Architecture:** Add a pure projection module that derives a bounded `DemoRunFacts` view from canonical replay/live events plus transient `DemoTimelineController` state. Render those facts through focused presentation components: input summary, interrupt decision form, resume/output state, and trace facts. Keep graph and chat secondary; the factual run state becomes the primary Scene 10 product proof. **Tech Stack:** React 19, TypeScript, Valibot where useful, Vitest, Testing Library, existing presentation CSS. No new dependencies. ## Global Constraints - Do not replace `useDemoTimeline`, `useTimelineAgent`, or the AI chat primitives. - Do not invent facts. Use only `DemoEvent.params`, `DemoEvent.interpreted`, `DemoTimelineController.interruptPayload`, `DemoTimelineController.output`, and `DemoTimelineController.trace`. - If data exists as an empty object, render `captured as empty object`. - If data is genuinely absent from recording/live state, render `not captured in this recording` or `not created yet` depending on state. - Cancel must stay terminal in replay and live presentation behavior. It must not advance into submitted/resume/output evidence. - The approval beat must show a large operator decision form: proposed issue rows with checkboxes, comment field, Submit, Cancel, run id, interrupt kind, and outcomes. - Schema details are supporting evidence, not the main approval UI. - Scope tests first; run full console typecheck/build before completion. --- ## File Structure Create: - `web/apps/console/src/presentation/demo-run-facts.ts` — pure projection from demo events/controller state to factual view models. - `web/apps/console/src/presentation/demo-run-facts.test.ts` — projection tests for input, interrupt, resume, output, trace, empty-object wording, cancelled state. - `web/apps/console/src/presentation/RunFactsPanel.tsx` — factual display primitives for workflow input, output, and trace facts. - `web/apps/console/src/presentation/RunFactsPanel.test.tsx` — rendering tests. - `web/apps/console/src/presentation/InterruptDecisionForm.tsx` — large operator decision form for proposed issues and comment. - `web/apps/console/src/presentation/InterruptDecisionForm.test.tsx` — form tests for selecting, comment editing, submit/cancel wiring. Modify: - `web/apps/console/src/demo/useDemoTimeline.ts` — make live cancel terminal instead of continuing graph execution. - `web/apps/console/src/demo/agent/timelineAgent.ts` — remove live-only `next()` after cancel. - `web/apps/console/src/demo/useDemoTimeline.test.tsx` — add live cancel regression. - `web/apps/console/src/demo/agent/timelineAgent.test.tsx` — add no-next-on-live-cancel regression. - `web/apps/console/src/presentation/GuidedProductMoment.tsx` — use factual panels/form for Scene 10. - `web/apps/console/src/presentation/GuidedProductMoment.test.tsx` — update route-level expectations for factual state. - `web/apps/console/src/presentation/DemoWorkflowScene.test.tsx` and/or `PresentationRoute.test.tsx` — verify direct hashes show facts. - `web/apps/console/src/presentation/presentation.css` — layout and visual hierarchy for the factual panels and decision form. - `docs/current_roadmap.md` — record completion. Do not modify: - `web/apps/console/src/presentation/chat/*` except if tests reveal approval wiring broke. - `web/apps/console/src/demo/recordings/lda-report-success.v1.json`; the point is to expose current evidence, not change it. --- ### Task 1: Project Factual Run State **Files:** - Create: `web/apps/console/src/presentation/demo-run-facts.ts` - Create: `web/apps/console/src/presentation/demo-run-facts.test.ts` **Interfaces:** - Consumes: - `DemoEvent` from `../demo/timeline/models.js` - `DemoTimelineController` from `../demo/useDemoTimeline.js` - Produces: - `projectDemoRunFacts(demo: DemoTimelineController): DemoRunFacts` - `formatFactValue(value: unknown, absentLabel: string): string` - `DemoRunFacts` with `input`, `interrupt`, `resume`, `output`, `trace` sections. - [ ] **Step 1: Write failing projection tests** Create `web/apps/console/src/presentation/demo-run-facts.test.ts`: ```ts import { describe, expect, it, vi } from "vitest"; import type { DemoTimelineController } from "../demo/useDemoTimeline.js"; import { initialDemoTimelineState } from "../demo/timeline/reducer.js"; import { loadCanonicalDemoRecording } from "../demo/timeline/replay.js"; import { formatFactValue, projectDemoRunFacts } from "./demo-run-facts.js"; const controller = (overrides: Partial = {}): DemoTimelineController => { const recording = loadCanonicalDemoRecording(); return { state: { ...initialDemoTimelineState, mode: "replay", phase: "review", events: recording.events, appliedCount: 3, autoplay: false, }, inFlight: false, interruptPayload: { report_markdown: "# lda.chat Thesis And Project Readiness Report\n\nThe workflow substrate is ready for the defense demo.", proposed_issues: [ { id: "risk-1", title: "Prepare the defense walkthrough", body: "Review the live and replay paths before the defense.", severity: "medium", }, ], }, output: null, trace: null, missingDeploymentMessage: null, recordingId: "lda-report-success-v1", canStart: true, setMode: vi.fn(), start: vi.fn(), pause: vi.fn(), play: vi.fn(), next: vi.fn(async () => {}), submitSelectedIssues: vi.fn(async () => {}), cancelReview: vi.fn(async () => {}), restart: vi.fn(), primeReplayToStage: vi.fn(), ...overrides, }; }; describe("demo-run-facts", () => { it("projects workflow input from run_start params", () => { const facts = projectDemoRunFacts(controller()); expect(facts.input.selectedDocuments).toEqual([ "project-brief.md", "architecture-notes.md", "evaluation-findings.md", "risk-register.md", "roadmap.md", ]); expect(facts.input.boardPath).toBe("issue-board.json"); }); it("projects interrupt payload and state", () => { const facts = projectDemoRunFacts(controller()); expect(facts.interrupt.kind).toBe("issue_review"); expect(facts.interrupt.typed).toBe(true); expect(facts.interrupt.outcomes).toEqual(["submitted", "cancelled"]); expect(facts.interrupt.proposedIssues[0]).toMatchObject({ id: "risk-1", title: "Prepare the defense walkthrough", severity: "medium", }); expect(facts.interrupt.reportMarkdownPreview).toContain("workflow substrate is ready"); }); it("projects resume payload and output after resume", () => { const recording = loadCanonicalDemoRecording(); const facts = projectDemoRunFacts(controller({ state: { ...initialDemoTimelineState, mode: "replay", phase: "completed", events: recording.events, appliedCount: 6, autoplay: false, }, })); expect(facts.resume.outcome).toBe("submitted"); expect(facts.resume.payload).toMatchObject({ approved: true, selected_issue_ids: ["risk-1"], comment: "Create the selected issue.", }); expect(facts.output.state).toBe("created"); if (facts.output.state === "created") { expect(facts.output.createdIssues[0]).toMatchObject({ id: "ISSUE-001" }); } }); it("marks output as not created before resume", () => { const facts = projectDemoRunFacts(controller()); expect(facts.output.state).toBe("not-created"); if (facts.output.state === "not-created") { expect(facts.output.message).toBe("Output not created yet"); } }); it("projects trace frames and empty object state accurately", () => { const recording = loadCanonicalDemoRecording(); const facts = projectDemoRunFacts(controller({ state: { ...initialDemoTimelineState, mode: "replay", phase: "completed", events: recording.events, appliedCount: 6, autoplay: false, }, })); expect(facts.trace.frames).toHaveLength(3); expect(facts.trace.frames[0]).toMatchObject({ nodeId: "list_documents", resolvedInputLabel: "captured as empty object", outputLabel: "captured as empty object", stateChangesLabel: "captured as empty object", }); }); it("formats absent and empty values differently", () => { expect(formatFactValue({}, "not captured in this recording")).toBe("captured as empty object"); expect(formatFactValue(undefined, "not captured in this recording")).toBe("not captured in this recording"); }); }); ``` - [ ] **Step 2: Run test to verify it fails** Run: ```bash pnpm --dir web --filter @lda/console test -- src/presentation/demo-run-facts.test.ts ``` Expected: FAIL because `demo-run-facts.ts` does not exist. - [ ] **Step 3: Write minimal implementation** Create `web/apps/console/src/presentation/demo-run-facts.ts` with pure event/controller projection. Use small local type guards rather than broad `as Record` in render code. The implementation must: - find `run_start`, `interrupt`, `run_resume`, `trace_read`, and `completed` events from `demo.state.events`; - read workflow input from `run_start.params.workflow_input`; - read interrupt facts from `run_start.interpreted.interrupt`, `demo.interruptPayload`, and `interrupt.interpreted.payload`; - read resume facts from `run_resume.params.resume_payload` and `run_resume.params.resume_outcome`; - read output from `demo.output`, `run_resume.interpreted.output`, or `completed.interpreted.output`; - read trace frames from `demo.trace`, `trace_read.interpreted.frames`, or `completed.interpreted.trace.frames`; - preserve the distinction between absent values and empty objects. - [ ] **Step 4: Run tests and commit** Run: ```bash pnpm --dir web --filter @lda/console test -- src/presentation/demo-run-facts.test.ts ``` Expected: PASS. Commit: ```bash git add web/apps/console/src/presentation/demo-run-facts.ts web/apps/console/src/presentation/demo-run-facts.test.ts git commit -m "feat: project factual demo run state" ``` --- ### Task 2: Render Input, Output, And Trace Facts **Files:** - Create: `web/apps/console/src/presentation/RunFactsPanel.tsx` - Create: `web/apps/console/src/presentation/RunFactsPanel.test.tsx` - Modify: `web/apps/console/src/presentation/presentation.css` **Interfaces:** - Consumes: `DemoRunFacts` from Task 1. - Produces: - `RunInputFacts({ facts })` - `RunOutputFacts({ facts })` - `RunTraceFacts({ facts })` - [ ] **Step 1: Write rendering tests** Create `web/apps/console/src/presentation/RunFactsPanel.test.tsx` with tests that assert: - `RunInputFacts` renders `workflow input facts`, selected document names, and `board_path`; - `RunOutputFacts` renders `ISSUE-001`, issue URL, comment, selected IDs, and markdown preview when output is created; - `RunOutputFacts` renders `Output not created yet` before resume; - `RunTraceFacts` renders frame node IDs and `captured as empty object`. - [ ] **Step 2: Run tests to verify failure** Run: ```bash pnpm --dir web --filter @lda/console test -- src/presentation/RunFactsPanel.test.tsx ``` Expected: FAIL because `RunFactsPanel.tsx` does not exist. - [ ] **Step 3: Implement facts panels** Create `RunFactsPanel.tsx` with three exported components: - `RunInputFacts` shows selected documents and board path. - `RunOutputFacts` shows factual not-created or created output state. - `RunTraceFacts` shows per-frame `nodeId`, `stepType`, `outcome`, resolved input label, output label, and state changes label. Do not show generic placeholders such as `not provided`. - [ ] **Step 4: Add CSS** Add `.run-facts-card`, `.run-facts-list`, `.run-facts-dl`, `.run-trace-facts`, and markdown-preview styles. Keep text large enough for a 720p presentation and avoid tiny key/value chips. - [ ] **Step 5: Run tests and commit** Run: ```bash pnpm --dir web --filter @lda/console test -- src/presentation/RunFactsPanel.test.tsx ``` Expected: PASS. Commit: ```bash git add web/apps/console/src/presentation/RunFactsPanel.tsx web/apps/console/src/presentation/RunFactsPanel.test.tsx web/apps/console/src/presentation/presentation.css git commit -m "feat: render factual run state panels" ``` --- ### Task 3: Replace Schema Summary With Operator Decision Form **Files:** - Create: `web/apps/console/src/presentation/InterruptDecisionForm.tsx` - Create: `web/apps/console/src/presentation/InterruptDecisionForm.test.tsx` - Modify: `web/apps/console/src/presentation/presentation.css` **Interfaces:** - Consumes: - `DemoRunFacts["interrupt"]` - `DemoApprovalActions` - Produces: large form with selectable issue rows, comment field, Submit, Cancel. - [ ] **Step 1: Write form tests** Create tests that assert: - proposed issues render as checkbox rows; - default issue selection is checked; - comment field defaults to `Create the selected issue.`; - submit receives selected issue IDs and edited comment; - cancel calls the provided cancel callback without submit. - [ ] **Step 2: Run tests to verify failure** Run: ```bash pnpm --dir web --filter @lda/console test -- src/presentation/InterruptDecisionForm.test.tsx ``` Expected: FAIL because component does not exist. - [ ] **Step 3: Implement form** Create `InterruptDecisionForm.tsx` with: - `role="group"` and `aria-label="operator resume decision"`; - header containing interrupt kind and run id; - factual metadata for typed state and outcomes; - report markdown preview; - checkbox rows for proposed issues; - `textarea` labelled `Resume comment`; - Submit and Cancel buttons; - terminal outcome label when state is `submitted` or `cancelled`. - [ ] **Step 4: Add form CSS** Make this the largest element on approval beat. Avoid tiny JSON-chip layout. Use a clear form hierarchy and keep schema data secondary. - [ ] **Step 5: Run tests and commit** Run: ```bash pnpm --dir web --filter @lda/console test -- src/presentation/InterruptDecisionForm.test.tsx ``` Expected: PASS. Commit: ```bash git add web/apps/console/src/presentation/InterruptDecisionForm.tsx web/apps/console/src/presentation/InterruptDecisionForm.test.tsx web/apps/console/src/presentation/presentation.css git commit -m "feat: render interrupt decision form" ``` --- ### Task 4: Wire Scene 10 To Factual Panels **Files:** - Modify: `web/apps/console/src/presentation/GuidedProductMoment.tsx` - Modify: `web/apps/console/src/presentation/GuidedProductMoment.test.tsx` - Modify: `web/apps/console/src/presentation/PresentationRoute.test.tsx` - Modify: `web/apps/console/src/presentation/presentation.css` **Interfaces:** - Consumes: - `projectDemoRunFacts(demo)` - `InterruptDecisionForm` - `RunInputFacts`, `RunOutputFacts`, `RunTraceFacts` - Produces: Scene 10 direct routes that show factual run state. - [ ] **Step 1: Add route tests for factual state** In `PresentationRoute.test.tsx`, add tests proving: - direct approval route shows workflow input, selected docs, board path, operator decision form, checked issue row, comment field, and `Output not created yet`; - direct output route shows created output facts, `ISSUE-001`, and issue URL; - direct trace route shows trace frame facts and `captured as empty object`. - [ ] **Step 2: Run route tests to verify failure** Run: ```bash pnpm --dir web --filter @lda/console test -- src/presentation/PresentationRoute.test.tsx ``` Expected: FAIL because Scene 10 does not render new facts yet. - [ ] **Step 3: Wire components** Modify `GuidedProductMoment.tsx`: - call `projectDemoRunFacts(demo)`; - render `RunInputFacts`, `InterruptDecisionForm`, and `RunOutputFacts` on approval beat; - render `OperationBlock` plus factual resume/output facts on resume beat; - render `RunOutputFacts` as primary content on output beat; - render `RunTraceFacts` as primary content on trace beat. - [ ] **Step 4: Update approval action typing if needed** If `DemoApprovalActions.submit` accepts no parameters, update `demo-approval-actions.ts` and `PresentationRoute.tsx` so `submit(selectedIssueIds, comment)` can forward the chosen form state into `demo.submitSelectedIssues`. - [ ] **Step 5: Add layout CSS** Add a factual grid for approval: - left: workflow input facts; - center: operator decision form; - right/bottom: output state (`Output not created yet` before resume). At narrower canvas widths, stack vertically without clipping. - [ ] **Step 6: Run tests and commit** Run: ```bash pnpm --dir web --filter @lda/console test -- src/presentation/GuidedProductMoment.test.tsx src/presentation/PresentationRoute.test.tsx ``` Expected: PASS. Commit: ```bash git add web/apps/console/src/presentation/GuidedProductMoment.tsx web/apps/console/src/presentation/GuidedProductMoment.test.tsx web/apps/console/src/presentation/PresentationRoute.test.tsx web/apps/console/src/presentation/demo-approval-actions.ts web/apps/console/src/presentation/presentation.css git commit -m "feat: show factual Scene 10 run state" ``` --- ### Task 5: Make Cancel Terminal In Live And Replay **Files:** - Modify: `web/apps/console/src/demo/useDemoTimeline.ts` - Modify: `web/apps/console/src/demo/useDemoTimeline.test.tsx` - Modify: `web/apps/console/src/demo/agent/timelineAgent.ts` - Modify: `web/apps/console/src/demo/agent/timelineAgent.test.tsx` - Modify: `web/apps/console/src/presentation/PresentationRoute.test.tsx` **Interfaces:** - Consumes: existing `cancelReview(comment)` APIs. - Produces: Cancel never auto-advances into resume/output/trace evidence. - [ ] **Step 1: Add failing live cancel tests** In `useDemoTimeline.test.tsx`, add a test that starts live mode, calls `cancelReview("Cancelled.")`, and expects `state.phase` to be `cancelled`. In `timelineAgent.test.tsx`, add a test with live `phase: "review"` where `result.current.cancelReview()` calls `demo.cancelReview` but does not call `demo.next`. - [ ] **Step 2: Run tests to verify failure** Run: ```bash pnpm --dir web --filter @lda/console test -- src/demo/useDemoTimeline.test.tsx src/demo/agent/timelineAgent.test.tsx ``` Expected: FAIL under current live cancel behavior. - [ ] **Step 3: Fix cancel behavior** In `useDemoTimeline.ts`, change live cancel from `continue_review` to `cancel_review`. In `timelineAgent.ts`, remove live-only `await demo.next()` from `cancelReview`. Add this comment: ```ts // Cancellation is terminal in presentation mode. Do not call next() or the UI // would falsely advance into submitted/resume evidence. ``` - [ ] **Step 4: Run tests and commit** Run: ```bash pnpm --dir web --filter @lda/console test -- src/demo/useDemoTimeline.test.tsx src/demo/agent/timelineAgent.test.tsx src/presentation/PresentationRoute.test.tsx ``` Expected: PASS. Commit: ```bash git add web/apps/console/src/demo/useDemoTimeline.ts web/apps/console/src/demo/useDemoTimeline.test.tsx web/apps/console/src/demo/agent/timelineAgent.ts web/apps/console/src/demo/agent/timelineAgent.test.tsx web/apps/console/src/presentation/PresentationRoute.test.tsx git commit -m "fix: keep live cancellation terminal" ``` --- ### Task 6: Docs, Roadmap, And Visual Smoke **Files:** - Modify: `docs/current_roadmap.md` - Modify: `web/README.md` - Move: `docs/superpowers/plans/2026-07-09-scene-10-factual-run-state.md` -> `docs/historical/superpowers/plans/2026-07-09-scene-10-factual-run-state.md` **Interfaces:** - Produces: documented current behavior and archived plan. - [ ] **Step 1: Update roadmap** In `docs/current_roadmap.md`, add a completed item after live/replay truth: ```md 20. Completed: Scene 10 now presents factual run state: workflow input, interrupt payload, operator resume decision, output, and trace frame facts. Cancel is terminal in presentation mode and does not advance into submitted evidence. Implementation: [`Scene 10 factual run state`](historical/superpowers/plans/2026-07-09-scene-10-factual-run-state.md). ``` Renumber following items. - [ ] **Step 2: Update web README** Add under Presentation Mode: ```md Scene 10 is factual by design. It projects the reviewed/live run into visible workflow input, interrupt payload, resume decision, output, and trace facts. Empty trace frame objects are shown as captured empty objects; absent fields are called out as not captured rather than replaced by generic placeholders. ``` - [ ] **Step 3: Archive plan** Run: ```bash git mv docs/superpowers/plans/2026-07-09-scene-10-factual-run-state.md docs/historical/superpowers/plans/2026-07-09-scene-10-factual-run-state.md ``` - [ ] **Step 4: Verification** Run: ```bash pnpm --dir web --filter @lda/console test -- src/presentation src/demo pnpm --dir web --filter @lda/console typecheck pnpm --dir web --filter @lda/console build git diff --check ``` Expected: - Tests pass. - Typecheck passes. - Build passes with only the known Vite chunk-size warning. - Diff check has no whitespace errors; CRLF warnings are acceptable. - [ ] **Step 5: Browser smoke** With dev server running, smoke: - `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/trace` Expected: - Approval route shows workflow input, interrupt payload, issue checkbox row, comment field, and output not created yet. - Submit advances to resume and shows resume payload/output facts. - Cancel stays on approval and shows cancelled without resume/output/trace evidence. - Output route shows created issue and markdown preview. - Trace route shows frame facts and `captured as empty object` where applicable. - [ ] **Step 6: Commit docs** ```bash git add docs/current_roadmap.md web/README.md docs/historical/superpowers/plans/2026-07-09-scene-10-factual-run-state.md git add -u docs/superpowers/plans/2026-07-09-scene-10-factual-run-state.md git commit -m "docs: complete Scene 10 factual run state" ``` --- ## Self-Review - Spec coverage: Covers workflow input, interrupt state, resume decision, output state, trace facts, live/replay cancel terminal behavior, docs, tests, and visual smoke. - Placeholder scan: No incomplete implementation markers remain outside this self-review sentence. - Type consistency: `DemoRunFacts`, `InterruptDecisionForm`, and `RunFactsPanel` names are defined before use and referenced consistently. - Risk: `LdaReportOutput` / `TraceFrameView` property names may differ slightly from imported models. If TypeScript reports mismatch, inspect those model definitions and update the projection tests to preserve the same factual assertions.