feat: add presentation opening thesis visual
This commit is contained in:
@@ -40,6 +40,28 @@ const demo: DemoTimelineController = {
|
|||||||
afterEach(() => cleanup());
|
afterEach(() => cleanup());
|
||||||
|
|
||||||
describe("SceneBody", () => {
|
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", () => {
|
it("renders narrative metadata without mounting the demo graph", () => {
|
||||||
const location: PresentationLocation = { kind: "main", sceneId: "positioning", beatId: "landscape", focusPath: [] };
|
const location: PresentationLocation = { kind: "main", sceneId: "positioning", beatId: "landscape", focusPath: [] };
|
||||||
render(
|
render(
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { DemoLifecycleScene } from "./DemoLifecycleScene.js";
|
|||||||
import { DemoWorkflowScene } from "./DemoWorkflowScene.js";
|
import { DemoWorkflowScene } from "./DemoWorkflowScene.js";
|
||||||
import { StageCaption } from "./StageCaption.js";
|
import { StageCaption } from "./StageCaption.js";
|
||||||
import { ArchitectureScene } from "./scenes/ArchitectureScene.js";
|
import { ArchitectureScene } from "./scenes/ArchitectureScene.js";
|
||||||
|
import { OpeningThesisScene } from "./opening/OpeningThesisScene.js";
|
||||||
|
|
||||||
type SceneBodyProps = {
|
type SceneBodyProps = {
|
||||||
readonly location: PresentationLocation;
|
readonly location: PresentationLocation;
|
||||||
@@ -304,6 +305,7 @@ export const SceneBody = ({ location, demo, selectedNodeId, selectNode, openEvid
|
|||||||
const content = (() => {
|
const content = (() => {
|
||||||
switch (scene.view) {
|
switch (scene.view) {
|
||||||
case "narrative":
|
case "narrative":
|
||||||
|
if (scene.id === "thesis") return <OpeningThesisScene scene={scene} beat={beat} />;
|
||||||
return <NarrativeScene scene={scene} beat={beat} />;
|
return <NarrativeScene scene={scene} beat={beat} />;
|
||||||
case "positioning":
|
case "positioning":
|
||||||
return <PositioningScene scene={scene} beat={beat} />;
|
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);
|
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 */
|
||||||
.demo-workflow-scene__graph-area {
|
.demo-workflow-scene__graph-area {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user