feat: add prepared lifecycle scene
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { loadCanonicalDemoRecording } from "../demo/timeline/replay.js";
|
||||
import { initialDemoTimelineState } from "../demo/timeline/reducer.js";
|
||||
import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
|
||||
import { DemoLifecycleScene } from "./DemoLifecycleScene.js";
|
||||
import { findBeat, findScene } from "./storyboard.js";
|
||||
|
||||
const recording = loadCanonicalDemoRecording();
|
||||
const demo: DemoTimelineController = {
|
||||
state: {
|
||||
...initialDemoTimelineState,
|
||||
mode: "replay",
|
||||
phase: "paused",
|
||||
events: recording.events,
|
||||
appliedCount: recording.events.length,
|
||||
autoplay: false,
|
||||
},
|
||||
inFlight: false,
|
||||
interruptPayload: null,
|
||||
output: null,
|
||||
trace: null,
|
||||
missingDeploymentMessage: null,
|
||||
recordingId: recording.recordingId,
|
||||
canStart: true,
|
||||
setMode: vi.fn(),
|
||||
start: vi.fn(),
|
||||
pause: vi.fn(),
|
||||
play: vi.fn(),
|
||||
next: vi.fn(async () => {}),
|
||||
submitSelectedIssues: vi.fn(async () => {}),
|
||||
cancelReview: vi.fn(async () => {}),
|
||||
restart: vi.fn(),
|
||||
primeReplayToStage: vi.fn(),
|
||||
};
|
||||
|
||||
const renderBeat = (beatId: "draft" | "artifact" | "deployment" | "ready-run") => {
|
||||
const scene = findScene("prepared-lifecycle");
|
||||
const beat = findBeat("prepared-lifecycle", beatId);
|
||||
if (!scene || !beat) throw new Error(`missing prepared-lifecycle/${beatId}`);
|
||||
render(<DemoLifecycleScene scene={scene} beat={beat} demo={demo} />);
|
||||
};
|
||||
|
||||
describe("DemoLifecycleScene", () => {
|
||||
it("renders prepared draft context honestly", () => {
|
||||
renderBeat("draft");
|
||||
expect(screen.getByRole("region", { name: /demo lifecycle detail/i })).toHaveAttribute("data-active-lifecycle", "draft");
|
||||
expect(screen.getByText("prepared context")).toBeInTheDocument();
|
||||
expect(screen.getByText("examples/lda_report_workflow")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders artifact and deployment facts from replay evidence", () => {
|
||||
renderBeat("deployment");
|
||||
expect(screen.getByText("lda_report_case_study.default")).toBeInTheDocument();
|
||||
expect(screen.getByText("block")).toBeInTheDocument();
|
||||
expect(screen.getByText(/local\.lda_docs/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders run readiness without claiming output exists yet", () => {
|
||||
renderBeat("ready-run");
|
||||
expect(screen.getByText("run_recorded_lda_report")).toBeInTheDocument();
|
||||
expect(screen.getByText("interrupted")).toBeInTheDocument();
|
||||
expect(screen.queryByText(/created issues/i)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
|
||||
import { projectDemoLifecycleFacts } from "./demo-lifecycle-facts.js";
|
||||
import { StageCaption } from "./StageCaption.js";
|
||||
import type { SceneBeatDefinition, SceneDefinition } from "./storyboard.js";
|
||||
|
||||
type DemoLifecycleSceneProps = {
|
||||
readonly scene: SceneDefinition;
|
||||
readonly beat: SceneBeatDefinition;
|
||||
readonly demo: DemoTimelineController;
|
||||
};
|
||||
|
||||
const stages = [
|
||||
{ id: "draft", label: "Draft", description: "Prepared authoring context" },
|
||||
{ id: "artifact", label: "Artifact", description: "Immutable versioned workflow" },
|
||||
{ id: "deployment", label: "Deployment", description: "Configured source bindings" },
|
||||
{ id: "ready-run", label: "Run-ready", description: "Persisted run can start" },
|
||||
] as const;
|
||||
|
||||
export const DemoLifecycleScene = ({ scene, beat, demo }: DemoLifecycleSceneProps) => {
|
||||
const facts = projectDemoLifecycleFacts(demo);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StageCaption eyebrow="Product lifecycle" title={scene.title}>
|
||||
<p>{beat.caption}</p>
|
||||
</StageCaption>
|
||||
<section
|
||||
className="demo-lifecycle-scene"
|
||||
aria-label="demo lifecycle detail"
|
||||
data-active-lifecycle={beat.id}
|
||||
>
|
||||
<ol className="demo-lifecycle-scene__rail">
|
||||
{stages.map((stage) => (
|
||||
<li key={stage.id} data-active={stage.id === beat.id ? "true" : "false"}>
|
||||
<strong>{stage.label}</strong>
|
||||
<span>{stage.description}</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
|
||||
<article className="demo-lifecycle-scene__proof">
|
||||
<h3>{stages.find((stage) => stage.id === beat.id)?.label ?? "Lifecycle"}</h3>
|
||||
{beat.id === "draft" && (
|
||||
<dl>
|
||||
<dt>Workflow</dt><dd>{facts.draft.label}</dd>
|
||||
<dt>Status</dt><dd>{facts.draft.status}</dd>
|
||||
<dt>Source</dt><dd>{facts.draft.source}</dd>
|
||||
</dl>
|
||||
)}
|
||||
{beat.id === "artifact" && (
|
||||
<dl>
|
||||
<dt>Artifact</dt><dd>{facts.artifact.id}</dd>
|
||||
<dt>Version</dt><dd>{facts.artifact.version ?? "unavailable"}</dd>
|
||||
</dl>
|
||||
)}
|
||||
{beat.id === "deployment" && (
|
||||
<dl>
|
||||
<dt>Deployment</dt><dd>{facts.deployment.id}</dd>
|
||||
<dt>Drift policy</dt><dd>{facts.deployment.driftPolicy}</dd>
|
||||
<dt>Bindings</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
{facts.deployment.bindings.map(([from, to]) => (
|
||||
<li key={`${from}:${to}`}>{from} -> {to}</li>
|
||||
))}
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
)}
|
||||
{beat.id === "ready-run" && (
|
||||
<dl>
|
||||
<dt>Run</dt><dd>{facts.run.id ?? "not started"}</dd>
|
||||
<dt>Status</dt><dd>{facts.run.status}</dd>
|
||||
<dt>Deployment</dt><dd>{facts.deployment.id}</dd>
|
||||
</dl>
|
||||
)}
|
||||
</article>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
type SceneDefinition,
|
||||
type SceneBeatDefinition,
|
||||
} from "./storyboard.js";
|
||||
import { DemoLifecycleScene } from "./DemoLifecycleScene.js";
|
||||
import { DemoWorkflowScene } from "./DemoWorkflowScene.js";
|
||||
import { StageCaption } from "./StageCaption.js";
|
||||
import { ArchitectureScene } from "./scenes/ArchitectureScene.js";
|
||||
@@ -325,7 +326,9 @@ export const SceneBody = ({ location, demo, selectedNodeId, selectNode, openEvid
|
||||
return <AuthoringScene scene={scene} beat={beat} />;
|
||||
case "agent":
|
||||
return <AgentHandoffScene scene={scene} beat={beat} />;
|
||||
case "demo":
|
||||
case "demo-lifecycle":
|
||||
return <DemoLifecycleScene scene={scene} beat={beat} demo={demo} />;
|
||||
case "demo":
|
||||
return (
|
||||
<DemoWorkflowScene
|
||||
scene={scene}
|
||||
|
||||
@@ -1066,6 +1066,84 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Demo lifecycle scene */
|
||||
.demo-lifecycle-scene {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(18rem, 0.8fr) minmax(0, 1.2fr);
|
||||
gap: 1rem;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.demo-lifecycle-scene__rail {
|
||||
display: grid;
|
||||
gap: 0.65rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.demo-lifecycle-scene__rail li,
|
||||
.demo-lifecycle-scene__proof {
|
||||
border: 1px solid var(--stage-line);
|
||||
border-radius: var(--presentation-panel-radius, 0.85rem);
|
||||
background: var(--stage-surface);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.demo-lifecycle-scene__rail li {
|
||||
padding: 0.8rem 0.9rem;
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.demo-lifecycle-scene__rail li[data-active="true"] {
|
||||
border-color: var(--accent-cyan);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.demo-lifecycle-scene__rail strong,
|
||||
.demo-lifecycle-scene__rail span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.demo-lifecycle-scene__rail span {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.demo-lifecycle-scene__proof {
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 1rem 1.1rem;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.demo-lifecycle-scene__proof::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.demo-lifecycle-scene__proof h3 {
|
||||
margin: 0 0 1rem;
|
||||
color: var(--accent-cyan);
|
||||
font: 700 1.35rem/1 var(--font-interface);
|
||||
}
|
||||
|
||||
.demo-lifecycle-scene__proof dl {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
gap: 0.55rem 0.9rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.demo-lifecycle-scene__proof dt {
|
||||
color: var(--text-muted);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.demo-lifecycle-scene__proof dd {
|
||||
margin: 0;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.scene-body__lifecycle-arrow {
|
||||
position: absolute;
|
||||
right: -0.55rem;
|
||||
|
||||
Reference in New Issue
Block a user