feat: recompose positioning scene

This commit is contained in:
lda
2026-07-08 16:34:50 +07:00 Verified
parent c7bd73a2f4
commit 8a06afc0b2
3 changed files with 174 additions and 47 deletions
@@ -1,4 +1,4 @@
import { cleanup, render, screen } from "@testing-library/react"; import { cleanup, render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import { loadCanonicalDemoRecording } from "../demo/timeline/replay.js"; import { loadCanonicalDemoRecording } from "../demo/timeline/replay.js";
@@ -119,6 +119,51 @@ describe("SceneBody", () => {
expect(openDiscussion).toHaveBeenCalledWith("hosted-automation"); expect(openDiscussion).toHaveBeenCalledWith("hosted-automation");
}); });
it("renders Scene 3 as a full positioning map", () => {
const location: PresentationLocation = { kind: "main", sceneId: "positioning", beatId: "landscape", focusPath: [] };
render(
<SceneBody
location={location}
demo={demo}
selectedNodeId={null}
selectNode={noop}
openEvidence={noop}
openDiscussion={noop}
onFocusPathChange={noop}
motionDisabled={false}
/>,
);
const map = screen.getByLabelText("positioning map");
expect(map).toHaveAttribute("data-positioning-active-region", "landscape");
const withinMap = within(map);
expect(withinMap.getByText("Tool loops")).toBeInTheDocument();
expect(withinMap.getByText("Generated scripts")).toBeInTheDocument();
expect(withinMap.getByText("lda.chat")).toBeInTheDocument();
expect(withinMap.getByText("Agent graphs")).toBeInTheDocument();
expect(withinMap.getByText("MCP")).toBeInTheDocument();
});
it("emphasizes lda.chat in the positioning beat", () => {
const location: PresentationLocation = { kind: "main", sceneId: "positioning", beatId: "lda-position", focusPath: [] };
render(
<SceneBody
location={location}
demo={demo}
selectedNodeId={null}
selectNode={noop}
openEvidence={noop}
openDiscussion={noop}
onFocusPathChange={noop}
motionDisabled={false}
/>,
);
const substrate = screen.getByText("lda.chat").closest("[data-positioning-role='substrate']");
expect(substrate).toHaveAttribute("data-positioning-active", "true");
expect(screen.getByLabelText("positioning map")).toHaveAttribute("data-positioning-active-region", "lda");
});
it("renders Scene 7 as an agent authoring loop with beat-specific emphasis", () => { it("renders Scene 7 as an agent authoring loop with beat-specific emphasis", () => {
const location: PresentationLocation = { kind: "main", sceneId: "authoring", beatId: "diagnose", focusPath: [] }; const location: PresentationLocation = { kind: "main", sceneId: "authoring", beatId: "diagnose", focusPath: [] };
render( render(
+44 -20
View File
@@ -62,27 +62,51 @@ const PositioningScene = ({ scene, beat }: { scene: SceneDefinition; beat: Scene
<StageCaption eyebrow={`Act I · ${scene.claimClass}`} title={scene.title}> <StageCaption eyebrow={`Act I · ${scene.claimClass}`} title={scene.title}>
<p>{beat.caption}</p> <p>{beat.caption}</p>
</StageCaption> </StageCaption>
<div className="scene-body__positioning-grid"> <div
<div className="scene-body__positioning-card"> className="scene-body__positioning-map"
<strong>Tool loops</strong> aria-label="positioning map"
<span>Direct action, no lifecycle</span> data-positioning-active-region={highlightLda ? "lda" : "landscape"}
</div> >
<div className="scene-body__positioning-card"> <section className="scene-body__positioning-column" aria-label="direct action patterns">
<strong>Scripts</strong> <p className="scene-body__positioning-label">Direct action</p>
<span>Simple, debuggable</span> <article className="scene-body__positioning-tile">
</div> <strong>Tool loops</strong>
<div className={`scene-body__positioning-card${highlightLda ? " scene-body__positioning-card--active" : ""}`}> <span>Fast action, no durable lifecycle</span>
</article>
<article className="scene-body__positioning-tile">
<strong>Generated scripts</strong>
<span>Inspectable code, weak deployment records</span>
</article>
</section>
<article
className="scene-body__positioning-substrate"
data-positioning-role="substrate"
data-positioning-active={highlightLda ? "true" : "false"}
>
<span className="scene-body__positioning-label">This thesis</span>
<strong>lda.chat</strong> <strong>lda.chat</strong>
<span>Typed lifecycle contracts</span> <p>Typed workflow substrate for external agents and human operators.</p>
</div> <ul>
<div className="scene-body__positioning-card"> <li>Lifecycle</li>
<strong>Agent graphs</strong> <li>Validation</li>
<span>Shared durability</span> <li>Persisted records</li>
</div> </ul>
<div className="scene-body__positioning-card"> </article>
<strong>MCP</strong> <section className="scene-body__positioning-column" aria-label="adjacent systems">
<span>Capability protocol</span> <p className="scene-body__positioning-label">Adjacent systems</p>
</div> <article className="scene-body__positioning-tile">
<strong>Hosted automation</strong>
<span>Managed triggers and app integrations</span>
</article>
<article className="scene-body__positioning-tile">
<strong>Agent graphs</strong>
<span>Durable planner loops</span>
</article>
<article className="scene-body__positioning-tile">
<strong>MCP</strong>
<span>Capability protocol boundary</span>
</article>
</section>
</div> </div>
<p className="scene-body__evidence">{scene.evidencePointer}</p> <p className="scene-body__evidence">{scene.evidencePointer}</p>
</> </>
@@ -661,34 +661,100 @@
} }
/* Scene body visual treatments */ /* Scene body visual treatments */
.scene-body__positioning-grid { .scene-body__positioning-map {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); grid-template-columns: minmax(13rem, 0.9fr) minmax(20rem, 1.35fr) minmax(13rem, 0.9fr);
gap: 0.5rem; gap: 1rem;
margin-top: 0.5rem; align-items: stretch;
margin-top: 1.1rem;
min-height: 20rem;
} }
.scene-body__positioning-card { .scene-body__positioning-column {
padding: 0.65rem 0.75rem; display: grid;
border: 1px solid oklch(0.36 0.04 250); align-content: start;
background: oklch(0.16 0.025 250); gap: 0.7rem;
border-radius: 0.5rem;
font-size: 0.8rem;
} }
.scene-body__positioning-card strong { .scene-body__positioning-label {
margin: 0;
color: var(--color-editorial-muted, oklch(0.48 0.025 65));
font: 700 0.68rem/1 var(--font-mono, monospace);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.scene-body__positioning-tile,
.scene-body__positioning-substrate {
border: 1px solid color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 30%, transparent);
border-radius: 0.85rem;
background: color-mix(in oklch, var(--color-editorial-paper, oklch(0.975 0.012 82)) 78%, white);
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
.scene-body__positioning-tile {
min-height: 5.8rem;
padding: 0.85rem;
}
.scene-body__positioning-tile strong {
display: block; display: block;
margin-bottom: 0.15rem; margin-bottom: 0.35rem;
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
} }
.scene-body__positioning-card span { .scene-body__positioning-tile span {
color: oklch(0.72 0.03 250); color: color-mix(in oklch, var(--color-editorial-ink, oklch(0.19 0.015 65)) 72%, var(--color-editorial-muted, oklch(0.48 0.025 65)));
font-size: 0.75rem; font-size: 0.82rem;
line-height: 1.35;
} }
.scene-body__positioning-card--active { .scene-body__positioning-substrate {
border-color: oklch(0.7 0.16 195); display: grid;
background: oklch(0.2 0.06 195); align-content: center;
gap: 0.8rem;
padding: 1.15rem;
box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--accent-cyan, oklch(0.7 0.16 195)) 18%, transparent);
}
.scene-body__positioning-substrate strong {
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
font-size: 2rem;
line-height: 0.95;
}
.scene-body__positioning-substrate p {
max-width: 28ch;
margin: 0;
color: color-mix(in oklch, var(--color-editorial-ink, oklch(0.19 0.015 65)) 82%, var(--color-editorial-muted, oklch(0.48 0.025 65)));
}
.scene-body__positioning-substrate ul {
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
margin: 0;
padding: 0;
list-style: none;
}
.scene-body__positioning-substrate li {
border: 1px solid color-mix(in oklch, var(--accent-cyan, oklch(0.7 0.16 195)) 42%, transparent);
border-radius: 999px;
padding: 0.25rem 0.5rem;
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
font-size: 0.72rem;
font-weight: 700;
}
.scene-body__positioning-map[data-positioning-active-region="lda"] .scene-body__positioning-tile {
opacity: 0.58;
}
.scene-body__positioning-substrate[data-positioning-active="true"] {
transform: translateY(-0.25rem);
border-color: color-mix(in oklch, var(--accent-cyan, oklch(0.7 0.16 195)) 68%, transparent);
background: color-mix(in oklch, var(--accent-cyan, oklch(0.7 0.16 195)) 8%, var(--color-editorial-paper, oklch(0.975 0.012 82)));
} }
.scene-body__boundary { .scene-body__boundary {
@@ -938,14 +1004,6 @@
transform: scale(1.05); transform: scale(1.05);
} }
.scene-body__positioning-card {
transition: opacity 0.3s ease, transform 0.25s ease, border-color 0.2s ease, background 0.2s ease;
}
.scene-body__positioning-card--active {
transform: scale(1.03);
}
@media (max-height: 760px) { @media (max-height: 760px) {
.stage-caption h1 { .stage-caption h1 {
font-size: clamp(1.4rem, 3vw, 2.2rem); font-size: clamp(1.4rem, 3vw, 2.2rem);