feat: show factual Scene 10 run state

This commit is contained in:
lda
2026-07-09 18:31:23 +07:00 Verified
parent 1ebb0a9964
commit 2606d38f70
6 changed files with 88 additions and 46 deletions
@@ -103,9 +103,7 @@ describe("DemoWorkflowScene", () => {
renderBeat("approval", "interrupt-evidence"); renderBeat("approval", "interrupt-evidence");
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "approval"); expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "approval");
expect(screen.getByLabelText("typed interrupt contract")).toHaveTextContent( expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
"run_recorded_lda_report",
);
}); });
it("makes the Scene 10 approval contract the primary visual", () => { it("makes the Scene 10 approval contract the primary visual", () => {
@@ -113,9 +111,7 @@ describe("DemoWorkflowScene", () => {
const stage = screen.getByLabelText("demo workflow stage"); const stage = screen.getByLabelText("demo workflow stage");
expect(stage).toHaveAttribute("data-demo-layout", "approval"); expect(stage).toHaveAttribute("data-demo-layout", "approval");
expect(screen.getByLabelText("typed interrupt contract")).toHaveAttribute("data-hero", "true"); expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
expect(screen.getByLabelText("typed interrupt contract")).toHaveTextContent("Operator decision");
expect(screen.getByRole("group", { name: /issue review resume/i })).toHaveTextContent("submitted / cancelled");
}); });
it("marks trace beat as evidence layout via guided product moment", () => { it("marks trace beat as evidence layout via guided product moment", () => {
@@ -186,8 +182,8 @@ describe("DemoWorkflowScene", () => {
it("keeps approval beat contract via guided product moment", () => { it("keeps approval beat contract via guided product moment", () => {
renderBeat("approval", "interrupt-evidence"); renderBeat("approval", "interrupt-evidence");
expect(screen.getByLabelText("typed interrupt contract")).toHaveAttribute("data-hero", "true");
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "approval"); expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "approval");
expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
}); });
it("wires approval actions into the Scene 10 schema approval surface", () => { it("wires approval actions into the Scene 10 schema approval surface", () => {
@@ -205,16 +201,11 @@ describe("DemoWorkflowScene", () => {
expect(screen.getByRole("button", { name: "Cancel" })).toBeEnabled(); expect(screen.getByRole("button", { name: "Cancel" })).toBeEnabled();
}); });
it("shows a schema approval surface for the approval beat instead of raw schema as the primary visual", () => { it("shows a factual decision form for the approval beat instead of raw schema as the primary visual", () => {
renderBeat("approval", "interrupt-evidence"); renderBeat("approval", "interrupt-evidence");
const approval = screen.getByRole("group", { name: /issue review resume/i }); expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
expect(within(approval).getByText("Schema-backed decision")).toBeInTheDocument(); expect(screen.getByText("Workflow input")).toBeInTheDocument();
expect(within(approval).getByText("selected_issue_ids")).toBeInTheDocument();
expect(within(approval).getByText("[\"risk-1\"]")).toBeInTheDocument();
expect(within(approval).queryByText("approved")).not.toBeInTheDocument();
expect(within(approval).getByRole("button", { name: /submit/i })).toBeDisabled();
expect(within(approval).getByRole("button", { name: /cancel/i })).toBeDisabled();
}); });
it("keeps raw resume schema visible only in interrupt preview mode", () => { it("keeps raw resume schema visible only in interrupt preview mode", () => {
@@ -16,7 +16,17 @@ const resumeOperation = projectOperationPresentation(runResume);
const demo = { const demo = {
state: { mode: "replay", phase: "review", events: recording.events, appliedCount: 3, autoplay: false, error: null }, state: { mode: "replay", phase: "review", events: recording.events, appliedCount: 3, autoplay: false, error: null },
inFlight: false, inFlight: false,
interruptPayload: null, interruptPayload: {
report_markdown: "# lda.chat Thesis And Project Readiness Report\n\nThe workflow substrate is ready for the defense demo.",
proposed_issues: [
{
id: "risk-1",
title: "Prepare the defense walkthrough",
body: "Review the live and replay paths before the defense.",
severity: "medium",
},
],
},
output: null, output: null,
trace: null, trace: null,
missingDeploymentMessage: null, missingDeploymentMessage: null,
@@ -34,7 +44,7 @@ const demo = {
} as unknown as DemoTimelineController; } as unknown as DemoTimelineController;
describe("GuidedProductMoment", () => { describe("GuidedProductMoment", () => {
it("makes approval the primary product decision", () => { it("makes approval the primary product decision with factual panels", () => {
render( render(
<GuidedProductMoment <GuidedProductMoment
beat={findBeat("interrupt-evidence", "approval")!} beat={findBeat("interrupt-evidence", "approval")!}
@@ -54,7 +64,11 @@ describe("GuidedProductMoment", () => {
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "approval"); expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "approval");
expect(screen.getByText(/Run is paused/i)).toBeInTheDocument(); expect(screen.getByText(/Run is paused/i)).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Submit" })).toBeEnabled(); expect(screen.getByText("Workflow input")).toBeInTheDocument();
expect(screen.getByText("project-brief.md")).toBeInTheDocument();
expect(screen.getByText("issue-board.json")).toBeInTheDocument();
expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
expect(screen.getByText("Output not created yet")).toBeInTheDocument();
}); });
it("makes resume operation proof primary on resume beat", () => { it("makes resume operation proof primary on resume beat", () => {
@@ -91,6 +105,6 @@ describe("GuidedProductMoment", () => {
); );
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveClass("guided-product-moment"); expect(screen.getByRole("region", { name: /current product moment/i })).toHaveClass("guided-product-moment");
expect(screen.getByLabelText("typed interrupt contract")).toHaveAttribute("data-hero", "true"); expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
}); });
}); });
@@ -1,14 +1,15 @@
import type { DemoTimelineController } from "../demo/useDemoTimeline.js"; import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
import type { DemoApprovalActions } from "./demo-approval-actions.js"; import type { DemoApprovalActions } from "./demo-approval-actions.js";
import { projectDemoRunFacts } from "./demo-run-facts.js";
import type { import type {
InterruptContractPresentation, InterruptContractPresentation,
OperationPresentation, OperationPresentation,
} from "./demo-workflow-model.js"; } from "./demo-workflow-model.js";
import { DemoOutcomePanel } from "./DemoOutcomePanel.js";
import { InterruptContractPreview } from "./InterruptContractPreview.js";
import { OperationBlock } from "./OperationBlock.js";
import type { SceneBeatDefinition } from "./storyboard.js";
import { demoBeatLensForBeat } from "./demo-workflow-model.js"; import { demoBeatLensForBeat } from "./demo-workflow-model.js";
import { InterruptDecisionForm } from "./InterruptDecisionForm.js";
import { OperationBlock } from "./OperationBlock.js";
import { RunInputFacts, RunOutputFacts, RunTraceFacts } from "./RunFactsPanel.js";
import type { SceneBeatDefinition } from "./storyboard.js";
export type GuidedProductMomentProps = { export type GuidedProductMomentProps = {
readonly beat: SceneBeatDefinition; readonly beat: SceneBeatDefinition;
@@ -44,6 +45,7 @@ export const GuidedProductMoment = ({
}: GuidedProductMomentProps) => { }: GuidedProductMomentProps) => {
const moment = momentForBeat(beat.id); const moment = momentForBeat(beat.id);
const lens = demoBeatLensForBeat(beat.id); const lens = demoBeatLensForBeat(beat.id);
const facts = projectDemoRunFacts(demo);
const runResume = demo.state.events.find((event) => event.stage === "run_resume"); const runResume = demo.state.events.find((event) => event.stage === "run_resume");
return ( return (
@@ -56,27 +58,34 @@ export const GuidedProductMoment = ({
<div className="guided-product-moment__primary"> <div className="guided-product-moment__primary">
{moment === "approval" && contract ? ( {moment === "approval" && contract ? (
<InterruptContractPreview <div className="guided-product-moment__approval-grid">
contract={contract} <RunInputFacts facts={facts} />
mode="approval" <InterruptDecisionForm
hero interrupt={facts.interrupt}
approvalActions={approvalActions} runId={demo.state.events.find((e) => e.stage === "run_start")?.resultingIds.runId ?? "unknown"}
/> onSubmit={(ids, comment) => approvalActions?.submit(ids, comment)}
onCancel={() => approvalActions?.cancel()}
terminalOutcome={approvalActions?.state === "submitted" ? "submitted" :
approvalActions?.state === "cancelled" ? "cancelled" : undefined}
/>
<RunOutputFacts facts={facts} />
</div>
) : null} ) : null}
{moment === "resume" && runResume ? ( {moment === "resume" && runResume ? (
<OperationBlock <div className="guided-product-moment__resume-grid">
event={runResume} <OperationBlock
variant="expanded" event={runResume}
openEvidence={openEvidence} variant="expanded"
/> openEvidence={openEvidence}
/>
<RunOutputFacts facts={facts} />
</div>
) : null} ) : null}
{(moment === "output" || moment === "trace") ? ( {moment === "output" ? (
<DemoOutcomePanel <RunOutputFacts facts={facts} />
beatId={beat.id} ) : null}
lens={lens} {moment === "trace" ? (
operation={operation} <RunTraceFacts facts={facts} />
contract={contract}
/>
) : null} ) : null}
</div> </div>
</section> </section>
@@ -201,9 +201,11 @@ describe("PresentationRoute", () => {
const { PresentationRoute } = await import("./PresentationRoute.js"); const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />); render(<PresentationRoute />);
expect(await screen.findByLabelText("typed interrupt contract")).toBeInTheDocument(); expect(await screen.findByText("Workflow input")).toBeInTheDocument();
expect(screen.getByRole("group", { name: /issue review resume/i })).toBeInTheDocument(); expect(screen.getByText("project-brief.md")).toBeInTheDocument();
expect(screen.getByText(/Recorded resume payload for this decision/i)).toBeInTheDocument(); expect(screen.getByText("issue-board.json")).toBeInTheDocument();
expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
expect(screen.getByText("Output not created yet")).toBeInTheDocument();
}); });
it("chat run action advances the replay timeline when no live server is configured", async () => { it("chat run action advances the replay timeline when no live server is configured", async () => {
@@ -250,7 +252,8 @@ describe("PresentationRoute", () => {
await user.click(cancelButton); await user.click(cancelButton);
}); });
expect(screen.getByText(/Outcome: cancelled/i)).toBeInTheDocument(); expect(screen.queryByRole("button", { name: "Submit" })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Cancel" })).not.toBeInTheDocument();
expect(window.location.hash).toBe("#scene/interrupt-evidence/approval"); expect(window.location.hash).toBe("#scene/interrupt-evidence/approval");
expect(screen.queryByLabelText("workflow.runs.resume operation")).not.toBeInTheDocument(); expect(screen.queryByLabelText("workflow.runs.resume operation")).not.toBeInTheDocument();
}); });
@@ -4,6 +4,9 @@ export type DemoApprovalActions = {
readonly state: DemoApprovalUiState; readonly state: DemoApprovalUiState;
readonly canSubmit: boolean; readonly canSubmit: boolean;
readonly canCancel: boolean; readonly canCancel: boolean;
readonly submit: () => Promise<void>; readonly submit: (
selectedIssueIds?: ReadonlyArray<string>,
comment?: string,
) => Promise<void>;
readonly cancel: () => Promise<void>; readonly cancel: () => Promise<void>;
}; };
@@ -1668,3 +1668,25 @@
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.04em; letter-spacing: 0.04em;
} }
/* Factual Scene 10 layout grids */
.guided-product-moment__approval-grid {
display: grid;
grid-template-columns: 1fr 1.4fr 1fr;
gap: 1rem;
align-items: start;
}
.guided-product-moment__resume-grid {
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 1rem;
align-items: start;
}
@media (max-width: 900px) {
.guided-product-moment__approval-grid,
.guided-product-moment__resume-grid {
grid-template-columns: 1fr;
}
}