refactor: remove duplicate authoring scene

This commit is contained in:
lda
2026-07-13 13:43:59 +07:00 Verified
parent 7fc706f6f4
commit 801f82b3b3
11 changed files with 98 additions and 414 deletions
+22
View File
@@ -0,0 +1,22 @@
# Task 4 Report
## Status
Implemented in shared `main`; ready for commit as `refactor: remove duplicate authoring scene`.
## Changes
- Removed the obsolete `AuthoringScene` branch, local authoring steps, projection helper, and exclusive SceneBody authoring CSS.
- Kept the approved Prepared Lifecycle six-step visual and exposed its migrated defense rail beneath the scene content.
- Limited the discussion rail to Prepared Lifecycle among workflow-demo scenes; run, interrupt, and output proof scenes remain rail-free.
- Renamed the Scene9 callback chain to `onPreparedLifecycleAdvance` and `handlePreparedLifecycleAdvance`.
- Removed stale `authoring` route/test/presenter callers and retargeted the presenter notes to the six Prepared Lifecycle beats.
## TDD And Verification
- RED: the focused migration run failed with the expected stale authoring scene/rail/callback assertions.
- GREEN: focused presentation tests passed: `5` files, `161/161` tests.
- Console typecheck passed: `pnpm --dir web --filter @lda/console typecheck`.
- `PresentationStage.test.tsx` named in the brief is not present in this checkout, so the focused run used the existing PresentationStage implementation coverage through route tests.
The broader presenter-note catalog test remains outside Task 4 scope and still expects the later Task 5 `architecture/node-use` note and timing ledger.
@@ -77,13 +77,13 @@ describe("PresentationRoute", () => {
{ hash: "#scene/thesis/title", heading: "Design and Implementation of lda.chat", hasDemoChrome: false },
{ hash: "#scene/architecture/overview", heading: "Architecture Zoom", hasDemoChrome: false },
{ hash: "#scene/architecture/client", heading: "Architecture Zoom", hasDemoChrome: false },
{ hash: "#scene/authoring/diagnose", heading: "Author, Validate, Repair", hasDemoChrome: false },
{ hash: "#scene/prepared-lifecycle/diagnose", heading: "Diagnose invalid draft", hasDemoChrome: true },
{
hash: "#scene/agent-handoff/request",
heading: "What should the workflow author prepare?",
hasDemoChrome: true,
},
{ hash: "#scene/prepared-lifecycle/discover", heading: "Prepared Workflow Lifecycle", hasDemoChrome: true },
{ hash: "#scene/prepared-lifecycle/discover", heading: "Discover capabilities", hasDemoChrome: true },
{ hash: "#scene/run-from-deployment/graph", heading: "Run From Deployment", hasDemoChrome: true },
{ hash: "#scene/typed-human-boundary/approval", heading: "Typed Human Boundary", hasDemoChrome: true },
{ hash: "#scene/resume-output-evidence/trace", heading: "Resume, Output, Evidence", hasDemoChrome: true },
@@ -124,8 +124,8 @@ describe("PresentationRoute", () => {
const visualRouteContracts = [
{ hash: "#scene/thesis/title", heading: "Design and Implementation of lda.chat", primary: "thesis opening" },
{ hash: "#scene/lifecycle/draft", heading: "Workflow Lifecycle", primary: "workflow lifecycle rail" },
{ hash: "#scene/authoring/diagnose", heading: "Author, Validate, Repair", primary: "agent authoring loop" },
{ hash: "#scene/prepared-lifecycle/validate", heading: "Prepared Workflow Lifecycle", primary: "prepared workflow authoring lifecycle" },
{ hash: "#scene/prepared-lifecycle/diagnose", heading: "Diagnose invalid draft", primary: "prepared workflow authoring lifecycle" },
{ hash: "#scene/prepared-lifecycle/repair", heading: "Apply targeted repair", primary: "prepared workflow authoring lifecycle" },
{ hash: "#scene/run-from-deployment/graph", heading: "Run From Deployment", primary: "workflow graph" },
{ hash: "#scene/evaluation/findings", heading: "Evaluation", primary: "evaluation evidence board" },
{ hash: "#scene/conclusion/conclusion", heading: "Limits and Conclusion", primary: "thesis contribution boundary" },
@@ -192,10 +192,10 @@ describe("PresentationRoute", () => {
render(<PresentationRoute />);
expect(screen.getByRole("textbox", { name: /authoring request/i })).toBeInTheDocument();
await userEvent.keyboard("{ArrowRight}");
expect(await screen.findByLabelText("authoring phase rail")).toBeInTheDocument();
expect(await screen.findByLabelText("prepared authoring lifecycle")).toBeInTheDocument();
});
it("advances Draft to Validate once and projects the edited request", async () => {
it("advances Draft to Diagnose once and projects the edited request", async () => {
window.location.hash = "#scene/prepared-lifecycle/draft";
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
@@ -205,10 +205,10 @@ describe("PresentationRoute", () => {
await userEvent.type(message, "Check this edited draft request");
await userEvent.click(screen.getByRole("button", { name: /send message/i }));
expect(window.location.hash).toBe("#scene/prepared-lifecycle/validate");
expect(window.location.hash).toBe("#scene/prepared-lifecycle/diagnose");
expect(await screen.findByText("Check this edited draft request")).toBeInTheDocument();
expect(screen.getByRole("complementary", { name: /prepared authoring assistant/i }))
.toHaveAttribute("data-phase", "validate");
.toHaveAttribute("data-phase", "diagnose");
});
it("advances Artifact to Deployment once and projects the edited request", async () => {
@@ -243,8 +243,8 @@ describe("PresentationRoute", () => {
.toBe(false);
});
it("keeps Validate submission on the current beat", async () => {
window.location.hash = "#scene/prepared-lifecycle/validate";
it("keeps Diagnose submission on the current beat", async () => {
window.location.hash = "#scene/prepared-lifecycle/diagnose";
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
@@ -252,9 +252,9 @@ describe("PresentationRoute", () => {
await userEvent.type(message, "Keep validating this draft");
await userEvent.click(screen.getByRole("button", { name: /send message/i }));
expect(window.location.hash).toBe("#scene/prepared-lifecycle/validate");
expect(window.location.hash).toBe("#scene/prepared-lifecycle/diagnose");
expect(screen.getByRole("complementary", { name: /prepared authoring assistant/i }))
.toHaveAttribute("data-phase", "validate");
.toHaveAttribute("data-phase", "diagnose");
});
it("renders audience progress chrome without rail or mode label", async () => {
@@ -720,7 +720,8 @@ describe("PresentationRoute", () => {
it.each([
"#scene/prepared-lifecycle/discover",
"#scene/prepared-lifecycle/draft",
"#scene/prepared-lifecycle/validate",
"#scene/prepared-lifecycle/diagnose",
"#scene/prepared-lifecycle/repair",
"#scene/prepared-lifecycle/artifact",
"#scene/prepared-lifecycle/deployment",
])("navigates to Scene 9 beat %s", async (hash) => {
@@ -728,7 +729,7 @@ describe("PresentationRoute", () => {
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
expect(await screen.findByLabelText("authoring phase rail")).toBeInTheDocument();
expect(await screen.findByLabelText("prepared authoring lifecycle")).toBeInTheDocument();
});
it("navigating Scene 9 beats does not call workflow authoring RPC operations", async () => {
@@ -739,7 +740,7 @@ describe("PresentationRoute", () => {
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
expect(await screen.findByLabelText("authoring phase rail")).toBeInTheDocument();
expect(await screen.findByLabelText("prepared authoring lifecycle")).toBeInTheDocument();
const authoringCalls = mockCallOp.mock.calls.filter((c) => c[0] !== "workflow.health");
expect(authoringCalls).toHaveLength(0);
});
@@ -263,7 +263,7 @@ export const PresentationRoute = () => {
[],
);
const handleScene9Advance = useCallback(() => {
const handlePreparedLifecycleAdvance = useCallback(() => {
if (
state.location.kind !== "main"
|| state.location.sceneId !== "prepared-lifecycle"
@@ -300,7 +300,7 @@ export const PresentationRoute = () => {
retryHealth={targetStatusController.retryHealth}
liveTargetReady={targetStatusController.liveTargetReady}
jump={handleJump}
onScene9Advance={handleScene9Advance}
onPreparedLifecycleAdvance={handlePreparedLifecycleAdvance}
selectNode={(nodeId) => dispatch({ type: "select_node", nodeId })}
openEvidence={() => dispatch({ type: "set_evidence_presentation", presentation: "inspector" })}
closeOverlay={() => dispatch({ type: "close_overlay" })}
@@ -28,7 +28,7 @@ type PresentationStageProps = {
readonly retryHealth: () => void;
readonly liveTargetReady: boolean;
readonly jump: (location: MainLocation) => void;
readonly onScene9Advance?: (() => void) | undefined;
readonly onPreparedLifecycleAdvance?: (() => void) | undefined;
readonly selectNode: (nodeId: string | null) => void;
readonly openEvidence: () => void;
readonly closeOverlay: () => void;
@@ -49,7 +49,7 @@ export const PresentationStage = ({
retryHealth,
liveTargetReady,
jump,
onScene9Advance,
onPreparedLifecycleAdvance,
selectNode,
openEvidence,
closeOverlay,
@@ -108,7 +108,7 @@ export const PresentationStage = ({
}}
motionDisabled={state.motionDisabled}
approvalActions={approvalActions}
onScene9Advance={onScene9Advance}
onPreparedLifecycleAdvance={onPreparedLifecycleAdvance}
/>
)}
</section>
@@ -368,40 +368,14 @@ describe("SceneBody", () => {
expect(screen.getByLabelText("positioning map")).toHaveAttribute("data-positioning-active-region", "lda");
});
it("renders Scene 7 as an agent authoring loop with beat-specific emphasis", () => {
const location: PresentationLocation = { kind: "main", sceneId: "authoring", beatId: "diagnose", focusPath: [] };
render(
<SceneBody
location={location}
demo={demo}
selectedNodeId={null}
selectNode={noop}
openEvidence={noop}
openDiscussion={noop}
onFocusPathChange={noop}
motionDisabled={false}
/>,
);
it("renders Prepared Lifecycle with its migrated defense discussion rail", () => {
renderSceneBodyAtMainLocation("prepared-lifecycle", "diagnose");
const composition = screen.getByLabelText("agent authoring loop");
expect(composition).toHaveAttribute("data-active-stage", "diagnose");
expect(composition).toHaveAttribute("data-presentation-surface", "editorial");
expect(screen.getByRole("article", { name: /validate product evidence/i })).toBeInTheDocument();
expect(screen.getByRole("region", { name: /validation repair evidence/i })).toHaveAttribute(
"data-authoring-focus",
"diagnose",
);
expect(screen.getByText("workflow.draft_workspaces.validate")).toBeInTheDocument();
const loop = screen.getByLabelText("authoring phase loop");
expect(loop).not.toHaveAttribute("data-readable-surface", "dark");
expect(screen.getByText("Discover capability")).toBeInTheDocument();
expect(screen.getByText("Author draft")).toBeInTheDocument();
expect(screen.getByText(/operates on the mutable Draft.*external agent proposal.*Artifact, Deployment, and Run/i)).toBeInTheDocument();
expect(screen.getByText("Validate and diagnose").closest("[data-authoring-active]")).toHaveAttribute("data-authoring-active", "true");
expect(screen.getByText("Validate and diagnose").closest(".scene-body__authoring-node"))
.not.toHaveAttribute("data-readable-surface", "dark");
expect(within(loop).getByText("Repair")).toBeInTheDocument();
expect(within(loop).getByText("Compile or save")).toBeInTheDocument();
expect(screen.getByLabelText("prepared authoring lifecycle")).toBeInTheDocument();
const rail = screen.getByLabelText("defense discussion topics");
expect(within(rail).getByRole("button", { name: /Raw plan import/i })).toBeInTheDocument();
expect(within(rail).getByRole("button", { name: /Validation and diagnostics/i })).toBeInTheDocument();
expect(within(rail).getByRole("button", { name: /Why schemas matter/i })).toBeInTheDocument();
});
it("renders discussion branches as a labelled presenter rail", () => {
@@ -427,7 +401,7 @@ describe("SceneBody", () => {
});
it("gives the repair beat its own correction visual", () => {
const location: PresentationLocation = { kind: "main", sceneId: "authoring", beatId: "repair", focusPath: [] };
const location: PresentationLocation = { kind: "main", sceneId: "prepared-lifecycle", beatId: "repair", focusPath: [] };
render(
<SceneBody
location={location}
@@ -441,14 +415,18 @@ describe("SceneBody", () => {
/>,
);
expect(screen.getByRole("region", { name: /validation repair evidence/i })).toHaveAttribute(
"data-authoring-focus",
expect(screen.getByRole("region", { name: "active authoring operation" })).toHaveAttribute(
"data-authoring-step",
"repair",
);
});
it("keeps discussion rails out of workflow proof scenes", () => {
const location: PresentationLocation = { kind: "main", sceneId: "run-from-deployment", beatId: "graph", focusPath: [] };
it.each([
["run-from-deployment", "graph"],
["typed-human-boundary", "approval"],
["resume-output-evidence", "trace"],
] as const)("keeps discussion rails out of %s proof scenes", (sceneId, beatId) => {
const location: PresentationLocation = { kind: "main", sceneId, beatId, focusPath: [] };
render(
<SceneBody
location={location}
@@ -514,13 +492,13 @@ describe("SceneBody", () => {
it("renders Scene 9 discover beat with prepared authoring phases", () => {
renderSceneBodyAtMainLocation("prepared-lifecycle", "discover");
expect(screen.getByLabelText("authoring phase rail")).toBeInTheDocument();
expect(screen.getByText("Discover")).toBeInTheDocument();
expect(screen.getByLabelText("prepared authoring lifecycle")).toBeInTheDocument();
expect(screen.getAllByText("Discover").length).toBeGreaterThanOrEqual(1);
});
it("renders Scene 9 artifact beat with compiled artifact evidence", () => {
renderSceneBodyAtMainLocation("prepared-lifecycle", "artifact");
expect(screen.getByText("Artifact")).toBeInTheDocument();
expect(screen.getAllByText("Artifact").length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText(/lda_report_case_study/i).length).toBeGreaterThanOrEqual(1);
});
@@ -18,9 +18,6 @@ import { ConclusionScene } from "./conclusion/ConclusionScene.js";
import { DefenseDiscussionIndex } from "./discussion/DefenseDiscussionIndex.js";
import { EvaluationEvidenceScene } from "./evaluation/EvaluationEvidenceScene.js";
import { ArchitectureScene } from "./scenes/ArchitectureScene.js";
import { AuthoringPhaseVisual } from "./authoring/AuthoringPhaseVisual.js";
import { projectPreparedAuthoringPhase } from "./authoring/authoring-projection.js";
import type { AuthoringPhaseId } from "./authoring/authoring-recording.js";
import { OpeningThesisScene } from "./opening/OpeningThesisScene.js";
import { ProblemLoopScene } from "./opening/ProblemLoopScene.js";
@@ -35,7 +32,7 @@ type SceneBodyProps = {
readonly onFocusPathChange: (path: readonly string[]) => void;
readonly motionDisabled: boolean;
readonly approvalActions?: DemoApprovalActions | undefined;
readonly onScene9Advance?: (() => void) | undefined;
readonly onPreparedLifecycleAdvance?: (() => void) | undefined;
};
const workflowDemoSceneIds = new Set([
@@ -289,94 +286,19 @@ const LifecycleScene = ({ scene, beat, motionDisabled }: ChoreographyProps) => {
);
};
const authoringSteps = [
{ id: "discover", label: "Discover capability", detail: "wf schema / cap inspect" },
{ id: "author", label: "Author draft", detail: "wf draft create / add-step / bind" },
{ id: "diagnose", label: "Validate and diagnose", detail: "structured diagnostics + repair hints" },
{ id: "repair", label: "Repair", detail: "focused edit, no full rewrite" },
{ id: "compile", label: "Compile or save", detail: "artifact / deployment / run" },
] as const;
// Scene 7 uses speaker-friendly beat names; the prepared recording keeps the
// underlying product phases (`draft` and `validate`) as its canonical IDs.
const authoringPhaseForBeat = (beatId: string): AuthoringPhaseId => {
if (beatId === "author") return "draft";
if (beatId === "diagnose" || beatId === "repair") return "validate";
return beatId as AuthoringPhaseId;
};
const AuthoringScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => {
const projection = projectPreparedAuthoringPhase(authoringPhaseForBeat(beat.id));
const primaryCommand = projection.commands[0];
// The prepared recording is the factual source for this public-operation label.
if (!primaryCommand) throw new Error(`Authoring phase ${projection.phase} has no public operation`);
return (
<>
<StageCaption eyebrow="Act II · implemented" title={scene.title}>
<p>{beat.caption}</p>
</StageCaption>
<section
className="scene-body__authoring-composition"
aria-label="agent authoring loop"
data-active-stage={beat.id}
data-presentation-surface="editorial"
>
<article className="scene-body__authoring-evidence" aria-label={`${projection.label} product evidence`}>
<header>
<span>Public operation</span>
<strong>{primaryCommand.title}</strong>
<p>{projection.summary}</p>
</header>
<AuthoringPhaseVisual
projection={projection}
focus={beat.id === "diagnose" || beat.id === "repair" ? beat.id : "full"}
/>
</article>
<div
className="scene-body__authoring-loop"
aria-label="authoring phase loop"
>
<div className="scene-body__authoring-loop-rail" aria-hidden="true" />
{authoringSteps.map((step, i) => {
const isActive = beat.id === step.id;
const isPast = authoringSteps.findIndex((candidate) => candidate.id === beat.id) > i;
return (
<div
key={step.id}
className="scene-body__authoring-node"
data-authoring-active={isActive}
data-authoring-past={isPast}
>
<span className="scene-body__authoring-number">{i + 1}</span>
<strong>{step.label}</strong>
<small>{step.detail}</small>
</div>
);
})}
</div>
</section>
<p className="scene-body__evidence">{scene.evidencePointer}</p>
</>
);
};
const assertNever = (value: never): never => {
throw new Error(`Unexpected view: ${value}`);
};
export const SceneBody = ({ location, demo, selectedNodeId, selectNode, openEvidence, openDiscussion, onFocusPathChange, motionDisabled, approvalActions, onScene9Advance }: SceneBodyProps) => {
export const SceneBody = ({ location, demo, selectedNodeId, selectNode, openEvidence, openDiscussion, onFocusPathChange, motionDisabled, approvalActions, onPreparedLifecycleAdvance }: SceneBodyProps) => {
const sceneId = location.kind === "main" ? location.sceneId : "positioning";
const beatId = location.kind === "main" ? location.beatId : "landscape";
const scene = findScene(sceneId) ?? findScene("thesis")!;
const beat = findBeat(sceneId, beatId) ?? scene.beats[0]!;
// The questions beat owns the full discussion index. Workflow demo scenes
// keep the product proof uncluttered; their discussion topics remain
// available from the global presenter controls.
const isWorkflowDemoScene = workflowDemoSceneIds.has(scene.id);
const showDiscussionRail = !(scene.id === "conclusion" && beat.id === "questions")
&& !isWorkflowDemoScene;
&& (!isWorkflowDemoScene || scene.id === "prepared-lifecycle");
const discussionLinks = showDiscussionRail ? <DiscussionLinks sceneId={scene.id} openDiscussion={openDiscussion} /> : null;
const content = (() => {
switch (scene.view) {
@@ -401,12 +323,10 @@ export const SceneBody = ({ location, demo, selectedNodeId, selectNode, openEvid
motionDisabled={motionDisabled}
/>
);
case "authoring":
return <AuthoringScene scene={scene} beat={beat} />;
case "agent":
return <AgentHandoffScene scene={scene} beat={beat} />;
case "demo-lifecycle":
return <PreparedAuthoringLifecycleScene scene={scene} beat={beat} onAdvance={onScene9Advance} />;
case "demo-lifecycle":
return <PreparedAuthoringLifecycleScene scene={scene} beat={beat} onAdvance={onPreparedLifecycleAdvance} />;
case "demo":
return (
<DemoWorkflowScene
@@ -52,7 +52,7 @@ describe("isDemoChromeScene", () => {
expect(isDemoChromeScene(sceneId)).toBe(true);
}
for (const sceneId of ["thesis", "architecture", "authoring", "evaluation", "conclusion"] as const) {
for (const sceneId of ["thesis", "architecture", "lifecycle", "evaluation", "conclusion"] as const) {
expect(isDemoChromeScene(sceneId)).toBe(false);
}
});
@@ -237,7 +237,7 @@ describe("presentationReducer", () => {
it("derives a receipt from beat metadata without opening an inspector", () => {
const state = presentationReducer(initialPresentationState, {
type: "jump",
location: { kind: "main", sceneId: "architecture", beatId: "node-use", focusPath: ["node-use"] },
location: { kind: "main", sceneId: "resume-output-evidence", beatId: "trace", focusPath: [] },
});
expect(compositionForState(state).evidencePresentation).toBe("receipt");
expect(state.evidencePresentationOverride).toBeNull();
@@ -279,7 +279,7 @@ describe("presentationReducer", () => {
it("closes the inspector and recomputes receipt state when the beat changes", () => {
const atReceiptBeat = presentationReducer(initialPresentationState, {
type: "jump",
location: { kind: "main", sceneId: "architecture", beatId: "node-use", focusPath: ["node-use"] },
location: { kind: "main", sceneId: "resume-output-evidence", beatId: "trace", focusPath: [] },
});
const opened = presentationReducer(atReceiptBeat, {
type: "set_evidence_presentation",
@@ -293,7 +293,7 @@ describe("presentationReducer", () => {
it("does not treat a receipt as an Escape-closeable overlay", () => {
const receipt = presentationReducer(initialPresentationState, {
type: "jump",
location: { kind: "main", sceneId: "authoring", beatId: "diagnose", focusPath: [] },
location: { kind: "main", sceneId: "prepared-lifecycle", beatId: "diagnose", focusPath: [] },
});
expect(presentationReducer(receipt, { type: "close_overlay" })).toEqual(receipt);
});
@@ -26,7 +26,6 @@
/* Presentation roots own their visual surfaces; do not inherit console cards. */
.presentation-stage__primary > .architecture-scene,
.presentation-stage__primary > .scene-body__authoring-composition,
.presentation-stage__primary > .prepared-lifecycle-scene,
.presentation-stage__primary > .opening-thesis-scene {
margin: 0;
@@ -38,7 +37,6 @@
.agent-handoff-scene[data-presentation-surface="editorial"],
.prepared-lifecycle-scene[data-presentation-surface="editorial"],
.scene-body__authoring-composition[data-presentation-surface="editorial"],
.authoring-visual[data-presentation-surface="editorial"] {
--authoring-paper: var(--color-editorial-paper, oklch(0.975 0.012 82));
--authoring-ink: var(--color-editorial-ink, oklch(0.19 0.015 65));
@@ -1750,215 +1748,6 @@
line-height: 1.3;
}
.scene-body__authoring-loop {
position: relative;
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 0.75rem;
margin-top: 1rem;
padding: 0.55rem 0 0.2rem;
}
.scene-body__authoring-loop-rail {
position: absolute;
z-index: 0;
top: 2.15rem;
left: 8%;
right: 8%;
height: 2px;
background: linear-gradient(90deg, var(--accent-cyan), color-mix(in oklch, var(--accent-cyan) 20%, transparent));
}
.scene-body__authoring-node {
position: relative;
z-index: 1;
display: grid;
align-content: start;
gap: 0.35rem;
min-height: 5.8rem;
border: 1px solid var(--authoring-rule, color-mix(in oklch, var(--authoring-muted) 32%, transparent));
border-radius: 0.2rem;
padding: 0.75rem;
background: transparent;
color: var(--authoring-ink, var(--text-primary));
transition: opacity 180ms ease, transform 180ms ease, border-color 180ms ease;
}
.scene-body__authoring-node strong {
color: var(--authoring-ink, var(--text-primary));
}
.scene-body__authoring-node[data-authoring-active="true"] {
z-index: 2;
transform: translateY(-0.35rem) scale(1.04);
border-color: var(--accent-cyan);
background: color-mix(in oklch, var(--accent-cyan) 8%, var(--authoring-paper, white));
}
.scene-body__authoring-node[data-authoring-past="true"] {
opacity: 1;
}
.scene-body__authoring-node small {
color: var(--authoring-muted, var(--text-muted));
font: 0.72rem/1.35 var(--font-interface);
}
@media (max-width: 1050px) {
.scene-body__authoring-loop {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.scene-body__authoring-loop-rail {
display: none;
}
}
.scene-body__authoring-composition {
display: grid;
grid-template-rows: minmax(0, 1fr) auto;
gap: 0.7rem;
flex: 1 1 auto;
min-height: 0;
background: var(--authoring-paper, var(--color-editorial-paper, oklch(0.975 0.012 82)));
color: var(--authoring-ink, var(--color-editorial-ink, oklch(0.19 0.015 65)));
}
.scene-body__authoring-evidence {
display: grid;
grid-template-rows: auto minmax(0, 1fr);
flex: 1 1 auto;
min-height: 0;
overflow: hidden;
border: 1px solid var(--authoring-rule, color-mix(in oklch, var(--authoring-muted) 32%, transparent));
border-radius: 0.2rem;
padding: 1rem 1.15rem;
background: var(--authoring-paper, var(--color-editorial-paper, oklch(0.975 0.012 82)));
}
.scene-body__authoring-evidence > header {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: 0.2rem 0.65rem;
align-items: baseline;
}
.scene-body__authoring-evidence > header span {
color: var(--authoring-muted, var(--accent-cyan));
font: 700 0.65rem/1 var(--font-evidence);
letter-spacing: 0.05em;
text-transform: uppercase;
}
.scene-body__authoring-evidence > header strong {
min-width: 0;
overflow: hidden;
color: var(--authoring-ink, var(--text-primary));
font: 700 0.82rem/1.2 var(--font-evidence);
text-overflow: ellipsis;
white-space: nowrap;
}
.scene-body__authoring-evidence > header p {
grid-column: 1 / -1;
margin: 0;
color: var(--authoring-muted, var(--text-secondary));
font: 0.82rem/1.3 var(--font-interface);
}
.scene-body__authoring-evidence .authoring-visual {
min-height: 0;
height: auto;
overflow: auto;
}
.scene-body__authoring-evidence .authoring-visual--repair {
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
gap: 0.55rem 0.8rem;
}
.scene-body__authoring-evidence .authoring-repair__diagnostic,
.scene-body__authoring-evidence .authoring-repair__correction {
min-height: 0;
padding: 1rem;
}
.scene-body__authoring-evidence .authoring-visual--inventory,
.scene-body__authoring-evidence .authoring-visual--graph,
.scene-body__authoring-evidence .authoring-visual--repair {
min-height: 0;
height: 100%;
}
.scene-body__authoring-evidence .authoring-visual--graph .authoring-graph__node {
min-width: 12rem;
min-height: 8rem;
}
.scene-body__authoring-evidence .authoring-visual--repair[data-authoring-focus="diagnose"] {
grid-template-columns: 1fr;
}
.scene-body__authoring-evidence .authoring-visual--repair[data-authoring-focus="diagnose"] .authoring-repair__connector,
.scene-body__authoring-evidence .authoring-visual--repair[data-authoring-focus="diagnose"] .authoring-repair__correction,
.scene-body__authoring-evidence .authoring-visual--repair[data-authoring-focus="diagnose"] .authoring-repair__status {
display: none;
}
.scene-body__authoring-evidence .authoring-visual--repair[data-authoring-focus="diagnose"] .authoring-repair__diagnostic {
grid-column: 1;
min-height: 0;
}
.scene-body__authoring-evidence .authoring-visual--repair[data-authoring-focus="repair"] {
grid-template-columns: 1fr;
}
.scene-body__authoring-evidence .authoring-visual--repair[data-authoring-focus="repair"] .authoring-repair__diagnostic,
.scene-body__authoring-evidence .authoring-visual--repair[data-authoring-focus="repair"] .authoring-repair__connector {
display: none;
}
.scene-body__authoring-evidence .authoring-visual--repair[data-authoring-focus="repair"] .authoring-repair__correction {
grid-column: 1;
min-height: 0;
}
.scene-body__authoring-composition .scene-body__authoring-loop {
grid-template-columns: repeat(5, minmax(0, 1fr));
flex: 0 0 auto;
min-height: 6.4rem;
margin-top: 0;
padding-top: 0.55rem;
}
.scene-body__authoring-composition .scene-body__authoring-node {
min-height: 5.8rem;
padding: 0.55rem;
}
.scene-body__authoring-evidence,
.scene-body__authoring-node,
.authoring-visual {
transition: opacity 180ms ease, transform 180ms ease, border-color 180ms ease;
}
@media (prefers-reduced-motion: reduce) {
.scene-body__authoring-evidence,
.scene-body__authoring-node,
.authoring-visual {
transition: none;
}
}
@media (max-width: 1050px) {
.scene-body__authoring-composition .scene-body__authoring-loop {
grid-template-columns: repeat(5, minmax(9.5rem, 1fr));
overflow-x: auto;
padding-bottom: 0.35rem;
}
}
.scene-body__lifecycle-number {
display: inline-flex;
align-items: center;
@@ -1971,19 +1760,6 @@
font: 800 0.72rem/1 var(--font-mono, monospace);
}
.scene-body__authoring-number {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.4rem;
height: 1.4rem;
border-radius: 50%;
background: oklch(0.7 0.16 195);
color: oklch(0.12 0.025 250);
font-size: 0.7rem;
font-weight: 600;
}
/* Demo lifecycle scene */
.demo-lifecycle-scene {
display: grid;
@@ -3706,6 +3482,11 @@
scrollbar-width: none;
}
.presentation-stage__primary > .prepared-lifecycle-scene + .scene-body__discussion-links {
margin-top: 0;
padding-top: 0.3rem;
}
.prepared-lifecycle-scene__rail::-webkit-scrollbar {
display: none;
}
@@ -4145,10 +3926,6 @@
}
@container presentation-canvas (max-width: 1050px) {
.scene-body__authoring-composition .scene-body__authoring-evidence {
padding: 0.75rem 0.85rem;
}
.prepared-lifecycle-scene[data-presentation-surface="editorial"] {
grid-template-columns: minmax(10.5rem, 0.28fr) minmax(0, 0.72fr);
gap: 0.6rem;
@@ -191,38 +191,6 @@ export const presenterNotes = [
"NodeUse **invokes a NodeDef handler**, reduces state, records trace, and routes the declared outcome.",
["Thesis Workflow Core Model"],
),
beatNote(
"authoring",
"discover",
10,
"Before authoring, a client can discover sources, capabilities, and schemas instead of guessing at hidden interfaces.",
["CLI documentation", "Draft authoring API", "Challenge UX findings"],
{ qnaBranchIds: ["why-schemas", "validation-diagnostics"] },
),
beatNote(
"authoring",
"author",
10,
"Focused operations let an external agent change a mutable Draft while preserving a clear lifecycle boundary.",
["CLI documentation", "Draft authoring API"],
{ qnaBranchIds: ["raw-plan-import"] },
),
beatNote(
"authoring",
"diagnose",
10,
"Validation returns structured diagnostics, affected paths, repair hints, and suggested next actions.",
["Validation and diagnostics", "Challenge UX findings"],
{ qnaBranchIds: ["validation-diagnostics"] },
),
beatNote(
"authoring",
"repair",
10,
"These surfaces make invalid intermediate drafts repairable; they support a loop, but no hint guarantees success.",
["Validation and diagnostics", "Challenge UX findings"],
{ warning: "Do not promise that diagnostics automatically repair every workflow.", qnaBranchIds: ["validation-diagnostics"] },
),
beatNote(
"agent-handoff",
"request",
@@ -240,23 +208,32 @@ export const presenterNotes = [
9,
"The later issue-review example first inspects configured local.lda_docs, report, and issue-board capabilities.",
["examples/lda_report_workflow", "deployment inspect replay evidence"],
{ qnaBranchIds: ["prepared-replay-boundary"] },
{ qnaBranchIds: ["prepared-replay-boundary", "why-schemas", "validation-diagnostics"] },
),
beatNote(
"prepared-lifecycle",
"draft",
9,
"It creates and edits a Draft for report generation, making the proposal visible before execution.",
["examples/lda_report_workflow", "deployment inspect replay evidence"],
["examples/lda_report_workflow", "deployment inspect replay evidence", "CLI documentation", "Draft authoring API"],
{ qnaBranchIds: ["raw-plan-import"] },
),
beatNote(
"prepared-lifecycle",
"validate",
"diagnose",
9,
"It validates incomplete state, exposes a missing output binding, and applies a targeted repair.",
"Validation returns structured diagnostics, affected paths, repair hints, and suggested next actions.",
["examples/lda_report_workflow", "deployment inspect replay evidence"],
{ qnaBranchIds: ["validation-diagnostics"] },
),
beatNote(
"prepared-lifecycle",
"repair",
9,
"These surfaces make invalid intermediate drafts repairable; they support a loop, but no hint guarantees success.",
["Validation and diagnostics", "Challenge UX findings"],
{ warning: "Do not promise that diagnostics automatically repair every workflow.", qnaBranchIds: ["validation-diagnostics"] },
),
beatNote(
"prepared-lifecycle",
"artifact",
@@ -146,6 +146,15 @@ describe("defense storyboard catalog", () => {
"mcp-agent-scale",
"not-just-scripts",
]);
expect(
discussionBranches
.filter((branch) => branch.parentSceneId === "prepared-lifecycle")
.map((branch) => branch.id),
).toEqual(expect.arrayContaining([
"raw-plan-import",
"validation-diagnostics",
"why-schemas",
]));
for (const branch of discussionBranches) {
expect(branch.title.length).toBeGreaterThan(0);
expect(branch.summary.length).toBeGreaterThan(0);