Files
lda-wf/docs/historical/superpowers/plans/2026-07-08-presentation-scene-composition.md
T

1008 lines
34 KiB
Markdown

# Presentation Scene Composition 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:** Recompose presentation Scenes 3, 4, and 5 so they use the 720p stage, and lightly tune Scene 10 graph/interrupt readability.
**Architecture:** Keep the existing storyboard, route, reducer, and demo state intact. Rewrite only the scene renderers inside `SceneBody.tsx` and tune CSS in the existing presentation stylesheets. Scene 10 remains the same data flow; this slice changes hierarchy and readability, not demo behavior.
**Tech Stack:** React, TypeScript, Vitest, Testing Library, CSS, existing Motion wrappers, existing Vite presentation route.
## Global Constraints
- Follow design spec: `docs/superpowers/specs/2026-07-08-presentation-scene-composition-design.md`.
- Do not introduce a generic diagram framework in this slice.
- Do not replace the chat UI, Q&A panel, evidence inspector, storyboard catalog, or recursive architecture figure.
- Do not add demo RPC calls, recording events, or new runtime state.
- Preserve existing keyboard/hash navigation behavior.
- Screenshot smoke output belongs under `web/apps/console/.visual-smoke/`, which is ignored.
- Do not commit `.superpowers/` local progress files.
---
## File Structure
- Modify: `web/apps/console/src/presentation/SceneBody.tsx`
- Owns Scene 3, Scene 4, and Scene 5 markup.
- Modify: `web/apps/console/src/presentation/SceneBody.test.tsx`
- Adds structural tests for the recomposed scenes.
- Modify: `web/apps/console/src/presentation/presentation.css`
- Replaces the small-card scene CSS for positioning, boundary, and lifecycle.
- Modify: `web/apps/console/src/presentation/styles/demo-workflow.css`
- Tunes Scene 10 graph/contract readability and proportions.
- Modify: `web/apps/console/src/presentation/DemoWorkflowScene.test.tsx`
- Pins approval-contract readability without changing demo behavior.
- Modify: `docs/current_roadmap.md`
- Marks the composition pass complete after verification.
- Move: `docs/superpowers/plans/2026-07-08-presentation-scene-composition.md`
- To `docs/historical/superpowers/plans/2026-07-08-presentation-scene-composition.md` after implementation.
---
### Task 1: Recompose Scene 3 Positioning Map
**Files:**
- Modify: `web/apps/console/src/presentation/SceneBody.tsx`
- Modify: `web/apps/console/src/presentation/SceneBody.test.tsx`
- Modify: `web/apps/console/src/presentation/presentation.css`
**Interfaces:**
- Consumes: `SceneDefinition`, `SceneBeatDefinition`, `DiscussionLinks`, existing `StageCaption`.
- Produces: a `PositioningScene` that renders `aria-label="positioning map"` and marks the center substrate with `data-positioning-active="true"` on `lda-position`.
- [ ] **Step 1: Write failing tests for the positioning map**
Add these tests inside `describe("SceneBody", () => { ... })` in `SceneBody.test.tsx`:
```tsx
it("renders Scene 3 as a full positioning map", () => {
const location: PresentationLocation = { kind: "main", sceneId: "positioning", beatId: "landscape", focusPath: [] };
render(
<SceneBody
location={location}
demo={demo}
selectedNodeId={null}
selectNode={noop}
openEvidence={noop}
openDiscussion={noop}
onFocusPathChange={noop}
motionDisabled={false}
/>,
);
const map = screen.getByLabelText("positioning map");
expect(map).toHaveAttribute("data-positioning-active-region", "landscape");
expect(screen.getByText("Tool loops")).toBeInTheDocument();
expect(screen.getByText("Generated scripts")).toBeInTheDocument();
expect(screen.getByText("lda.chat")).toBeInTheDocument();
expect(screen.getByText("Agent graphs")).toBeInTheDocument();
expect(screen.getByText("MCP")).toBeInTheDocument();
});
it("emphasizes lda.chat in the positioning beat", () => {
const location: PresentationLocation = { kind: "main", sceneId: "positioning", beatId: "lda-position", focusPath: [] };
render(
<SceneBody
location={location}
demo={demo}
selectedNodeId={null}
selectNode={noop}
openEvidence={noop}
openDiscussion={noop}
onFocusPathChange={noop}
motionDisabled={false}
/>,
);
const substrate = screen.getByText("lda.chat").closest("[data-positioning-role='substrate']");
expect(substrate).toHaveAttribute("data-positioning-active", "true");
expect(screen.getByLabelText("positioning map")).toHaveAttribute("data-positioning-active-region", "lda");
});
```
- [ ] **Step 2: Run positioning tests and verify they fail**
Run:
```powershell
pnpm --dir web --filter @lda/console test -- src/presentation/SceneBody.test.tsx
```
Expected: FAIL because `positioning map`, `Generated scripts`, and the new data attributes do not exist.
- [ ] **Step 3: Replace `PositioningScene` markup**
In `SceneBody.tsx`, replace the current `PositioningScene` implementation with:
```tsx
const PositioningScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => {
const highlightLda = beat.id === "lda-position";
return (
<>
<StageCaption eyebrow={`Act I · ${scene.claimClass}`} title={scene.title}>
<p>{beat.caption}</p>
</StageCaption>
<div
className="scene-body__positioning-map"
aria-label="positioning map"
data-positioning-active-region={highlightLda ? "lda" : "landscape"}
>
<section className="scene-body__positioning-column" aria-label="direct action patterns">
<p className="scene-body__positioning-label">Direct action</p>
<article className="scene-body__positioning-tile">
<strong>Tool loops</strong>
<span>Fast action, no durable lifecycle</span>
</article>
<article className="scene-body__positioning-tile">
<strong>Generated scripts</strong>
<span>Inspectable code, weak deployment records</span>
</article>
</section>
<article
className="scene-body__positioning-substrate"
data-positioning-role="substrate"
data-positioning-active={highlightLda ? "true" : "false"}
>
<span className="scene-body__positioning-label">This thesis</span>
<strong>lda.chat</strong>
<p>Typed workflow substrate for external agents and human operators.</p>
<ul>
<li>Lifecycle</li>
<li>Validation</li>
<li>Persisted records</li>
</ul>
</article>
<section className="scene-body__positioning-column" aria-label="adjacent systems">
<p className="scene-body__positioning-label">Adjacent systems</p>
<article className="scene-body__positioning-tile">
<strong>Hosted automation</strong>
<span>Managed triggers and app integrations</span>
</article>
<article className="scene-body__positioning-tile">
<strong>Agent graphs</strong>
<span>Durable planner loops</span>
</article>
<article className="scene-body__positioning-tile">
<strong>MCP</strong>
<span>Capability protocol boundary</span>
</article>
</section>
</div>
<p className="scene-body__evidence">{scene.evidencePointer}</p>
</>
);
};
```
- [ ] **Step 4: Replace positioning CSS**
In `presentation.css`, replace the existing `.scene-body__positioning-grid`, `.scene-body__positioning-card`, and `.scene-body__positioning-card--active` rules with:
```css
.scene-body__positioning-map {
display: grid;
grid-template-columns: minmax(13rem, 0.9fr) minmax(20rem, 1.35fr) minmax(13rem, 0.9fr);
gap: 1rem;
align-items: stretch;
margin-top: 1.1rem;
min-height: 20rem;
}
.scene-body__positioning-column {
display: grid;
align-content: start;
gap: 0.7rem;
}
.scene-body__positioning-label {
margin: 0;
color: var(--color-editorial-muted, oklch(0.48 0.025 65));
font: 700 0.68rem/1 var(--font-mono, monospace);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.scene-body__positioning-tile,
.scene-body__positioning-substrate {
border: 1px solid color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 30%, transparent);
border-radius: 0.85rem;
background: color-mix(in oklch, var(--color-editorial-paper, oklch(0.975 0.012 82)) 78%, white);
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
.scene-body__positioning-tile {
min-height: 5.8rem;
padding: 0.85rem;
}
.scene-body__positioning-tile strong {
display: block;
margin-bottom: 0.35rem;
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
.scene-body__positioning-tile span {
color: color-mix(in oklch, var(--color-editorial-ink, oklch(0.19 0.015 65)) 72%, var(--color-editorial-muted, oklch(0.48 0.025 65)));
font-size: 0.82rem;
line-height: 1.35;
}
.scene-body__positioning-substrate {
display: grid;
align-content: center;
gap: 0.8rem;
padding: 1.15rem;
box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--accent-cyan, oklch(0.7 0.16 195)) 18%, transparent);
}
.scene-body__positioning-substrate strong {
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
font-size: 2rem;
line-height: 0.95;
}
.scene-body__positioning-substrate p {
max-width: 28ch;
margin: 0;
color: color-mix(in oklch, var(--color-editorial-ink, oklch(0.19 0.015 65)) 82%, var(--color-editorial-muted, oklch(0.48 0.025 65)));
}
.scene-body__positioning-substrate ul {
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
margin: 0;
padding: 0;
list-style: none;
}
.scene-body__positioning-substrate li {
border: 1px solid color-mix(in oklch, var(--accent-cyan, oklch(0.7 0.16 195)) 42%, transparent);
border-radius: 999px;
padding: 0.25rem 0.5rem;
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
font-size: 0.72rem;
font-weight: 700;
}
.scene-body__positioning-map[data-positioning-active-region="lda"] .scene-body__positioning-tile {
opacity: 0.58;
}
.scene-body__positioning-substrate[data-positioning-active="true"] {
transform: translateY(-0.25rem);
border-color: color-mix(in oklch, var(--accent-cyan, oklch(0.7 0.16 195)) 68%, transparent);
background: color-mix(in oklch, var(--accent-cyan, oklch(0.7 0.16 195)) 8%, var(--color-editorial-paper, oklch(0.975 0.012 82)));
}
```
- [ ] **Step 5: Run tests and commit Task 1**
Run:
```powershell
pnpm --dir web --filter @lda/console test -- src/presentation/SceneBody.test.tsx
pnpm --dir web --filter @lda/console typecheck
```
Expected: PASS.
Commit:
```powershell
git add web/apps/console/src/presentation/SceneBody.tsx web/apps/console/src/presentation/SceneBody.test.tsx web/apps/console/src/presentation/presentation.css
git commit -m "feat: recompose positioning scene"
```
---
### Task 2: Recompose Scene 4 Planner Runtime Boundary
**Files:**
- Modify: `web/apps/console/src/presentation/SceneBody.tsx`
- Modify: `web/apps/console/src/presentation/SceneBody.test.tsx`
- Modify: `web/apps/console/src/presentation/presentation.css`
**Interfaces:**
- Consumes: `BoundaryScene` beat IDs `planner`, `runtime`, and `boundary`.
- Produces: a boundary region with `aria-label="planner runtime boundary"` and `data-boundary-active`.
- [ ] **Step 1: Write failing boundary tests**
Add these tests to `SceneBody.test.tsx`:
```tsx
it("renders Scene 4 as a planner runtime boundary", () => {
const location: PresentationLocation = { kind: "main", sceneId: "planner-runtime", beatId: "boundary", focusPath: [] };
render(
<SceneBody
location={location}
demo={demo}
selectedNodeId={null}
selectNode={noop}
openEvidence={noop}
openDiscussion={noop}
onFocusPathChange={noop}
motionDisabled={false}
/>,
);
const boundary = screen.getByLabelText("planner runtime boundary");
expect(boundary).toHaveAttribute("data-boundary-active", "boundary");
expect(screen.getByText("Planner")).toBeInTheDocument();
expect(screen.getByText("Runtime")).toBeInTheDocument();
expect(screen.getByText(/CLI/)).toBeInTheDocument();
expect(screen.getByText(/JSON-RPC/)).toBeInTheDocument();
});
it("marks the planner side active on the planner beat", () => {
const location: PresentationLocation = { kind: "main", sceneId: "planner-runtime", beatId: "planner", focusPath: [] };
render(
<SceneBody
location={location}
demo={demo}
selectedNodeId={null}
selectNode={noop}
openEvidence={noop}
openDiscussion={noop}
onFocusPathChange={noop}
motionDisabled={false}
/>,
);
expect(screen.getByLabelText("planner runtime boundary")).toHaveAttribute("data-boundary-active", "planner");
expect(screen.getByText("Planner").closest("[data-boundary-side='planner']")).toHaveAttribute("data-boundary-emphasis", "active");
});
```
- [ ] **Step 2: Run boundary tests and verify they fail**
Run:
```powershell
pnpm --dir web --filter @lda/console test -- src/presentation/SceneBody.test.tsx
```
Expected: FAIL because the new aria label and data attributes do not exist.
- [ ] **Step 3: Replace `BoundaryScene` markup**
In `SceneBody.tsx`, replace the current `BoundaryScene` with:
```tsx
const boundaryActiveForBeat = (beatId: string): "planner" | "runtime" | "boundary" => {
if (beatId === "runtime") return "runtime";
if (beatId === "boundary") return "boundary";
return "planner";
};
const BoundaryScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => {
const active = boundaryActiveForBeat(beat.id);
const plannerActive = active === "planner" || active === "boundary";
const runtimeActive = active === "runtime" || active === "boundary";
return (
<>
<StageCaption eyebrow="Act II · implemented" title={scene.title}>
<p>{beat.caption}</p>
</StageCaption>
<div className="scene-body__boundary" aria-label="planner runtime boundary" data-boundary-active={active}>
<section
className="scene-body__boundary-pane"
data-boundary-side="planner"
data-boundary-emphasis={plannerActive ? "active" : "reduced"}
>
<span>External operator</span>
<h3>Planner</h3>
<ul>
<li>Proposes workflow structure</li>
<li>Revises steps and bindings</li>
<li>Chooses public operations</li>
</ul>
</section>
<div className="scene-body__boundary-seam" aria-label="workflow operation boundary">
<strong>CLI / JSON-RPC</strong>
<span>typed workflow operations</span>
</div>
<section
className="scene-body__boundary-pane"
data-boundary-side="runtime"
data-boundary-emphasis={runtimeActive ? "active" : "reduced"}
>
<span>lda.chat substrate</span>
<h3>Runtime</h3>
<ul>
<li>Validates schemas and routes</li>
<li>Executes deterministic nodes</li>
<li>Records traces and resume state</li>
</ul>
</section>
</div>
<p className="scene-body__evidence">{scene.evidencePointer}</p>
</>
);
};
```
- [ ] **Step 4: Replace boundary CSS**
In `presentation.css`, replace `.scene-body__boundary`, `.scene-body__boundary-side`, `.scene-body__boundary-divider`, and related dim rules with:
```css
.scene-body__boundary {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(8rem, 0.26fr) minmax(0, 1fr);
gap: 1rem;
align-items: stretch;
margin-top: 1.2rem;
min-height: 20rem;
}
.scene-body__boundary-pane {
display: grid;
align-content: center;
gap: 0.85rem;
border: 1px solid color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 32%, transparent);
border-radius: 1rem;
padding: 1.1rem;
background: color-mix(in oklch, var(--color-editorial-paper, oklch(0.975 0.012 82)) 82%, white);
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
.scene-body__boundary-pane[data-boundary-emphasis="reduced"] {
opacity: 0.58;
}
.scene-body__boundary-pane h3 {
margin: 0;
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
font-size: 1.8rem;
line-height: 1;
}
.scene-body__boundary-pane > span {
color: var(--color-editorial-muted, oklch(0.48 0.025 65));
font: 700 0.68rem/1 var(--font-mono, monospace);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.scene-body__boundary-pane ul {
display: grid;
gap: 0.45rem;
margin: 0;
padding-left: 1rem;
color: color-mix(in oklch, var(--color-editorial-ink, oklch(0.19 0.015 65)) 78%, var(--color-editorial-muted, oklch(0.48 0.025 65)));
}
.scene-body__boundary-seam {
display: grid;
place-content: center;
gap: 0.35rem;
border-inline: 2px solid color-mix(in oklch, var(--accent-cyan, oklch(0.7 0.16 195)) 58%, transparent);
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
text-align: center;
}
.scene-body__boundary-seam strong {
font-size: 0.85rem;
}
.scene-body__boundary-seam span {
color: var(--color-editorial-muted, oklch(0.48 0.025 65));
font-size: 0.72rem;
line-height: 1.25;
}
.scene-body__boundary[data-boundary-active="boundary"] .scene-body__boundary-seam {
background: color-mix(in oklch, var(--accent-cyan, oklch(0.7 0.16 195)) 7%, transparent);
}
```
- [ ] **Step 5: Run tests and commit Task 2**
Run:
```powershell
pnpm --dir web --filter @lda/console test -- src/presentation/SceneBody.test.tsx
pnpm --dir web --filter @lda/console typecheck
```
Expected: PASS.
Commit:
```powershell
git add web/apps/console/src/presentation/SceneBody.tsx web/apps/console/src/presentation/SceneBody.test.tsx web/apps/console/src/presentation/presentation.css
git commit -m "feat: recompose planner runtime scene"
```
---
### Task 3: Recompose Scene 5 Lifecycle Rail
**Files:**
- Modify: `web/apps/console/src/presentation/SceneBody.tsx`
- Modify: `web/apps/console/src/presentation/SceneBody.test.tsx`
- Modify: `web/apps/console/src/presentation/presentation.css`
**Interfaces:**
- Consumes: lifecycle beat IDs `draft`, `artifact`, `deployment`, and `run`.
- Produces: `aria-label="workflow lifecycle rail"` and a changing current-state explanation panel.
- [ ] **Step 1: Write failing lifecycle tests**
Add these tests to `SceneBody.test.tsx`:
```tsx
it("renders Scene 5 as a full workflow lifecycle rail", () => {
const location: PresentationLocation = { kind: "main", sceneId: "lifecycle", beatId: "deployment", focusPath: [] };
render(
<SceneBody
location={location}
demo={demo}
selectedNodeId={null}
selectNode={noop}
openEvidence={noop}
openDiscussion={noop}
onFocusPathChange={noop}
motionDisabled={false}
/>,
);
const rail = screen.getByLabelText("workflow lifecycle rail");
expect(rail).toHaveAttribute("data-lifecycle-active-stage", "deployment");
expect(screen.getByText("Draft")).toBeInTheDocument();
expect(screen.getByText("Artifact")).toBeInTheDocument();
expect(screen.getByText("Deployment")).toBeInTheDocument();
expect(screen.getByText("Run")).toBeInTheDocument();
expect(screen.getByText("Source binding")).toBeInTheDocument();
});
it("updates the lifecycle explanation with the active beat", () => {
const { rerender } = render(
<SceneBody
location={{ kind: "main", sceneId: "lifecycle", beatId: "draft", focusPath: [] }}
demo={demo}
selectedNodeId={null}
selectNode={noop}
openEvidence={noop}
openDiscussion={noop}
onFocusPathChange={noop}
motionDisabled={false}
/>,
);
expect(screen.getByLabelText("current lifecycle state")).toHaveTextContent("Mutable authoring state");
rerender(
<SceneBody
location={{ kind: "main", sceneId: "lifecycle", beatId: "run", focusPath: [] }}
demo={demo}
selectedNodeId={null}
selectNode={noop}
openEvidence={noop}
openDiscussion={noop}
onFocusPathChange={noop}
motionDisabled={false}
/>,
);
expect(screen.getByLabelText("current lifecycle state")).toHaveTextContent("Execution record and trace");
});
```
- [ ] **Step 2: Run lifecycle tests and verify they fail**
Run:
```powershell
pnpm --dir web --filter @lda/console test -- src/presentation/SceneBody.test.tsx
```
Expected: FAIL because the rail label and current-state panel do not exist.
- [ ] **Step 3: Extend lifecycle data and replace `LifecycleScene`**
In `SceneBody.tsx`, replace `lifecycleStages` and `LifecycleScene` with:
```tsx
const lifecycleStages = [
{ id: "draft", label: "Draft", role: "Mutable authoring state", detail: "Iterate before freezing a workflow definition." },
{ id: "artifact", label: "Artifact", role: "Immutable workflow definition", detail: "Save a versioned plan that can be deployed." },
{ id: "deployment", label: "Deployment", role: "Source binding", detail: "Bind workflow requirements to configured runtime sources." },
{ id: "run", label: "Run", role: "Execution record and trace", detail: "Persist status, outputs, interrupts, and trace evidence." },
] as const;
const LifecycleScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => {
const activeIndex = Math.max(0, lifecycleStages.findIndex((stage) => stage.id === beat.id));
const activeStage = lifecycleStages[activeIndex] ?? lifecycleStages[0];
return (
<>
<StageCaption eyebrow="Act II · implemented" title={scene.title}>
<p>{beat.caption}</p>
</StageCaption>
<div className="scene-body__lifecycle" aria-label="workflow lifecycle rail" data-lifecycle-active-stage={activeStage.id}>
{lifecycleStages.map((stage, i) => (
<article
key={stage.id}
className="scene-body__lifecycle-stage"
data-lifecycle-active={i === activeIndex ? "true" : "false"}
data-lifecycle-complete={i < activeIndex ? "true" : "false"}
>
<span className="scene-body__lifecycle-number">{i + 1}</span>
<strong>{stage.label}</strong>
<small>{stage.role}</small>
{i < lifecycleStages.length - 1 && <span className="scene-body__lifecycle-arrow"></span>}
</article>
))}
</div>
<aside className="scene-body__lifecycle-current" aria-label="current lifecycle state">
<span>{activeStage.label}</span>
<strong>{activeStage.role}</strong>
<p>{activeStage.detail}</p>
</aside>
<p className="scene-body__evidence">{scene.evidencePointer}</p>
</>
);
};
```
- [ ] **Step 4: Replace lifecycle CSS**
In `presentation.css`, replace `.scene-body__lifecycle`, `.scene-body__lifecycle-stage`, `.scene-body__lifecycle-number`, `.scene-body__lifecycle-arrow`, and active-state lifecycle rules with:
```css
.scene-body__lifecycle {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 0.65rem;
align-items: stretch;
margin-top: 1.2rem;
}
.scene-body__lifecycle-stage {
position: relative;
display: grid;
align-content: start;
gap: 0.5rem;
min-height: 9rem;
border: 1px solid color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 32%, transparent);
border-radius: 0.9rem;
padding: 0.9rem;
background: color-mix(in oklch, var(--color-editorial-paper, oklch(0.975 0.012 82)) 82%, white);
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
.scene-body__lifecycle-stage[data-lifecycle-active="false"] {
opacity: 0.68;
}
.scene-body__lifecycle-stage[data-lifecycle-active="true"] {
opacity: 1;
transform: translateY(-0.25rem);
border-color: color-mix(in oklch, var(--accent-cyan, oklch(0.7 0.16 195)) 58%, transparent);
background: color-mix(in oklch, var(--accent-cyan, oklch(0.7 0.16 195)) 8%, var(--color-editorial-paper, oklch(0.975 0.012 82)));
}
.scene-body__lifecycle-stage strong {
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
font-size: 1.2rem;
}
.scene-body__lifecycle-stage small {
color: color-mix(in oklch, var(--color-editorial-ink, oklch(0.19 0.015 65)) 72%, var(--color-editorial-muted, oklch(0.48 0.025 65)));
line-height: 1.3;
}
.scene-body__lifecycle-number {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.5rem;
height: 1.5rem;
border-radius: 50%;
background: color-mix(in oklch, var(--accent-cyan, oklch(0.7 0.16 195)) 74%, white);
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
font: 800 0.72rem/1 var(--font-mono, monospace);
}
.scene-body__lifecycle-arrow {
position: absolute;
right: -0.55rem;
top: 50%;
color: var(--color-editorial-muted, oklch(0.48 0.025 65));
transform: translateY(-50%);
}
.scene-body__lifecycle-current {
display: grid;
gap: 0.3rem;
margin-top: 0.9rem;
border: 1px solid color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 28%, transparent);
border-radius: 0.85rem;
padding: 0.8rem 0.9rem;
background: color-mix(in oklch, var(--color-editorial-paper, oklch(0.975 0.012 82)) 86%, white);
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
.scene-body__lifecycle-current span {
color: var(--color-editorial-muted, oklch(0.48 0.025 65));
font: 700 0.66rem/1 var(--font-mono, monospace);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.scene-body__lifecycle-current strong,
.scene-body__lifecycle-current p {
margin: 0;
}
.scene-body__lifecycle-current p {
color: color-mix(in oklch, var(--color-editorial-ink, oklch(0.19 0.015 65)) 76%, var(--color-editorial-muted, oklch(0.48 0.025 65)));
}
```
Keep the existing `.scene-body__authoring-number` styles by splitting any combined selector that used `.scene-body__lifecycle-number, .scene-body__authoring-number`. After this task, `.scene-body__authoring-number` must still render the Scene 7 numbered circles.
- [ ] **Step 5: Run tests and commit Task 3**
Run:
```powershell
pnpm --dir web --filter @lda/console test -- src/presentation/SceneBody.test.tsx
pnpm --dir web --filter @lda/console typecheck
```
Expected: PASS.
Commit:
```powershell
git add web/apps/console/src/presentation/SceneBody.tsx web/apps/console/src/presentation/SceneBody.test.tsx web/apps/console/src/presentation/presentation.css
git commit -m "feat: recompose lifecycle scene"
```
---
### Task 4: Tune Scene 10 Demo Graph And Interrupt Contract
**Files:**
- Modify: `web/apps/console/src/presentation/DemoWorkflowScene.test.tsx`
- Modify: `web/apps/console/src/presentation/styles/demo-workflow.css`
**Interfaces:**
- Consumes: existing `DemoWorkflowScene` approval beat and `InterruptContractPreview`.
- Produces: a readable graph/contract layout without changing data or component props.
- [ ] **Step 1: Add an approval readability test**
In `DemoWorkflowScene.test.tsx`, extend the existing `"makes the Scene 10 approval contract the primary visual"` test:
```tsx
it("makes the Scene 10 approval contract the primary visual", () => {
renderBeat("approval", "interrupt-evidence");
const stage = screen.getByLabelText("demo workflow stage");
expect(stage).toHaveAttribute("data-demo-layout", "approval");
expect(screen.getByLabelText("typed interrupt contract")).toHaveAttribute("data-hero", "true");
expect(screen.getByLabelText("typed interrupt contract")).toHaveTextContent("Operator decision");
expect(screen.getByLabelText("typed interrupt contract")).toHaveTextContent("Resume outcomes");
expect(screen.getByLabelText("workflow graph")).toBeInTheDocument();
});
```
If the test already contains the first three assertions, replace it with the block above.
- [ ] **Step 2: Run demo scene tests and verify they pass before CSS**
Run:
```powershell
pnpm --dir web --filter @lda/console test -- src/presentation/DemoWorkflowScene.test.tsx
```
Expected: PASS. This confirms the task is visual/readability only.
- [ ] **Step 3: Tune demo workflow CSS**
In `web/apps/console/src/presentation/styles/demo-workflow.css`, update only existing selectors. Use these target values:
```css
.demo-workflow-stage[data-demo-layout="approval"] .demo-workflow-stage__graph,
.demo-workflow-stage[data-demo-layout="interrupt"] .demo-workflow-stage__graph {
grid-template-columns: minmax(18rem, 0.78fr) minmax(26rem, 1.22fr);
align-items: stretch;
}
.demo-workflow-stage[data-demo-layout="approval"] .interrupt-contract-preview,
.demo-workflow-stage[data-demo-layout="interrupt"] .interrupt-contract-preview {
min-width: 0;
color: var(--text-primary);
}
.demo-workflow-stage[data-demo-layout="approval"] .workflow-graph-stage,
.demo-workflow-stage[data-demo-layout="interrupt"] .workflow-graph-stage {
min-height: 18rem;
}
.presentation-route .workflow-graph-stage__node {
color: var(--text-primary);
}
.workflow-graph-stage__node strong {
color: var(--text-primary);
}
.workflow-graph-stage__node small,
.workflow-graph-stage__node-state {
color: color-mix(in oklch, var(--text-primary) 76%, var(--text-muted));
}
.workflow-graph-stage__node[data-execution-state="future"] {
opacity: 0.72;
}
.workflow-graph-stage__node[data-execution-state="completed"] {
opacity: 0.9;
}
.interrupt-contract-preview__schema pre {
max-height: 7.5rem;
color: color-mix(in oklch, var(--text-primary) 82%, var(--text-muted));
}
```
Do not duplicate selectors if they already exist. Merge these values into the current blocks.
- [ ] **Step 4: Run tests and commit Task 4**
Run:
```powershell
pnpm --dir web --filter @lda/console test -- src/presentation/DemoWorkflowScene.test.tsx
pnpm --dir web --filter @lda/console typecheck
```
Expected: PASS.
Commit:
```powershell
git add web/apps/console/src/presentation/DemoWorkflowScene.test.tsx web/apps/console/src/presentation/styles/demo-workflow.css
git commit -m "fix: tune demo graph contract readability"
```
---
### Task 5: Screenshot Smoke, Roadmap, And Archive
**Files:**
- Modify: `docs/current_roadmap.md`
- Move: `docs/superpowers/plans/2026-07-08-presentation-scene-composition.md` to `docs/historical/superpowers/plans/2026-07-08-presentation-scene-composition.md`
**Interfaces:**
- Consumes: completed scene composition tasks.
- Produces: verified screenshot set and live roadmap entry pointing to the archived plan.
- [ ] **Step 1: Run focused verification**
Run:
```powershell
pnpm --dir web --filter @lda/console test -- src/presentation/SceneBody.test.tsx src/presentation/DemoWorkflowScene.test.tsx
pnpm --dir web --filter @lda/console typecheck
pnpm --dir web --filter @lda/console build
```
Expected:
- SceneBody and DemoWorkflowScene tests pass.
- Typecheck prints no TypeScript errors.
- Build succeeds with the existing Vite chunk-size warning only.
- [ ] **Step 2: Capture screenshot smoke**
With `pnpm dev` running, use the existing browser smoke method used by prior presentation slices. Capture these routes at `1280x720`:
```text
http://127.0.0.1:5173/present#scene/positioning/landscape
http://127.0.0.1:5173/present#scene/positioning/lda-position
http://127.0.0.1:5173/present#scene/planner-runtime/planner
http://127.0.0.1:5173/present#scene/planner-runtime/boundary
http://127.0.0.1:5173/present#scene/lifecycle/draft
http://127.0.0.1:5173/present#scene/lifecycle/run
http://127.0.0.1:5173/present#scene/workflow-demo/approval
```
Save screenshots under:
```text
web/apps/console/.visual-smoke/
```
Expected visual checks:
- Scene 3 uses the center of the canvas and reads as a map.
- Scene 4 has a clear central boundary and two large panes.
- Scene 5 has a readable Draft -> Artifact -> Deployment -> Run rail.
- Scene 10 graph labels and interrupt-contract labels are readable.
- No stage scrollbars are visible.
- [ ] **Step 3: Update roadmap**
In `docs/current_roadmap.md`, update the presentation visual slices list so item 3 reads:
```markdown
3. Completed: scene composition pass expanded the positioning map,
planner/runtime boundary, lifecycle rail, and Scene 10 demo
graph/contract readability. Implementation:
[`presentation scene composition`](historical/superpowers/plans/2026-07-08-presentation-scene-composition.md).
4. Presentation craft pass: tune motion, discussion-chip placement, Q&A modal
hierarchy, evidence receipt placement, and remaining generic-slide styling
after the compositions are stable.
```
- [ ] **Step 4: Archive the plan**
Run:
```powershell
Move-Item docs/superpowers/plans/2026-07-08-presentation-scene-composition.md docs/historical/superpowers/plans/2026-07-08-presentation-scene-composition.md
```
- [ ] **Step 5: Run docs/status checks**
Run:
```powershell
rg -n '2026-07-08-presentation-scene-composition' docs/current_roadmap.md docs/historical/superpowers/plans docs/superpowers/plans
git status --short
git diff --check
```
Expected:
- `docs/current_roadmap.md` points to the historical plan path.
- No active plan with this filename remains in `docs/superpowers/plans/`.
- `.visual-smoke/` is ignored.
- `.superpowers/` is not staged.
- `git diff --check` reports no whitespace errors.
- [ ] **Step 6: Commit Task 5**
Commit:
```powershell
git add docs/current_roadmap.md docs/historical/superpowers/plans/2026-07-08-presentation-scene-composition.md
git commit -m "docs: record presentation scene composition"
```
---
## Final Verification
Run:
```powershell
pnpm --dir web test
pnpm --dir web typecheck
pnpm --dir web build
git diff --check
git status --short
```
Expected:
- All web tests pass, except any explicitly reported pre-existing flaky timeout must be named in the final report with the failing test.
- Typecheck is clean.
- Build succeeds with only the existing Vite chunk-size warning.
- Whitespace check is clean.
- Git status has no unintended files; `.superpowers/` and `.visual-smoke/` are not staged.
## Self-Review
- Spec coverage: Tasks 1-3 cover Scenes 3, 4, and 5. Task 4 covers Scene 10. Task 5 covers screenshots, roadmap, and plan archival.
- Completion-word scan: no unresolved implementation markers are present.
- Type consistency: all new attributes and labels referenced by tests are produced by the matching scene renderers.
- Scope check: no chat replacement, Q&A rewrite, evidence-inspector redesign, route change, or demo state change is included.