feat: give opening scenes a clear visual focal point
This commit is contained in:
@@ -69,7 +69,10 @@ describe("SceneBody", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByLabelText("AI agent decomposition")).toBeInTheDocument();
|
expect(screen.getByRole("region", { name: "thesis opening" })).toHaveAttribute(
|
||||||
|
"data-opening-focus",
|
||||||
|
"substrate",
|
||||||
|
);
|
||||||
expect(screen.getByText("submitted substrate")).toBeInTheDocument();
|
expect(screen.getByText("submitted substrate")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Typed · Durable · Inspectable")).toBeInTheDocument();
|
expect(screen.getByText("Typed · Durable · Inspectable")).toBeInTheDocument();
|
||||||
expect(screen.getByRole("button", { name: /where is the ai agent/i })).toBeInTheDocument();
|
expect(screen.getByRole("button", { name: /where is the ai agent/i })).toBeInTheDocument();
|
||||||
|
|||||||
@@ -8,9 +8,12 @@ const thesisScene = findScene("thesis")!;
|
|||||||
afterEach(() => cleanup());
|
afterEach(() => cleanup());
|
||||||
|
|
||||||
describe("OpeningThesisScene", () => {
|
describe("OpeningThesisScene", () => {
|
||||||
it("renders the title reveal with latent component icons", () => {
|
it("makes the title boundary the opening focal artifact", () => {
|
||||||
render(<OpeningThesisScene scene={thesisScene} beat={findBeat("thesis", "title")!} />);
|
render(<OpeningThesisScene scene={thesisScene} beat={findBeat("thesis", "title")!} />);
|
||||||
|
|
||||||
|
const opening = screen.getByRole("region", { name: /thesis opening/i });
|
||||||
|
expect(opening).toHaveAttribute("data-opening-focus", "title");
|
||||||
|
expect(opening).toHaveAttribute("data-support-state", "receded");
|
||||||
expect(screen.getByRole("heading", { name: /Design and Implementation of lda\.chat/i })).toBeInTheDocument();
|
expect(screen.getByRole("heading", { name: /Design and Implementation of lda\.chat/i })).toBeInTheDocument();
|
||||||
expect(screen.getByText("Planner")).toBeInTheDocument();
|
expect(screen.getByText("Planner")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Tool Surface")).toBeInTheDocument();
|
expect(screen.getByText("Tool Surface")).toBeInTheDocument();
|
||||||
@@ -23,6 +26,9 @@ describe("OpeningThesisScene", () => {
|
|||||||
it("renders contribution focus without relying on lda.chat as category label", () => {
|
it("renders contribution focus without relying on lda.chat as category label", () => {
|
||||||
render(<OpeningThesisScene scene={thesisScene} beat={findBeat("thesis", "substrate")!} />);
|
render(<OpeningThesisScene scene={thesisScene} beat={findBeat("thesis", "substrate")!} />);
|
||||||
|
|
||||||
|
const opening = screen.getByRole("region", { name: /thesis opening/i });
|
||||||
|
expect(opening).toHaveAttribute("data-opening-focus", "substrate");
|
||||||
|
expect(opening).toHaveAttribute("data-support-state", "revealed");
|
||||||
expect(screen.getByText("Codex / Claude / OpenCode")).toBeInTheDocument();
|
expect(screen.getByText("Codex / Claude / OpenCode")).toBeInTheDocument();
|
||||||
expect(screen.getByText("CLI / MCP / APIs")).toBeInTheDocument();
|
expect(screen.getByText("CLI / MCP / APIs")).toBeInTheDocument();
|
||||||
expect(screen.getByText("submitted substrate")).toBeInTheDocument();
|
expect(screen.getByText("submitted substrate")).toBeInTheDocument();
|
||||||
|
|||||||
@@ -18,35 +18,39 @@ export const OpeningThesisScene = ({ scene, beat }: OpeningThesisSceneProps) =>
|
|||||||
</StageCaption>
|
</StageCaption>
|
||||||
<section
|
<section
|
||||||
className="opening-thesis"
|
className="opening-thesis"
|
||||||
aria-label="AI agent decomposition"
|
aria-label="thesis opening"
|
||||||
data-opening-beat={beat.id}
|
data-opening-beat={beat.id}
|
||||||
|
data-opening-focus={contributionBeat ? "substrate" : "title"}
|
||||||
|
data-support-state={contributionBeat ? "revealed" : "receded"}
|
||||||
>
|
>
|
||||||
<div className="opening-thesis__title-card">
|
<div className="opening-thesis__title-card" data-opening-role="title">
|
||||||
<span>AI agent for workspace workflows</span>
|
<span>AI agent for workspace workflows</span>
|
||||||
<strong>{contributionBeat ? "Decomposed" : scene.title}</strong>
|
<strong>{contributionBeat ? "Decomposed" : scene.title}</strong>
|
||||||
</div>
|
</div>
|
||||||
<ConceptRail label="AI agent components">
|
<div className="opening-thesis__decomposition">
|
||||||
<ConceptNode
|
<ConceptRail label="AI agent components">
|
||||||
title="Planner"
|
<ConceptNode
|
||||||
subtitle="Codex / Claude / OpenCode"
|
title="Planner"
|
||||||
icon="planner"
|
subtitle="Codex / Claude / OpenCode"
|
||||||
emphasis={contributionBeat ? "muted" : "normal"}
|
icon="planner"
|
||||||
/>
|
emphasis={contributionBeat ? "muted" : "normal"}
|
||||||
<ConceptNode
|
/>
|
||||||
title="Tool Surface"
|
<ConceptNode
|
||||||
subtitle="CLI / MCP / APIs"
|
title="Tool Surface"
|
||||||
icon="tool"
|
subtitle="CLI / MCP / APIs"
|
||||||
emphasis={contributionBeat ? "muted" : "normal"}
|
icon="tool"
|
||||||
/>
|
emphasis={contributionBeat ? "muted" : "normal"}
|
||||||
<ConceptNode
|
/>
|
||||||
title="Workflow Platform"
|
<ConceptNode
|
||||||
subtitle="submitted substrate"
|
title="Workflow Platform"
|
||||||
icon="platform"
|
subtitle="submitted substrate"
|
||||||
emphasis="primary"
|
icon="platform"
|
||||||
>
|
emphasis="primary"
|
||||||
<span>Typed · Durable · Inspectable</span>
|
>
|
||||||
</ConceptNode>
|
<span>Typed · Durable · Inspectable</span>
|
||||||
</ConceptRail>
|
</ConceptNode>
|
||||||
|
</ConceptRail>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<p className="scene-body__evidence">{scene.evidencePointer}</p>
|
<p className="scene-body__evidence">{scene.evidencePointer}</p>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ describe("ProblemLoopScene", () => {
|
|||||||
it("uses the assistant transcript surface for the direct-action side", async () => {
|
it("uses the assistant transcript surface for the direct-action side", async () => {
|
||||||
render(<ProblemLoopScene scene={problemScene} beat={findBeat("problem", "direct-actions")!} />);
|
render(<ProblemLoopScene scene={problemScene} beat={findBeat("problem", "direct-actions")!} />);
|
||||||
|
|
||||||
|
const scene = screen.getByRole("region", { name: /chat tool loop versus reusable automation/i });
|
||||||
|
expect(scene).toHaveAttribute("data-problem-focus", "tool-loop");
|
||||||
|
expect(scene).toHaveAttribute("data-support-state", "quiet");
|
||||||
const transcript = screen.getByRole("log", { name: /one-off assistant transcript/i });
|
const transcript = screen.getByRole("log", { name: /one-off assistant transcript/i });
|
||||||
expect(transcript).toHaveClass("assistant-operator-thread");
|
expect(transcript).toHaveClass("assistant-operator-thread");
|
||||||
expect(within(transcript).getByText("Can you finish this workspace task?")).toBeInTheDocument();
|
expect(within(transcript).getByText("Can you finish this workspace task?")).toBeInTheDocument();
|
||||||
@@ -23,6 +26,9 @@ describe("ProblemLoopScene", () => {
|
|||||||
it("renders reusable automation as a durable workflow blueprint", () => {
|
it("renders reusable automation as a durable workflow blueprint", () => {
|
||||||
render(<ProblemLoopScene scene={problemScene} beat={findBeat("problem", "missing-contracts")!} />);
|
render(<ProblemLoopScene scene={problemScene} beat={findBeat("problem", "missing-contracts")!} />);
|
||||||
|
|
||||||
|
const scene = screen.getByRole("region", { name: /chat tool loop versus reusable automation/i });
|
||||||
|
expect(scene).toHaveAttribute("data-problem-focus", "workflow-blueprint");
|
||||||
|
expect(scene).toHaveAttribute("data-support-state", "receded");
|
||||||
const blueprint = screen.getByRole("group", { name: /durable workflow blueprint/i });
|
const blueprint = screen.getByRole("group", { name: /durable workflow blueprint/i });
|
||||||
expect(blueprint).toHaveAttribute("data-blueprint-active", "true");
|
expect(blueprint).toHaveAttribute("data-blueprint-active", "true");
|
||||||
|
|
||||||
|
|||||||
@@ -58,10 +58,16 @@ export const ProblemLoopScene = ({ scene, beat }: ProblemLoopSceneProps) => {
|
|||||||
<StageCaption eyebrow="Problem shape" title={scene.title}>
|
<StageCaption eyebrow="Problem shape" title={scene.title}>
|
||||||
<p>{beat.caption}</p>
|
<p>{beat.caption}</p>
|
||||||
</StageCaption>
|
</StageCaption>
|
||||||
<section className="problem-loop-scene" aria-label="chat tool loop versus reusable automation">
|
<section
|
||||||
|
className="problem-loop-scene"
|
||||||
|
aria-label="chat tool loop versus reusable automation"
|
||||||
|
data-problem-focus={automationBeat ? "workflow-blueprint" : "tool-loop"}
|
||||||
|
data-support-state={automationBeat ? "receded" : "quiet"}
|
||||||
|
>
|
||||||
<article
|
<article
|
||||||
className="problem-chat-card"
|
className="problem-chat-card"
|
||||||
data-problem-active={automationBeat ? "false" : "true"}
|
data-problem-active={automationBeat ? "false" : "true"}
|
||||||
|
data-problem-role="tool-loop"
|
||||||
aria-label="one-off chat and tool loop"
|
aria-label="one-off chat and tool loop"
|
||||||
>
|
>
|
||||||
<header className="problem-artifact-header">
|
<header className="problem-artifact-header">
|
||||||
@@ -75,13 +81,14 @@ export const ProblemLoopScene = ({ scene, beat }: ProblemLoopSceneProps) => {
|
|||||||
<p className="problem-artifact-note">The useful work lives in the conversation history.</p>
|
<p className="problem-artifact-note">The useful work lives in the conversation history.</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<div className="problem-loop-scene__bridge" aria-hidden="true">→</div>
|
<div className="problem-loop-scene__bridge" aria-hidden="true">one request → durable definition</div>
|
||||||
|
|
||||||
<article
|
<article
|
||||||
className="problem-blueprint"
|
className="problem-blueprint"
|
||||||
role="group"
|
role="group"
|
||||||
aria-label="durable workflow blueprint"
|
aria-label="durable workflow blueprint"
|
||||||
data-blueprint-active={automationBeat ? "true" : "false"}
|
data-blueprint-active={automationBeat ? "true" : "false"}
|
||||||
|
data-problem-role="workflow-blueprint"
|
||||||
>
|
>
|
||||||
<header className="problem-artifact-header">
|
<header className="problem-artifact-header">
|
||||||
<span>Reusable</span>
|
<span>Reusable</span>
|
||||||
|
|||||||
@@ -1754,6 +1754,14 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.opening-thesis[data-opening-focus="title"] {
|
||||||
|
grid-template-rows: minmax(11rem, 1fr) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opening-thesis[data-opening-focus="substrate"] {
|
||||||
|
grid-template-rows: auto minmax(11rem, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
.opening-thesis__title-card {
|
.opening-thesis__title-card {
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
@@ -1765,6 +1773,16 @@
|
|||||||
radial-gradient(circle at 30% 30%, color-mix(in oklch, var(--accent-cyan) 12%, transparent), transparent 34%),
|
radial-gradient(circle at 30% 30%, color-mix(in oklch, var(--accent-cyan) 12%, transparent), transparent 34%),
|
||||||
var(--stage-inset);
|
var(--stage-inset);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
transition: min-height 220ms ease-out, opacity 180ms ease-out, transform 220ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opening-thesis[data-opening-focus="title"] .opening-thesis__title-card {
|
||||||
|
min-height: 15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opening-thesis[data-opening-focus="substrate"] .opening-thesis__title-card {
|
||||||
|
min-height: 5rem;
|
||||||
|
transform: translateY(-0.15rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.opening-thesis__title-card span {
|
.opening-thesis__title-card span {
|
||||||
@@ -1784,6 +1802,16 @@
|
|||||||
.opening-thesis .concept-rail {
|
.opening-thesis .concept-rail {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
transition: opacity 180ms ease-out, transform 220ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opening-thesis[data-opening-focus="title"] .opening-thesis__decomposition {
|
||||||
|
opacity: 0.72;
|
||||||
|
transform: translateY(0.3rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.opening-thesis[data-opening-focus="substrate"] .opening-thesis__decomposition {
|
||||||
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Problem loop scene */
|
/* Problem loop scene */
|
||||||
@@ -1804,6 +1832,7 @@
|
|||||||
border-radius: 0.95rem;
|
border-radius: 0.95rem;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
transition: opacity 220ms ease-out, transform 240ms ease-out, border-color 220ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.problem-chat-card {
|
.problem-chat-card {
|
||||||
@@ -1825,7 +1854,13 @@
|
|||||||
|
|
||||||
.problem-chat-card[data-problem-active="false"],
|
.problem-chat-card[data-problem-active="false"],
|
||||||
.problem-blueprint[data-blueprint-active="false"] {
|
.problem-blueprint[data-blueprint-active="false"] {
|
||||||
opacity: 0.62;
|
opacity: 0.52;
|
||||||
|
transform: scale(0.97);
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-loop-scene[data-problem-focus="tool-loop"] .problem-chat-card,
|
||||||
|
.problem-loop-scene[data-problem-focus="workflow-blueprint"] .problem-blueprint {
|
||||||
|
border-color: color-mix(in oklch, var(--accent-cyan) 72%, var(--stage-line));
|
||||||
}
|
}
|
||||||
|
|
||||||
.problem-artifact-header {
|
.problem-artifact-header {
|
||||||
@@ -1900,8 +1935,11 @@
|
|||||||
|
|
||||||
.problem-loop-scene__bridge {
|
.problem-loop-scene__bridge {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
color: var(--accent-cyan);
|
max-width: 7rem;
|
||||||
font: 800 2rem/1 var(--font-interface);
|
color: var(--text-secondary);
|
||||||
|
font: 700 0.64rem/1.25 var(--font-evidence);
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
@container presentation-canvas (max-width: 1050px) {
|
@container presentation-canvas (max-width: 1050px) {
|
||||||
@@ -1914,6 +1952,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.opening-thesis__title-card,
|
||||||
|
.opening-thesis .concept-rail,
|
||||||
|
.problem-chat-card,
|
||||||
|
.problem-blueprint {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Demo workflow scene */
|
/* Demo workflow scene */
|
||||||
.demo-workflow-scene__graph-area {
|
.demo-workflow-scene__graph-area {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -2703,6 +2750,14 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.problem-loop-scene[data-problem-focus="tool-loop"] {
|
||||||
|
grid-template-columns: minmax(0, 1.35fr) auto minmax(17rem, 0.65fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-loop-scene[data-problem-focus="workflow-blueprint"] {
|
||||||
|
grid-template-columns: minmax(17rem, 0.65fr) auto minmax(0, 1.35fr);
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes authoring-canvas-enter {
|
@keyframes authoring-canvas-enter {
|
||||||
from { opacity: 0; }
|
from { opacity: 0; }
|
||||||
to { opacity: 1; }
|
to { opacity: 1; }
|
||||||
|
|||||||
Reference in New Issue
Block a user