feat: add presentation opening thesis visual
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user