refactor: use one editorial presentation canvas

This commit is contained in:
lda
2026-07-11 18:53:03 +07:00 Verified
parent d715fb68ee
commit cbbb000a44
14 changed files with 30 additions and 82 deletions
+1 -1
View File
@@ -300,7 +300,7 @@ Presentation wishlist / defense readiness:
[`presentation evidence handoff`](historical/superpowers/plans/2026-07-11-presentation-evidence-handoff.md). [`presentation evidence handoff`](historical/superpowers/plans/2026-07-11-presentation-evidence-handoff.md).
13. Completed: deck hierarchy pass gave Scenes 1, 2, 6, 7, 8-12, and 13-14 13. Completed: deck hierarchy pass gave Scenes 1, 2, 6, 7, 8-12, and 13-14
one focal artifact per beat, reused factual authoring/demo projections, one focal artifact per beat, reused factual authoring/demo projections,
applied storyboard stage themes, and froze Scenes 3-5. Implementation: kept one editorial canvas, and froze Scenes 3-5. Implementation:
[`defense deck hierarchy pass`](historical/superpowers/plans/2026-07-11-defense-deck-hierarchy-pass.md). [`defense deck hierarchy pass`](historical/superpowers/plans/2026-07-11-defense-deck-hierarchy-pass.md).
- Presenter runbook: - Presenter runbook:
[`defense presentation runbook`](runbooks/defense-presentation.md) covers [`defense presentation runbook`](runbooks/defense-presentation.md) covers
+4 -4
View File
@@ -243,11 +243,11 @@ Chat is composed per-beat with four modes: `hidden`, `full`, `rail`, and `dock`.
When `dock` is active, a floating button in the lower-left corner expands the When `dock` is active, a floating button in the lower-left corner expands the
chat to `rail`. chat to `rail`.
#### Stage Themes #### Presentation Canvas
Stage themes change by act: `paper` for scenes 1-3 and 13-14, `night` for The presentation uses one warm Editorial Canvas across all scenes. Dark
scenes 4-12. Presenter controls allow overriding stage and chat themes surfaces are local to product evidence such as chat, graphs, traces, and
independently during rehearsal. terminal-style operation blocks; they are not whole-stage themes.
#### Discussion Branches #### Discussion Branches
@@ -17,17 +17,11 @@ describe("PresentationCanvas", () => {
const canvas = screen.getByTestId("presentation-canvas"); const canvas = screen.getByTestId("presentation-canvas");
expect(canvas).toHaveClass("presentation-canvas"); expect(canvas).toHaveClass("presentation-canvas");
expect(canvas).toHaveAttribute("data-stage-theme", "paper"); expect(canvas).not.toHaveAttribute("data-stage-theme");
expect(canvas.style.transform).toBe(""); expect(canvas.style.transform).toBe("");
expect(canvas).toHaveTextContent("Scene"); expect(canvas).toHaveTextContent("Scene");
}); });
it("applies the authored night stage theme without changing canvas geometry", () => {
render(<PresentationCanvas stageTheme="night"><div>Scene</div></PresentationCanvas>);
expect(screen.getByTestId("presentation-canvas")).toHaveAttribute("data-stage-theme", "night");
});
it("uses CSS ratio bounds instead of JavaScript scale calculations", () => { it("uses CSS ratio bounds instead of JavaScript scale calculations", () => {
expect(editorialCss).toContain("width: min(100dvw, calc(100dvh * 16 / 9))"); expect(editorialCss).toContain("width: min(100dvw, calc(100dvh * 16 / 9))");
expect(editorialCss).toContain("height: min(100dvh, calc(100dvw * 9 / 12))"); expect(editorialCss).toContain("height: min(100dvh, calc(100dvw * 9 / 12))");
@@ -1,12 +1,10 @@
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import type { StageTheme } from "./storyboard.js";
type PresentationCanvasProps = { type PresentationCanvasProps = {
readonly children: ReactNode; readonly children: ReactNode;
readonly stageTheme?: StageTheme;
}; };
export const PresentationCanvas = ({ children, stageTheme = "paper" }: PresentationCanvasProps) => ( export const PresentationCanvas = ({ children }: PresentationCanvasProps) => (
<div className="presentation-viewport"> <div className="presentation-viewport">
{/* {/*
Keep the presentation as normal responsive DOM instead of scaling the Keep the presentation as normal responsive DOM instead of scaling the
@@ -17,7 +15,6 @@ export const PresentationCanvas = ({ children, stageTheme = "paper" }: Presentat
<div <div
className="presentation-canvas" className="presentation-canvas"
data-testid="presentation-canvas" data-testid="presentation-canvas"
data-stage-theme={stageTheme}
> >
{children} {children}
</div> </div>
@@ -13,7 +13,7 @@ import {
presentationReducer, presentationReducer,
} from "./presentation-state.js"; } from "./presentation-state.js";
import { hashForLocation } from "./storyboard-navigation.js"; import { hashForLocation } from "./storyboard-navigation.js";
import { findScene, type MainLocation } from "./storyboard.js"; import type { MainLocation } from "./storyboard.js";
import type { DemoApprovalActions, DemoApprovalUiState } from "./demo-approval-actions.js"; import type { DemoApprovalActions, DemoApprovalUiState } from "./demo-approval-actions.js";
import "./presentation.css"; import "./presentation.css";
import "./styles/demo-workflow.css"; import "./styles/demo-workflow.css";
@@ -224,13 +224,9 @@ export const PresentationRoute = () => {
[], [],
); );
const stageTheme = state.location.kind === "main"
? findScene(state.location.sceneId)?.stageTheme ?? "paper"
: "paper";
return ( return (
<main className="presentation-route" aria-label="lda.chat presentation" data-motion={state.motionDisabled ? "disabled" : "enabled"}> <main className="presentation-route" aria-label="lda.chat presentation" data-motion={state.motionDisabled ? "disabled" : "enabled"}>
<PresentationCanvas stageTheme={stageTheme}> <PresentationCanvas>
<PresentationStage <PresentationStage
state={state} state={state}
demo={demo} demo={demo}
@@ -62,9 +62,16 @@ const DiscussionLinks = ({
); );
}; };
const actForSceneNumber = (sceneNumber: number): string => {
if (sceneNumber <= 3) return "I";
if (sceneNumber <= 12) return "II";
if (sceneNumber === 13) return "III";
return "IV";
};
const NarrativeScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => ( const NarrativeScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => (
<> <>
<StageCaption eyebrow={`Act ${scene.stageTheme === "paper" ? "I" : "II"} · ${scene.claimClass}`} title={scene.title}> <StageCaption eyebrow={`Act ${actForSceneNumber(scene.number)} · ${scene.claimClass}`} title={scene.title}>
<p>{beat.caption}</p> <p>{beat.caption}</p>
</StageCaption> </StageCaption>
<p className="scene-body__evidence">{scene.evidencePointer}</p> <p className="scene-body__evidence">{scene.evidencePointer}</p>
@@ -8,7 +8,6 @@ const scene = {
title: "Contribution and future work", title: "Contribution and future work",
claimClass: "future-work" as const, claimClass: "future-work" as const,
evidencePointer: "Thesis conclusion", evidencePointer: "Thesis conclusion",
stageTheme: "night" as const,
view: "conclusion" as const, view: "conclusion" as const,
beats: [], beats: [],
}; };
@@ -8,7 +8,6 @@ const scene = {
title: "Evaluation", title: "Evaluation",
claimClass: "evaluated" as const, claimClass: "evaluated" as const,
evidencePointer: "Thesis Evaluation and Appendix C", evidencePointer: "Thesis Evaluation and Appendix C",
stageTheme: "paper" as const,
view: "evaluation" as const, view: "evaluation" as const,
beats: [], beats: [],
}; };
@@ -193,7 +193,7 @@
/* Semantic kind colors */ /* Semantic kind colors */
.figure-node[data-figure-node-kind="actor"], .figure-node[data-figure-node-kind="actor"],
.figure-node[data-figure-node-kind="operation"] { .figure-node[data-figure-node-kind="operation"] {
border-color: var(--color-intent, oklch(0.53 0.17 250)); border-color: var(--color-editorial-ink, oklch(0.19 0.015 65));
} }
.figure-node[data-figure-node-kind="runtime"] { .figure-node[data-figure-node-kind="runtime"] {
@@ -245,14 +245,6 @@
font-weight: 600; font-weight: 600;
} }
.presentation-canvas[data-stage-theme="night"] .figure-breadcrumbs {
color: var(--text-secondary, oklch(0.78 0.035 250));
}
.presentation-canvas[data-stage-theme="night"] .figure-breadcrumbs__crumb[aria-current="page"] {
color: var(--text-primary, oklch(0.96 0.01 250));
}
.figure-breadcrumbs__crumb + .figure-breadcrumbs__crumb::before { .figure-breadcrumbs__crumb + .figure-breadcrumbs__crumb::before {
content: "\203A"; content: "\203A";
margin-right: 4px; margin-right: 4px;
@@ -19,31 +19,20 @@
--chat-width: 18rem; --chat-width: 18rem;
} }
.presentation-canvas[data-stage-theme="night"] .presentation-stage__primary { .presentation-stage__primary {
background: var(--stage-canvas);
color: var(--text-primary);
}
.presentation-canvas[data-stage-theme="paper"] .presentation-stage__primary {
background: var(--color-editorial-paper, oklch(0.975 0.012 82)); background: var(--color-editorial-paper, oklch(0.975 0.012 82));
color: var(--color-editorial-ink, oklch(0.19 0.015 65)); color: var(--color-editorial-ink, oklch(0.19 0.015 65));
} }
/* The console's global section rule must not turn night-mode slide roots white. */ /* Presentation roots own their visual surfaces; do not inherit console cards. */
.presentation-canvas[data-stage-theme="night"] .presentation-stage__primary > .stage-caption, .presentation-stage__primary > .architecture-scene,
.presentation-canvas[data-stage-theme="night"] .architecture-scene > .stage-caption, .presentation-stage__primary > .scene-body__authoring-composition,
.presentation-canvas[data-stage-theme="night"] .presentation-stage__primary > .architecture-scene, .presentation-stage__primary > .prepared-lifecycle-scene {
.presentation-canvas[data-stage-theme="night"] .presentation-stage__primary > .scene-body__authoring-composition { margin: 0;
border: 0;
border-radius: 0;
padding: 0;
background: transparent; background: transparent;
color: var(--text-primary);
border-color: var(--stage-line);
}
.presentation-canvas[data-stage-theme="night"] .presentation-stage__primary > .prepared-lifecycle-scene {
background: var(--stage-canvas);
color: var(--text-primary);
border-color: var(--stage-line);
margin-bottom: 0;
} }
.presentation-stage { .presentation-stage {
@@ -295,7 +284,7 @@
} }
.stage-caption__eyebrow { .stage-caption__eyebrow {
color: oklch(0.78 0.035 250); color: var(--color-editorial-muted, oklch(0.48 0.025 65));
font-size: 0.8rem; font-size: 0.8rem;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.08em; letter-spacing: 0.08em;
@@ -326,7 +315,7 @@
.scene-body__evidence { .scene-body__evidence {
flex-shrink: 0; flex-shrink: 0;
margin: 0.55rem 0 0; margin: 0.55rem 0 0;
color: oklch(0.62 0.05 250); color: var(--color-editorial-muted, oklch(0.48 0.025 65));
font-size: 0.8rem; font-size: 0.8rem;
} }
@@ -37,7 +37,6 @@ const mockScene = {
title: "Architecture Zoom", title: "Architecture Zoom",
claimClass: "implemented" as const, claimClass: "implemented" as const,
evidencePointer: "Thesis System Architecture", evidencePointer: "Thesis System Architecture",
stageTheme: "night" as const,
view: "architecture" as const, view: "architecture" as const,
beats: [], beats: [],
}; };
@@ -42,10 +42,7 @@ describe("defense storyboard catalog", () => {
expect(findBeat("conclusion", "questions")?.chatMode).toBe("hidden"); expect(findBeat("conclusion", "questions")?.chatMode).toBe("hidden");
}); });
it("uses act-level stage themes and independent chat composition", () => { it("uses one editorial canvas theme and independent chat composition", () => {
expect(mainScenes.slice(0, 3).every((scene) => scene.stageTheme === "paper")).toBe(true);
expect(mainScenes.slice(3, 12).every((scene) => scene.stageTheme === "night")).toBe(true);
expect(mainScenes.slice(12).every((scene) => scene.stageTheme === "paper")).toBe(true);
expect(findBeat("agent-handoff", "request")?.chatMode).toBe("hidden"); expect(findBeat("agent-handoff", "request")?.chatMode).toBe("hidden");
expect(findBeat("resume-output-evidence", "trace")?.chatMode).toBe("hidden"); expect(findBeat("resume-output-evidence", "trace")?.chatMode).toBe("hidden");
}); });
@@ -1,5 +1,4 @@
export type ClaimClass = "motivation" | "implemented" | "evaluated" | "external-context" | "future-work"; export type ClaimClass = "motivation" | "implemented" | "evaluated" | "external-context" | "future-work";
export type StageTheme = "paper" | "night";
export type ChatTheme = "light" | "dark"; export type ChatTheme = "light" | "dark";
export type ChatMode = "hidden" | "full" | "rail" | "dock"; export type ChatMode = "hidden" | "full" | "rail" | "dock";
export type EvidencePresentation = "hidden" | "receipt" | "inspector"; export type EvidencePresentation = "hidden" | "receipt" | "inspector";
@@ -39,7 +38,6 @@ export type SceneDefinition = {
readonly title: string; readonly title: string;
readonly claimClass: ClaimClass; readonly claimClass: ClaimClass;
readonly evidencePointer: string; readonly evidencePointer: string;
readonly stageTheme: StageTheme;
readonly view: SceneView; readonly view: SceneView;
readonly beats: readonly SceneBeatDefinition[]; readonly beats: readonly SceneBeatDefinition[];
}; };
@@ -68,7 +66,6 @@ export const mainScenes = defineScenes([
title: "Thesis", title: "Thesis",
claimClass: "implemented", claimClass: "implemented",
evidencePointer: "Thesis Abstract and Introduction", evidencePointer: "Thesis Abstract and Introduction",
stageTheme: "paper",
view: "narrative", view: "narrative",
beats: [ beats: [
sceneBeat("title", "Design and Implementation of lda.chat", "The project began as a pursuit of an AI agent for workspace automation."), sceneBeat("title", "Design and Implementation of lda.chat", "The project began as a pursuit of an AI agent for workspace automation."),
@@ -81,7 +78,6 @@ export const mainScenes = defineScenes([
title: "The Problem", title: "The Problem",
claimClass: "motivation", claimClass: "motivation",
evidencePointer: "Thesis Problem Statement and Requirements", evidencePointer: "Thesis Problem Statement and Requirements",
stageTheme: "paper",
view: "narrative", view: "narrative",
beats: [ beats: [
sceneBeat("direct-actions", "Direct actions are not reusable automation", "A tool loop can act without owning durable lifecycle records."), sceneBeat("direct-actions", "Direct actions are not reusable automation", "A tool loop can act without owning durable lifecycle records."),
@@ -94,7 +90,6 @@ export const mainScenes = defineScenes([
title: "Positioning and Related Systems", title: "Positioning and Related Systems",
claimClass: "motivation", claimClass: "motivation",
evidencePointer: "Thesis Positioning and Related Systems", evidencePointer: "Thesis Positioning and Related Systems",
stageTheme: "paper",
view: "positioning", view: "positioning",
beats: [ beats: [
sceneBeat("landscape", "Different centers of gravity", "Tool loops, scripts, hosted automation, agent graphs, and MCP solve different parts of the problem."), sceneBeat("landscape", "Different centers of gravity", "Tool loops, scripts, hosted automation, agent graphs, and MCP solve different parts of the problem."),
@@ -107,7 +102,6 @@ export const mainScenes = defineScenes([
title: "Planner and Runtime", title: "Planner and Runtime",
claimClass: "implemented", claimClass: "implemented",
evidencePointer: "Thesis Architecture Overview", evidencePointer: "Thesis Architecture Overview",
stageTheme: "night",
view: "boundary", view: "boundary",
beats: [ beats: [
sceneBeat("planner", "External planner", "The planner proposes and revises workflow structure."), sceneBeat("planner", "External planner", "The planner proposes and revises workflow structure."),
@@ -121,7 +115,6 @@ export const mainScenes = defineScenes([
title: "Workflow Lifecycle", title: "Workflow Lifecycle",
claimClass: "implemented", claimClass: "implemented",
evidencePointer: "Thesis Workflow Lifecycle", evidencePointer: "Thesis Workflow Lifecycle",
stageTheme: "night",
view: "lifecycle", view: "lifecycle",
beats: [ beats: [
sceneBeat("draft", "Draft", "Mutable iterative authoring state."), sceneBeat("draft", "Draft", "Mutable iterative authoring state."),
@@ -136,7 +129,6 @@ export const mainScenes = defineScenes([
title: "Architecture Zoom", title: "Architecture Zoom",
claimClass: "implemented", claimClass: "implemented",
evidencePointer: "Thesis System Architecture; docs/project_map.md; docs/source_architecture.md", evidencePointer: "Thesis System Architecture; docs/project_map.md; docs/source_architecture.md",
stageTheme: "night",
view: "architecture", view: "architecture",
beats: [ beats: [
sceneBeat("client", "Client operations", "Human and agent clients use the same public lifecycle surface.", { figure: { catalogId: "system-architecture", focusPath: [], activeNodeId: "client-operations" } }), sceneBeat("client", "Client operations", "Human and agent clients use the same public lifecycle surface.", { figure: { catalogId: "system-architecture", focusPath: [], activeNodeId: "client-operations" } }),
@@ -151,7 +143,6 @@ export const mainScenes = defineScenes([
title: "Author, Validate, Repair", title: "Author, Validate, Repair",
claimClass: "implemented", claimClass: "implemented",
evidencePointer: "CLI documentation; draft authoring API; challenge UX findings", evidencePointer: "CLI documentation; draft authoring API; challenge UX findings",
stageTheme: "night",
view: "authoring", view: "authoring",
beats: [ beats: [
sceneBeat("discover", "Discover", "Inspect capabilities and schemas before authoring."), sceneBeat("discover", "Discover", "Inspect capabilities and schemas before authoring."),
@@ -166,7 +157,6 @@ export const mainScenes = defineScenes([
title: "Agent Handoff", title: "Agent Handoff",
claimClass: "implemented", claimClass: "implemented",
evidencePointer: "Constrained demo agent and prepared replay recipe", evidencePointer: "Constrained demo agent and prepared replay recipe",
stageTheme: "night",
view: "agent", view: "agent",
beats: [ beats: [
// Scene 8 renders its own full-screen prepared transcript in the primary // Scene 8 renders its own full-screen prepared transcript in the primary
@@ -181,7 +171,6 @@ export const mainScenes = defineScenes([
title: "Prepared Workflow Lifecycle", title: "Prepared Workflow Lifecycle",
claimClass: "implemented", claimClass: "implemented",
evidencePointer: "examples/lda_report_workflow; deployment inspect replay evidence", evidencePointer: "examples/lda_report_workflow; deployment inspect replay evidence",
stageTheme: "night",
view: "demo-lifecycle", view: "demo-lifecycle",
beats: [ beats: [
sceneBeat("discover", "Discover capabilities", "Inspect available sources, capabilities, and schemas before authoring.", { chatMode: "hidden", chatTheme: "light" }), sceneBeat("discover", "Discover capabilities", "Inspect available sources, capabilities, and schemas before authoring.", { chatMode: "hidden", chatTheme: "light" }),
@@ -197,7 +186,6 @@ export const mainScenes = defineScenes([
title: "Run From Deployment", title: "Run From Deployment",
claimClass: "implemented", claimClass: "implemented",
evidencePointer: "workflow.runs.start replay evidence", evidencePointer: "workflow.runs.start replay evidence",
stageTheme: "night",
view: "demo", view: "demo",
beats: [ beats: [
sceneBeat("input", "Workflow input", "The run starts from selected documents and an issue board path.", { chatMode: "hidden", chatTheme: "light" }), sceneBeat("input", "Workflow input", "The run starts from selected documents and an issue board path.", { chatMode: "hidden", chatTheme: "light" }),
@@ -211,7 +199,6 @@ export const mainScenes = defineScenes([
title: "Typed Human Boundary", title: "Typed Human Boundary",
claimClass: "implemented", claimClass: "implemented",
evidencePointer: "typed interrupt payload and resume contract", evidencePointer: "typed interrupt payload and resume contract",
stageTheme: "night",
view: "demo", view: "demo",
beats: [ beats: [
sceneBeat("interrupt", "Interrupt payload", "Execution reaches a declared issue-review boundary.", { chatMode: "hidden", chatTheme: "light" }), sceneBeat("interrupt", "Interrupt payload", "Execution reaches a declared issue-review boundary.", { chatMode: "hidden", chatTheme: "light" }),
@@ -225,7 +212,6 @@ export const mainScenes = defineScenes([
title: "Resume, Output, Evidence", title: "Resume, Output, Evidence",
claimClass: "implemented", claimClass: "implemented",
evidencePointer: "workflow.runs.resume, workflow output, and trace replay evidence", evidencePointer: "workflow.runs.resume, workflow output, and trace replay evidence",
stageTheme: "night",
view: "demo", view: "demo",
beats: [ beats: [
sceneBeat("resume", "Resume", "The submitted payload resumes the same persisted run.", { chatMode: "hidden", chatTheme: "light" }), sceneBeat("resume", "Resume", "The submitted payload resumes the same persisted run.", { chatMode: "hidden", chatTheme: "light" }),
@@ -239,7 +225,6 @@ export const mainScenes = defineScenes([
title: "Evaluation", title: "Evaluation",
claimClass: "evaluated", claimClass: "evaluated",
evidencePointer: "Thesis Evaluation and Appendix C", evidencePointer: "Thesis Evaluation and Appendix C",
stageTheme: "paper",
view: "evaluation", view: "evaluation",
beats: [ beats: [
sceneBeat("cohort", "36-trial cohort", "Two challenges, two hosted models, three profiles, and three waves.", { chatMode: "hidden" }), sceneBeat("cohort", "36-trial cohort", "Two challenges, two hosted models, three profiles, and three waves.", { chatMode: "hidden" }),
@@ -253,7 +238,6 @@ export const mainScenes = defineScenes([
title: "Limits and Conclusion", title: "Limits and Conclusion",
claimClass: "future-work", claimClass: "future-work",
evidencePointer: "Thesis Limitations, Future Work, and Conclusion", evidencePointer: "Thesis Limitations, Future Work, and Conclusion",
stageTheme: "paper",
view: "conclusion", view: "conclusion",
beats: [ beats: [
sceneBeat("limits", "Implemented boundary", "The prototype is not a production sandbox, scheduler, or broad agent benchmark.", { chatMode: "hidden" }), sceneBeat("limits", "Implemented boundary", "The prototype is not a production sandbox, scheduler, or broad agent benchmark.", { chatMode: "hidden" }),
@@ -40,8 +40,3 @@
container-name: presentation-canvas; container-name: presentation-canvas;
container-type: inline-size; container-type: inline-size;
} }
.presentation-canvas[data-stage-theme="night"] {
background: oklch(0.12 0.025 250);
color: oklch(0.96 0.01 250);
}