feat: choreograph architecture presentation beats

This commit is contained in:
lda
2026-07-13 10:15:51 +07:00 Verified
parent f43853e63f
commit 870902a69b
9 changed files with 93 additions and 10 deletions
@@ -300,7 +300,7 @@ export const architectureCatalog: FigureCatalogDefinition = defineFigureCatalog(
{
id: "configured-providers",
label: "Capability inventory",
summary: "Provider-neutral CapabilitySource map",
summary: "Provider-neutral source map",
kind: "provider",
icon: "layers",
evidencePointer: "src/wf_platform/sources.py",
@@ -359,6 +359,27 @@
margin: 0;
}
.architecture-scene .figure-node {
transition:
opacity 180ms ease-out,
border-color 150ms ease-out,
background-color 150ms ease-out;
}
.architecture-scene .interactive-figure:has(.figure-node[data-active="true"])
.figure-node:not([data-active="true"]) {
opacity: 0.48;
}
.architecture-scene .figure-node[data-active="true"] {
z-index: 1;
background: oklch(0.995 0.004 82);
}
.architecture-scene .figure-node__current-marker {
display: none;
}
.scene-body__evidence {
flex-shrink: 0;
margin: 0.55rem 0 0;
@@ -158,14 +158,14 @@ export const presenterNotes = [
"architecture",
"client",
13,
"Human and agent clients use the same public lifecycle operations.",
"Human operators and external agents enter through **the same public lifecycle surface**.",
["Thesis System Architecture", "docs/project_map.md"],
),
beatNote(
"architecture",
"api",
13,
"JSON-RPC handles transport concerns and delegates to WorkflowApi rather than owning domain behavior.",
"**WorkflowApi owns lifecycle operations**; JSON-RPC adapts requests without owning domain behavior.",
["Thesis System Architecture", "docs/source_architecture.md"],
{ qnaBranchIds: ["not-just-cli"] },
),
@@ -173,7 +173,7 @@ export const presenterNotes = [
"architecture",
"runtime",
14,
"Server composition supplies stores, provider projections, and the runtime while the core remains independent of MCP and Python behavior.",
"**WorkflowServer composes records, provider-neutral capabilities, WorkflowApi, and the execution kernel**; provider behavior remains outside the core.",
["Thesis System Architecture", "docs/source_architecture.md"],
{ qnaBranchIds: ["provider-security"] },
),
@@ -181,7 +181,7 @@ export const presenterNotes = [
"architecture",
"node-use",
15,
"A NodeUse validates input, invokes a projected capability, checks its declared outcome, reduces output into state, appends a trace frame, and routes to the next edge.",
"A NodeUse resolves bindings, **invokes its NodeDef handler**, reduces output into state, appends a trace frame, and routes the outcome.",
["Thesis Workflow Core Model"],
),
beatNote(
@@ -117,6 +117,27 @@ describe("ArchitectureScene", () => {
expect(screen.getByRole("group", { name: /configured provider boundary/i })).toHaveAttribute("data-figure-focus-level", "2");
});
it("marks the current beat as a guided camera focus", () => {
renderArchitecture({
beat: { ...mockBeat, id: "api" },
focusPath: ["application-lifecycle"],
activeNodeId: "workflow-api-boundary",
});
expect(screen.getByTestId("architecture-scene")).toHaveAttribute("data-architecture-beat", "api");
expect(screen.getByTestId("figure-node-workflow-api-boundary")).toHaveAttribute("data-active", "true");
});
it("highlights the actual NodeDef handler during the NodeUse beat", () => {
renderArchitecture({
beat: { ...mockBeat, id: "node-use" },
focusPath: ["node-use"],
activeNodeId: "node-def-handler",
});
expect(screen.getByTestId("figure-node-node-def-handler")).toHaveAttribute("data-active", "true");
});
it("shows the kernel loop and clickable NodeUse sequence as nested figures", () => {
const onFocusPathChange = vi.fn();
renderArchitecture({ focusPath: ["core-runtime"], onFocusPathChange });
@@ -39,6 +39,7 @@ export const ArchitectureScene = ({
data-motion={motionDisabled ? "disabled" : "enabled"}
data-focus-level={focusPath.length}
data-architecture-focus={focusPath.length === 0 ? "system" : "nested"}
data-architecture-beat={beat.id}
>
<StageCaption eyebrow={`Act II · ${scene.claimClass}`} title={scene.title}>
<p>{beat.caption}</p>
@@ -111,6 +111,30 @@ describe("storyboard navigation", () => {
expect(locationFromHash(hashForLocation(location))).toEqual(location);
});
it("uses each architecture beat's authored focus for a plain direct hash", () => {
expect(locationFromHash("#scene/architecture/api")).toMatchObject({
focusPath: ["application-lifecycle"],
});
expect(locationFromHash("#scene/architecture/runtime")).toMatchObject({
focusPath: ["runtime-providers"],
});
expect(locationFromHash("#scene/architecture/node-use")).toMatchObject({
focusPath: ["node-use"],
});
});
it("round-trips an explicit root view for a beat with an authored nested focus", () => {
const rootView: MainLocation = {
kind: "main",
sceneId: "architecture",
beatId: "runtime",
focusPath: [],
};
expect(hashForLocation(rootView)).toBe("#scene/architecture/runtime/focus/~");
expect(locationFromHash(hashForLocation(rootView))).toEqual(rootView);
});
it("decodes escaped focus segments and rejects malformed encoding", () => {
expect(locationFromHash("#scene/architecture/runtime/focus/runtime%20providers"))
.toMatchObject({ focusPath: ["runtime providers"] });
@@ -18,12 +18,23 @@ const flattenMainLocations = (): readonly MainLocation[] =>
})),
);
const ROOT_FOCUS_SENTINEL = "~";
const authoredFocusPath = (sceneId: string, beatId: string): readonly string[] =>
findScene(sceneId)?.beats.find((beat) => beat.id === beatId)?.figure?.focusPath ?? [];
export const hashForLocation = (location: PresentationLocation): string => {
if (location.kind === "discussion") {
return `#discuss/${encodeURIComponent(location.branchId)}`;
}
const base = `#scene/${encodeURIComponent(location.sceneId)}/${encodeURIComponent(location.beatId)}`;
if (location.focusPath.length === 0) return base;
if (location.focusPath.length === 0) {
// Beats may open on an authored nested figure. The sentinel preserves an
// explicitly selected root view without making plain deep links ambiguous.
return authoredFocusPath(location.sceneId, location.beatId).length > 0
? `${base}/focus/${ROOT_FOCUS_SENTINEL}`
: base;
}
const segments = location.focusPath.map(encodeURIComponent).join("/");
return `${base}/focus/${segments}`;
};
@@ -42,7 +53,9 @@ export const locationFromHash = (hash: string): PresentationLocation => {
try {
sceneId = decodeURIComponent(sceneSegment);
beatId = decodeURIComponent(beatSegment);
if (focusSegment !== undefined && focusSegment !== "") {
if (focusSegment === ROOT_FOCUS_SENTINEL) {
focusPath = [];
} else if (focusSegment !== undefined && focusSegment !== "") {
focusPath = focusSegment.split("/").map(decodeURIComponent);
}
} catch {
@@ -50,6 +63,9 @@ export const locationFromHash = (hash: string): PresentationLocation => {
}
const scene = findScene(sceneId);
if (scene && scene.beats.some((b) => b.id === beatId)) {
if (focusSegment === undefined) {
focusPath = [...authoredFocusPath(scene.id, beatId)];
}
return { kind: "main", sceneId: scene.id as MainLocation["sceneId"], beatId, focusPath };
}
return defaultMainLocation;
@@ -133,9 +133,9 @@ export const mainScenes = defineScenes([
view: "architecture",
beats: [
sceneBeat("client", "Client operations", "Human and agent clients use the same public lifecycle surface.", { figure: { catalogId: "system-architecture", focusPath: [], activeNodeId: "client-operations" } }),
sceneBeat("api", "Transport and API", "JSON-RPC reaches WorkflowApi without owning domain behavior.", { figure: { catalogId: "system-architecture", focusPath: [], activeNodeId: "application-lifecycle" } }),
sceneBeat("api", "Transport and API", "JSON-RPC reaches WorkflowApi without owning domain behavior.", { figure: { catalogId: "system-architecture", focusPath: ["application-lifecycle"], activeNodeId: "workflow-api-boundary" } }),
sceneBeat("runtime", "Runtime and providers", "The runtime resolves provider-neutral capabilities and stores lifecycle records.", { figure: { catalogId: "system-architecture", focusPath: ["runtime-providers"], activeNodeId: "configured-providers" } }),
sceneBeat("node-use", "NodeUse", "One callable node validates input, invokes a capability, and reduces output into state.", { evidencePresentation: "receipt", figure: { catalogId: "system-architecture", focusPath: ["node-use"], activeNodeId: "invoke-handler" } }),
sceneBeat("node-use", "NodeUse", "One callable node validates input, invokes a capability, and reduces output into state.", { evidencePresentation: "receipt", figure: { catalogId: "system-architecture", focusPath: ["node-use"], activeNodeId: "node-def-handler" } }),
],
},
{