feat: show factual Scene 10 run state
This commit is contained in:
@@ -103,9 +103,7 @@ describe("DemoWorkflowScene", () => {
|
||||
renderBeat("approval", "interrupt-evidence");
|
||||
|
||||
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "approval");
|
||||
expect(screen.getByLabelText("typed interrupt contract")).toHaveTextContent(
|
||||
"run_recorded_lda_report",
|
||||
);
|
||||
expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("makes the Scene 10 approval contract the primary visual", () => {
|
||||
@@ -113,9 +111,7 @@ describe("DemoWorkflowScene", () => {
|
||||
|
||||
const stage = screen.getByLabelText("demo workflow stage");
|
||||
expect(stage).toHaveAttribute("data-demo-layout", "approval");
|
||||
expect(screen.getByLabelText("typed interrupt contract")).toHaveAttribute("data-hero", "true");
|
||||
expect(screen.getByLabelText("typed interrupt contract")).toHaveTextContent("Operator decision");
|
||||
expect(screen.getByRole("group", { name: /issue review resume/i })).toHaveTextContent("submitted / cancelled");
|
||||
expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
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", () => {
|
||||
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("group", { name: /operator resume decision/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("wires approval actions into the Scene 10 schema approval surface", () => {
|
||||
@@ -205,16 +201,11 @@ describe("DemoWorkflowScene", () => {
|
||||
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");
|
||||
|
||||
const approval = screen.getByRole("group", { name: /issue review resume/i });
|
||||
expect(within(approval).getByText("Schema-backed decision")).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();
|
||||
expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
|
||||
expect(screen.getByText("Workflow input")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps raw resume schema visible only in interrupt preview mode", () => {
|
||||
|
||||
@@ -16,7 +16,17 @@ const resumeOperation = projectOperationPresentation(runResume);
|
||||
const demo = {
|
||||
state: { mode: "replay", phase: "review", events: recording.events, appliedCount: 3, autoplay: false, error: null },
|
||||
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,
|
||||
trace: null,
|
||||
missingDeploymentMessage: null,
|
||||
@@ -34,7 +44,7 @@ const demo = {
|
||||
} as unknown as DemoTimelineController;
|
||||
|
||||
describe("GuidedProductMoment", () => {
|
||||
it("makes approval the primary product decision", () => {
|
||||
it("makes approval the primary product decision with factual panels", () => {
|
||||
render(
|
||||
<GuidedProductMoment
|
||||
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.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", () => {
|
||||
@@ -91,6 +105,6 @@ describe("GuidedProductMoment", () => {
|
||||
);
|
||||
|
||||
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 { DemoApprovalActions } from "./demo-approval-actions.js";
|
||||
import { projectDemoRunFacts } from "./demo-run-facts.js";
|
||||
import type {
|
||||
InterruptContractPresentation,
|
||||
OperationPresentation,
|
||||
} 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 { 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 = {
|
||||
readonly beat: SceneBeatDefinition;
|
||||
@@ -44,6 +45,7 @@ export const GuidedProductMoment = ({
|
||||
}: GuidedProductMomentProps) => {
|
||||
const moment = momentForBeat(beat.id);
|
||||
const lens = demoBeatLensForBeat(beat.id);
|
||||
const facts = projectDemoRunFacts(demo);
|
||||
const runResume = demo.state.events.find((event) => event.stage === "run_resume");
|
||||
|
||||
return (
|
||||
@@ -56,27 +58,34 @@ export const GuidedProductMoment = ({
|
||||
|
||||
<div className="guided-product-moment__primary">
|
||||
{moment === "approval" && contract ? (
|
||||
<InterruptContractPreview
|
||||
contract={contract}
|
||||
mode="approval"
|
||||
hero
|
||||
approvalActions={approvalActions}
|
||||
/>
|
||||
<div className="guided-product-moment__approval-grid">
|
||||
<RunInputFacts facts={facts} />
|
||||
<InterruptDecisionForm
|
||||
interrupt={facts.interrupt}
|
||||
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}
|
||||
{moment === "resume" && runResume ? (
|
||||
<OperationBlock
|
||||
event={runResume}
|
||||
variant="expanded"
|
||||
openEvidence={openEvidence}
|
||||
/>
|
||||
<div className="guided-product-moment__resume-grid">
|
||||
<OperationBlock
|
||||
event={runResume}
|
||||
variant="expanded"
|
||||
openEvidence={openEvidence}
|
||||
/>
|
||||
<RunOutputFacts facts={facts} />
|
||||
</div>
|
||||
) : null}
|
||||
{(moment === "output" || moment === "trace") ? (
|
||||
<DemoOutcomePanel
|
||||
beatId={beat.id}
|
||||
lens={lens}
|
||||
operation={operation}
|
||||
contract={contract}
|
||||
/>
|
||||
{moment === "output" ? (
|
||||
<RunOutputFacts facts={facts} />
|
||||
) : null}
|
||||
{moment === "trace" ? (
|
||||
<RunTraceFacts facts={facts} />
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -201,9 +201,11 @@ describe("PresentationRoute", () => {
|
||||
const { PresentationRoute } = await import("./PresentationRoute.js");
|
||||
render(<PresentationRoute />);
|
||||
|
||||
expect(await screen.findByLabelText("typed interrupt contract")).toBeInTheDocument();
|
||||
expect(screen.getByRole("group", { name: /issue review resume/i })).toBeInTheDocument();
|
||||
expect(screen.getByText(/Recorded resume payload for this decision/i)).toBeInTheDocument();
|
||||
expect(await screen.findByText("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("chat run action advances the replay timeline when no live server is configured", async () => {
|
||||
@@ -250,7 +252,8 @@ describe("PresentationRoute", () => {
|
||||
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(screen.queryByLabelText("workflow.runs.resume operation")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -4,6 +4,9 @@ export type DemoApprovalActions = {
|
||||
readonly state: DemoApprovalUiState;
|
||||
readonly canSubmit: boolean;
|
||||
readonly canCancel: boolean;
|
||||
readonly submit: () => Promise<void>;
|
||||
readonly submit: (
|
||||
selectedIssueIds?: ReadonlyArray<string>,
|
||||
comment?: string,
|
||||
) => Promise<void>;
|
||||
readonly cancel: () => Promise<void>;
|
||||
};
|
||||
|
||||
@@ -1668,3 +1668,25 @@
|
||||
text-transform: uppercase;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user