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).
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,
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).
- Presenter runbook:
[`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
chat to `rail`.
#### Stage Themes
#### Presentation Canvas
Stage themes change by act: `paper` for scenes 1-3 and 13-14, `night` for
scenes 4-12. Presenter controls allow overriding stage and chat themes
independently during rehearsal.
The presentation uses one warm Editorial Canvas across all scenes. Dark
surfaces are local to product evidence such as chat, graphs, traces, and
terminal-style operation blocks; they are not whole-stage themes.
#### Discussion Branches
@@ -17,17 +17,11 @@ describe("PresentationCanvas", () => {
const canvas = screen.getByTestId("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).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", () => {
expect(editorialCss).toContain("width: min(100dvw, calc(100dvh * 16 / 9))");
expect(editorialCss).toContain("height: min(100dvh, calc(100dvw * 9 / 12))");
@@ -1,12 +1,10 @@
import type { ReactNode } from "react";
import type { StageTheme } from "./storyboard.js";
type PresentationCanvasProps = {
readonly children: ReactNode;
readonly stageTheme?: StageTheme;
};
export const PresentationCanvas = ({ children, stageTheme = "paper" }: PresentationCanvasProps) => (
export const PresentationCanvas = ({ children }: PresentationCanvasProps) => (
<div className="presentation-viewport">
{/*
Keep the presentation as normal responsive DOM instead of scaling the
@@ -17,7 +15,6 @@ export const PresentationCanvas = ({ children, stageTheme = "paper" }: Presentat
<div
className="presentation-canvas"
data-testid="presentation-canvas"
data-stage-theme={stageTheme}
>
{children}
</div>
@@ -13,7 +13,7 @@ import {
presentationReducer,
} from "./presentation-state.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 "./presentation.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 (
<main className="presentation-route" aria-label="lda.chat presentation" data-motion={state.motionDisabled ? "disabled" : "enabled"}>
<PresentationCanvas stageTheme={stageTheme}>
<PresentationCanvas>
<PresentationStage
state={state}
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 }) => (
<>
<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>
</StageCaption>
<p className="scene-body__evidence">{scene.evidencePointer}</p>
@@ -8,7 +8,6 @@ const scene = {
title: "Contribution and future work",
claimClass: "future-work" as const,
evidencePointer: "Thesis conclusion",
stageTheme: "night" as const,
view: "conclusion" as const,
beats: [],
};
@@ -8,7 +8,6 @@ const scene = {
title: "Evaluation",
claimClass: "evaluated" as const,
evidencePointer: "Thesis Evaluation and Appendix C",
stageTheme: "paper" as const,
view: "evaluation" as const,
beats: [],
};
@@ -193,7 +193,7 @@
/* Semantic kind colors */
.figure-node[data-figure-node-kind="actor"],
.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"] {
@@ -245,14 +245,6 @@
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 {
content: "\203A";
margin-right: 4px;
@@ -19,31 +19,20 @@
--chat-width: 18rem;
}
.presentation-canvas[data-stage-theme="night"] .presentation-stage__primary {
background: var(--stage-canvas);
color: var(--text-primary);
}
.presentation-canvas[data-stage-theme="paper"] .presentation-stage__primary {
.presentation-stage__primary {
background: var(--color-editorial-paper, oklch(0.975 0.012 82));
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-canvas[data-stage-theme="night"] .presentation-stage__primary > .stage-caption,
.presentation-canvas[data-stage-theme="night"] .architecture-scene > .stage-caption,
.presentation-canvas[data-stage-theme="night"] .presentation-stage__primary > .architecture-scene,
.presentation-canvas[data-stage-theme="night"] .presentation-stage__primary > .scene-body__authoring-composition {
/* Presentation roots own their visual surfaces; do not inherit console cards. */
.presentation-stage__primary > .architecture-scene,
.presentation-stage__primary > .scene-body__authoring-composition,
.presentation-stage__primary > .prepared-lifecycle-scene {
margin: 0;
border: 0;
border-radius: 0;
padding: 0;
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 {
@@ -295,7 +284,7 @@
}
.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;
text-transform: uppercase;
letter-spacing: 0.08em;
@@ -326,7 +315,7 @@
.scene-body__evidence {
flex-shrink: 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;
}
@@ -37,7 +37,6 @@ const mockScene = {
title: "Architecture Zoom",
claimClass: "implemented" as const,
evidencePointer: "Thesis System Architecture",
stageTheme: "night" as const,
view: "architecture" as const,
beats: [],
};
@@ -42,10 +42,7 @@ describe("defense storyboard catalog", () => {
expect(findBeat("conclusion", "questions")?.chatMode).toBe("hidden");
});
it("uses act-level stage themes 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);
it("uses one editorial canvas theme and independent chat composition", () => {
expect(findBeat("agent-handoff", "request")?.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 StageTheme = "paper" | "night";
export type ChatTheme = "light" | "dark";
export type ChatMode = "hidden" | "full" | "rail" | "dock";
export type EvidencePresentation = "hidden" | "receipt" | "inspector";
@@ -39,7 +38,6 @@ export type SceneDefinition = {
readonly title: string;
readonly claimClass: ClaimClass;
readonly evidencePointer: string;
readonly stageTheme: StageTheme;
readonly view: SceneView;
readonly beats: readonly SceneBeatDefinition[];
};
@@ -68,7 +66,6 @@ export const mainScenes = defineScenes([
title: "Thesis",
claimClass: "implemented",
evidencePointer: "Thesis Abstract and Introduction",
stageTheme: "paper",
view: "narrative",
beats: [
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",
claimClass: "motivation",
evidencePointer: "Thesis Problem Statement and Requirements",
stageTheme: "paper",
view: "narrative",
beats: [
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",
claimClass: "motivation",
evidencePointer: "Thesis Positioning and Related Systems",
stageTheme: "paper",
view: "positioning",
beats: [
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",
claimClass: "implemented",
evidencePointer: "Thesis Architecture Overview",
stageTheme: "night",
view: "boundary",
beats: [
sceneBeat("planner", "External planner", "The planner proposes and revises workflow structure."),
@@ -121,7 +115,6 @@ export const mainScenes = defineScenes([
title: "Workflow Lifecycle",
claimClass: "implemented",
evidencePointer: "Thesis Workflow Lifecycle",
stageTheme: "night",
view: "lifecycle",
beats: [
sceneBeat("draft", "Draft", "Mutable iterative authoring state."),
@@ -136,7 +129,6 @@ export const mainScenes = defineScenes([
title: "Architecture Zoom",
claimClass: "implemented",
evidencePointer: "Thesis System Architecture; docs/project_map.md; docs/source_architecture.md",
stageTheme: "night",
view: "architecture",
beats: [
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",
claimClass: "implemented",
evidencePointer: "CLI documentation; draft authoring API; challenge UX findings",
stageTheme: "night",
view: "authoring",
beats: [
sceneBeat("discover", "Discover", "Inspect capabilities and schemas before authoring."),
@@ -166,7 +157,6 @@ export const mainScenes = defineScenes([
title: "Agent Handoff",
claimClass: "implemented",
evidencePointer: "Constrained demo agent and prepared replay recipe",
stageTheme: "night",
view: "agent",
beats: [
// Scene 8 renders its own full-screen prepared transcript in the primary
@@ -181,7 +171,6 @@ export const mainScenes = defineScenes([
title: "Prepared Workflow Lifecycle",
claimClass: "implemented",
evidencePointer: "examples/lda_report_workflow; deployment inspect replay evidence",
stageTheme: "night",
view: "demo-lifecycle",
beats: [
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",
claimClass: "implemented",
evidencePointer: "workflow.runs.start replay evidence",
stageTheme: "night",
view: "demo",
beats: [
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",
claimClass: "implemented",
evidencePointer: "typed interrupt payload and resume contract",
stageTheme: "night",
view: "demo",
beats: [
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",
claimClass: "implemented",
evidencePointer: "workflow.runs.resume, workflow output, and trace replay evidence",
stageTheme: "night",
view: "demo",
beats: [
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",
claimClass: "evaluated",
evidencePointer: "Thesis Evaluation and Appendix C",
stageTheme: "paper",
view: "evaluation",
beats: [
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",
claimClass: "future-work",
evidencePointer: "Thesis Limitations, Future Work, and Conclusion",
stageTheme: "paper",
view: "conclusion",
beats: [
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-type: inline-size;
}
.presentation-canvas[data-stage-theme="night"] {
background: oklch(0.12 0.025 250);
color: oklch(0.96 0.01 250);
}