feat: add presentation opening thesis visual

This commit is contained in:
lda
2026-07-10 03:39:17 +07:00 Verified
parent a7acace668
commit 9a42e2bfc3
5 changed files with 152 additions and 0 deletions
@@ -40,6 +40,28 @@ const demo: DemoTimelineController = {
afterEach(() => cleanup());
describe("SceneBody", () => {
it("renders Scene 1 as an opening decomposition visual", () => {
const location: PresentationLocation = { kind: "main", sceneId: "thesis", beatId: "substrate", focusPath: [] };
render(
<SceneBody
location={location}
demo={demo}
selectedNodeId={null}
selectNode={noop}
openEvidence={noop}
openDiscussion={noop}
onFocusPathChange={noop}
motionDisabled={false}
/>,
);
expect(screen.getByLabelText("AI agent decomposition")).toBeInTheDocument();
expect(screen.getByText("submitted substrate")).toBeInTheDocument();
expect(screen.getByText("Typed · Durable · Inspectable")).toBeInTheDocument();
expect(screen.getByRole("button", { name: /where is the ai agent/i })).toBeInTheDocument();
});
it("renders narrative metadata without mounting the demo graph", () => {
const location: PresentationLocation = { kind: "main", sceneId: "positioning", beatId: "landscape", focusPath: [] };
render(
@@ -12,6 +12,7 @@ import { DemoLifecycleScene } from "./DemoLifecycleScene.js";
import { DemoWorkflowScene } from "./DemoWorkflowScene.js";
import { StageCaption } from "./StageCaption.js";
import { ArchitectureScene } from "./scenes/ArchitectureScene.js";
import { OpeningThesisScene } from "./opening/OpeningThesisScene.js";
type SceneBodyProps = {
readonly location: PresentationLocation;
@@ -304,6 +305,7 @@ export const SceneBody = ({ location, demo, selectedNodeId, selectNode, openEvid
const content = (() => {
switch (scene.view) {
case "narrative":
if (scene.id === "thesis") return <OpeningThesisScene scene={scene} beat={beat} />;
return <NarrativeScene scene={scene} beat={beat} />;
case "positioning":
return <PositioningScene scene={scene} beat={beat} />;
@@ -0,0 +1,32 @@
import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";
import { findBeat, findScene } from "../storyboard.js";
import { OpeningThesisScene } from "./OpeningThesisScene.js";
const thesisScene = findScene("thesis")!;
afterEach(() => cleanup());
describe("OpeningThesisScene", () => {
it("renders the title reveal with latent component icons", () => {
render(<OpeningThesisScene scene={thesisScene} beat={findBeat("thesis", "title")!} />);
expect(screen.getByRole("heading", { name: /Design and Implementation of lda\.chat/i })).toBeInTheDocument();
expect(screen.getByText("Planner")).toBeInTheDocument();
expect(screen.getByText("Tool Surface")).toBeInTheDocument();
expect(screen.getByText("Workflow Platform")).toBeInTheDocument();
expect(screen.getByRole("img", { name: /Planner icon/i })).toBeInTheDocument();
expect(screen.getByRole("img", { name: /Tool Surface icon/i })).toBeInTheDocument();
expect(screen.getByRole("img", { name: /Workflow Platform icon/i })).toBeInTheDocument();
});
it("renders contribution focus without relying on lda.chat as category label", () => {
render(<OpeningThesisScene scene={thesisScene} beat={findBeat("thesis", "substrate")!} />);
expect(screen.getByText("Codex / Claude / OpenCode")).toBeInTheDocument();
expect(screen.getByText("CLI / MCP / APIs")).toBeInTheDocument();
expect(screen.getByText("submitted substrate")).toBeInTheDocument();
expect(screen.getByText("Typed · Durable · Inspectable")).toBeInTheDocument();
expect(screen.queryByText("wf")).not.toBeInTheDocument();
});
});
@@ -0,0 +1,54 @@
import { StageCaption } from "../StageCaption.js";
import type { SceneBeatDefinition, SceneDefinition } from "../storyboard.js";
import { ConceptNode, ConceptRail } from "./ConceptPrimitives.js";
type OpeningThesisSceneProps = {
readonly scene: SceneDefinition;
readonly beat: SceneBeatDefinition;
};
const title = "Design and Implementation of lda.chat";
export const OpeningThesisScene = ({ scene, beat }: OpeningThesisSceneProps) => {
const contributionBeat = beat.id === "substrate";
return (
<>
<StageCaption eyebrow="Origin story" title={title}>
<p>{beat.caption}</p>
</StageCaption>
<section
className="opening-thesis"
aria-label="AI agent decomposition"
data-opening-beat={beat.id}
>
<div className="opening-thesis__title-card">
<span>AI agent for workspace workflows</span>
<strong>{contributionBeat ? "Decomposed" : scene.title}</strong>
</div>
<ConceptRail label="AI agent components">
<ConceptNode
title="Planner"
subtitle="Codex / Claude / OpenCode"
icon="planner"
emphasis={contributionBeat ? "muted" : "normal"}
/>
<ConceptNode
title="Tool Surface"
subtitle="CLI / MCP / APIs"
icon="tool"
emphasis={contributionBeat ? "muted" : "normal"}
/>
<ConceptNode
title="Workflow Platform"
subtitle="submitted substrate"
icon="platform"
emphasis="primary"
>
<span>Typed · Durable · Inspectable</span>
</ConceptNode>
</ConceptRail>
</section>
<p className="scene-body__evidence">{scene.evidencePointer}</p>
</>
);
};
@@ -1359,6 +1359,48 @@
font: 700 0.72rem/1.2 var(--font-evidence);
}
/* Opening thesis scene */
.opening-thesis {
flex: 1 1 auto;
min-height: 0;
display: grid;
grid-template-rows: minmax(8rem, 0.85fr) auto;
gap: 1rem;
align-items: center;
}
.opening-thesis__title-card {
display: grid;
place-items: center;
gap: 0.5rem;
min-height: 10rem;
border: 1px solid color-mix(in oklch, var(--stage-line) 54%, transparent);
border-radius: 1rem;
background:
radial-gradient(circle at 30% 30%, color-mix(in oklch, var(--accent-cyan) 12%, transparent), transparent 34%),
var(--stage-inset);
text-align: center;
}
.opening-thesis__title-card span {
color: var(--text-secondary);
font: 700 0.85rem/1 var(--font-evidence);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.opening-thesis__title-card strong {
max-width: 14ch;
color: var(--text-primary);
font: 800 clamp(2rem, 7vw, 4.8rem)/0.92 var(--font-editorial);
text-wrap: balance;
}
.opening-thesis .concept-rail {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
/* Demo workflow scene */
.demo-workflow-scene__graph-area {
flex: 1;