feat: stage scene 10 as guided product moment
This commit is contained in:
@@ -23,6 +23,7 @@ const demoController = (
|
|||||||
submitSelectedIssues: vi.fn(async () => {}),
|
submitSelectedIssues: vi.fn(async () => {}),
|
||||||
cancelReview: vi.fn(async () => {}),
|
cancelReview: vi.fn(async () => {}),
|
||||||
restart: vi.fn(),
|
restart: vi.fn(),
|
||||||
|
primeReplayToStage: vi.fn(),
|
||||||
...overrides,
|
...overrides,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ const demo: DemoTimelineController = {
|
|||||||
submitSelectedIssues: noopAsync,
|
submitSelectedIssues: noopAsync,
|
||||||
cancelReview: noopAsync,
|
cancelReview: noopAsync,
|
||||||
restart: noop,
|
restart: noop,
|
||||||
|
primeReplayToStage: noop,
|
||||||
};
|
};
|
||||||
|
|
||||||
const requireSceneBeat = (sceneId: string, beatId: string) => {
|
const requireSceneBeat = (sceneId: string, beatId: string) => {
|
||||||
@@ -98,17 +99,13 @@ describe("DemoWorkflowScene", () => {
|
|||||||
expect(contract).toHaveTextContent("run_recorded_lda_report");
|
expect(contract).toHaveTextContent("run_recorded_lda_report");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("carries the same graph and contract into Scene 10 approval", () => {
|
it("carries the contract into Scene 10 approval via guided product moment", () => {
|
||||||
renderBeat("approval", "interrupt-evidence");
|
renderBeat("approval", "interrupt-evidence");
|
||||||
|
|
||||||
expect(screen.getByLabelText("workflow graph")).toBeInTheDocument();
|
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "approval");
|
||||||
expect(screen.getByLabelText("typed interrupt contract")).toHaveTextContent(
|
expect(screen.getByLabelText("typed interrupt contract")).toHaveTextContent(
|
||||||
"run_recorded_lda_report",
|
"run_recorded_lda_report",
|
||||||
);
|
);
|
||||||
expect(screen.getByRole("button", { name: /issue review/i })).toHaveAttribute(
|
|
||||||
"data-execution-state",
|
|
||||||
"current",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("makes the Scene 10 approval contract the primary visual", () => {
|
it("makes the Scene 10 approval contract the primary visual", () => {
|
||||||
@@ -119,23 +116,22 @@ describe("DemoWorkflowScene", () => {
|
|||||||
expect(screen.getByLabelText("typed interrupt contract")).toHaveAttribute("data-hero", "true");
|
expect(screen.getByLabelText("typed interrupt contract")).toHaveAttribute("data-hero", "true");
|
||||||
expect(screen.getByLabelText("typed interrupt contract")).toHaveTextContent("Operator decision");
|
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: /issue review resume/i })).toHaveTextContent("submitted / cancelled");
|
||||||
expect(screen.getByLabelText("workflow graph")).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("marks trace beat as evidence layout", () => {
|
it("marks trace beat as evidence layout via guided product moment", () => {
|
||||||
renderBeat("trace", "interrupt-evidence");
|
renderBeat("trace", "interrupt-evidence");
|
||||||
|
|
||||||
expect(screen.getByLabelText("demo workflow stage")).toHaveAttribute("data-demo-layout", "evidence");
|
expect(screen.getByLabelText("demo workflow stage")).toHaveAttribute("data-demo-layout", "evidence");
|
||||||
expect(screen.getByLabelText("workflow.runs.trace operation")).toBeInTheDocument();
|
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "trace");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders resume and trace operations as expanded evidence summaries", () => {
|
it("renders resume and trace via guided product moment", () => {
|
||||||
const { unmount } = renderBeat("resume", "interrupt-evidence");
|
const { unmount } = renderBeat("resume", "interrupt-evidence");
|
||||||
expect(screen.getByLabelText("workflow.runs.resume operation")).toBeInTheDocument();
|
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "resume");
|
||||||
unmount();
|
unmount();
|
||||||
|
|
||||||
renderBeat("trace", "interrupt-evidence");
|
renderBeat("trace", "interrupt-evidence");
|
||||||
expect(screen.getByLabelText("workflow.runs.trace operation")).toBeInTheDocument();
|
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "trace");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes run proof into full graph beats", () => {
|
it("passes run proof into full graph beats", () => {
|
||||||
@@ -145,15 +141,14 @@ describe("DemoWorkflowScene", () => {
|
|||||||
unmount();
|
unmount();
|
||||||
|
|
||||||
renderBeat("output", "interrupt-evidence");
|
renderBeat("output", "interrupt-evidence");
|
||||||
expect(screen.getByLabelText("workflow graph proof")).toHaveTextContent("JSON-RPC evidence");
|
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "output");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("marks outcome-panel layouts so CSS can clear the receipt row", () => {
|
it("marks outcome-panel layouts via guided product moment", () => {
|
||||||
renderBeat("resume", "interrupt-evidence");
|
renderBeat("resume", "interrupt-evidence");
|
||||||
|
|
||||||
expect(screen.getByLabelText("demo workflow stage")).toHaveAttribute("data-demo-layout", "operation");
|
expect(screen.getByLabelText("demo workflow stage")).toHaveAttribute("data-demo-layout", "operation");
|
||||||
expect(screen.getByLabelText("demo outcome proof")).toBeInTheDocument();
|
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "resume");
|
||||||
expect(screen.getByLabelText("workflow.runs.resume operation")).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows the continuity rail across Scene 9 operation, graph, and interrupt beats", () => {
|
it("shows the continuity rail across Scene 9 operation, graph, and interrupt beats", () => {
|
||||||
@@ -176,24 +171,23 @@ describe("DemoWorkflowScene", () => {
|
|||||||
interrupt.unmount();
|
interrupt.unmount();
|
||||||
|
|
||||||
renderBeat("approval", "interrupt-evidence");
|
renderBeat("approval", "interrupt-evidence");
|
||||||
expect(screen.getByLabelText("workflow graph")).toHaveAttribute("data-graph-variant", "compact");
|
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "approval");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps full graph mode for graph and output beats", () => {
|
it("keeps full graph mode for graph beats and guided product moment for output", () => {
|
||||||
const graph = renderBeat("graph");
|
const graph = renderBeat("graph");
|
||||||
expect(screen.getByLabelText("workflow graph")).toHaveAttribute("data-graph-variant", "full");
|
expect(screen.getByLabelText("workflow graph")).toHaveAttribute("data-graph-variant", "full");
|
||||||
graph.unmount();
|
graph.unmount();
|
||||||
|
|
||||||
renderBeat("output", "interrupt-evidence");
|
renderBeat("output", "interrupt-evidence");
|
||||||
expect(screen.getByLabelText("workflow graph")).toHaveAttribute("data-graph-variant", "full");
|
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "output");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps approval beat contract, compact graph, and outcome proof present", () => {
|
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.getByLabelText("typed interrupt contract")).toHaveAttribute("data-hero", "true");
|
||||||
expect(screen.getByLabelText("workflow graph")).toHaveAttribute("data-graph-variant", "compact");
|
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "approval");
|
||||||
expect(screen.queryByLabelText("demo outcome proof")).not.toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("wires approval actions into the Scene 10 schema approval surface", () => {
|
it("wires approval actions into the Scene 10 schema approval surface", () => {
|
||||||
@@ -232,16 +226,16 @@ describe("DemoWorkflowScene", () => {
|
|||||||
expect(screen.queryByText("Resume schema")).not.toBeInTheDocument();
|
expect(screen.queryByText("Resume schema")).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("adds outcome proof to resume, output, and trace beats", () => {
|
it("adds guided product moment for resume, output, and trace beats", () => {
|
||||||
const resume = renderBeat("resume", "interrupt-evidence");
|
const resume = renderBeat("resume", "interrupt-evidence");
|
||||||
expect(screen.getByLabelText("demo outcome proof")).toHaveTextContent("Same persisted run");
|
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "resume");
|
||||||
resume.unmount();
|
resume.unmount();
|
||||||
|
|
||||||
const output = renderBeat("output", "interrupt-evidence");
|
const output = renderBeat("output", "interrupt-evidence");
|
||||||
expect(screen.getByLabelText("demo outcome proof")).toHaveTextContent("Report markdown");
|
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "output");
|
||||||
output.unmount();
|
output.unmount();
|
||||||
|
|
||||||
renderBeat("trace", "interrupt-evidence");
|
renderBeat("trace", "interrupt-evidence");
|
||||||
expect(screen.getByLabelText("demo outcome proof")).toHaveTextContent("Trace frames");
|
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "trace");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
import type { DemoApprovalActions } from "./demo-approval-actions.js";
|
import type { DemoApprovalActions } from "./demo-approval-actions.js";
|
||||||
import { DemoContinuityRail } from "./DemoContinuityRail.js";
|
import { DemoContinuityRail } from "./DemoContinuityRail.js";
|
||||||
import { DemoOutcomePanel } from "./DemoOutcomePanel.js";
|
import { DemoOutcomePanel } from "./DemoOutcomePanel.js";
|
||||||
|
import { GuidedProductMoment } from "./GuidedProductMoment.js";
|
||||||
import { InterruptContractPreview } from "./InterruptContractPreview.js";
|
import { InterruptContractPreview } from "./InterruptContractPreview.js";
|
||||||
import { NodeSpotlight } from "./NodeSpotlight.js";
|
import { NodeSpotlight } from "./NodeSpotlight.js";
|
||||||
import { OperationBlock } from "./OperationBlock.js";
|
import { OperationBlock } from "./OperationBlock.js";
|
||||||
@@ -87,6 +88,8 @@ export const DemoWorkflowScene = ({
|
|||||||
? "preview"
|
? "preview"
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
const isGuidedScene10 = scene.id === "interrupt-evidence";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StageCaption eyebrow="Live system walkthrough" title={scene.title}>
|
<StageCaption eyebrow="Live system walkthrough" title={scene.title}>
|
||||||
@@ -96,55 +99,68 @@ export const DemoWorkflowScene = ({
|
|||||||
<DemoContinuityRail lens={lens} />
|
<DemoContinuityRail lens={lens} />
|
||||||
|
|
||||||
<div className="demo-workflow-stage" data-beat={beat.id} data-demo-layout={layout} aria-label="demo workflow stage">
|
<div className="demo-workflow-stage" data-beat={beat.id} data-demo-layout={layout} aria-label="demo workflow stage">
|
||||||
{showExpandedOperation && currentEvent && (
|
{isGuidedScene10 ? (
|
||||||
<OperationBlock
|
<GuidedProductMoment
|
||||||
event={currentEvent}
|
beat={beat}
|
||||||
variant="expanded"
|
demo={demo}
|
||||||
|
contract={contract}
|
||||||
|
operation={currentOperation}
|
||||||
|
approvalActions={approvalActions}
|
||||||
openEvidence={openEvidence}
|
openEvidence={openEvidence}
|
||||||
/>
|
/>
|
||||||
)}
|
) : (
|
||||||
|
<>
|
||||||
{showReceipt && runStart && (
|
{showExpandedOperation && currentEvent && (
|
||||||
<OperationBlock
|
<OperationBlock
|
||||||
event={runStart}
|
event={currentEvent}
|
||||||
variant="receipt"
|
variant="expanded"
|
||||||
openEvidence={openEvidence}
|
openEvidence={openEvidence}
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{showGraph && (
|
|
||||||
<div className="demo-workflow-stage__graph">
|
|
||||||
<WorkflowGraphStage
|
|
||||||
execution={execution}
|
|
||||||
selectedNodeId={selectedNodeId}
|
|
||||||
selectNode={selectNode}
|
|
||||||
proof={runProof}
|
|
||||||
variant={graphVariant}
|
|
||||||
/>
|
|
||||||
{contractMode && contract && (
|
|
||||||
<InterruptContractPreview
|
|
||||||
contract={contract}
|
|
||||||
mode={contractMode}
|
|
||||||
hero={layout === "approval"}
|
|
||||||
approvalActions={approvalActions}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{showOutcomePanel && (
|
{showReceipt && runStart && (
|
||||||
<DemoOutcomePanel
|
<OperationBlock
|
||||||
beatId={beat.id}
|
event={runStart}
|
||||||
lens={lens}
|
variant="receipt"
|
||||||
operation={currentOperation}
|
openEvidence={openEvidence}
|
||||||
contract={contract}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
{showExpandedOperation && !currentEvent && (
|
{showGraph && (
|
||||||
<div className="demo-workflow-stage__pending" role="status">
|
<div className="demo-workflow-stage__graph">
|
||||||
Replay operation is not available yet.
|
<WorkflowGraphStage
|
||||||
</div>
|
execution={execution}
|
||||||
|
selectedNodeId={selectedNodeId}
|
||||||
|
selectNode={selectNode}
|
||||||
|
proof={runProof}
|
||||||
|
variant={graphVariant}
|
||||||
|
/>
|
||||||
|
{contractMode && contract && (
|
||||||
|
<InterruptContractPreview
|
||||||
|
contract={contract}
|
||||||
|
mode={contractMode}
|
||||||
|
hero={layout === "approval"}
|
||||||
|
approvalActions={approvalActions}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showOutcomePanel && (
|
||||||
|
<DemoOutcomePanel
|
||||||
|
beatId={beat.id}
|
||||||
|
lens={lens}
|
||||||
|
operation={currentOperation}
|
||||||
|
contract={contract}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showExpandedOperation && !currentEvent && (
|
||||||
|
<div className="demo-workflow-stage__pending" role="status">
|
||||||
|
Replay operation is not available yet.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import { cleanup, render, screen } from "@testing-library/react";
|
||||||
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { loadCanonicalDemoRecording } from "../demo/timeline/replay.js";
|
||||||
|
import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
|
||||||
|
import { projectInterruptContract, projectOperationPresentation } from "./demo-workflow-model.js";
|
||||||
|
import { GuidedProductMoment } from "./GuidedProductMoment.js";
|
||||||
|
import { findBeat } from "./storyboard.js";
|
||||||
|
|
||||||
|
afterEach(() => cleanup());
|
||||||
|
|
||||||
|
const recording = loadCanonicalDemoRecording();
|
||||||
|
const runStart = recording.events.find((event) => event.stage === "run_start")!;
|
||||||
|
const runResume = recording.events.find((event) => event.stage === "run_resume")!;
|
||||||
|
const contract = projectInterruptContract(runStart, runResume);
|
||||||
|
const resumeOperation = projectOperationPresentation(runResume);
|
||||||
|
const demo = {
|
||||||
|
state: { mode: "replay", phase: "review", events: recording.events, appliedCount: 3, autoplay: false, error: null },
|
||||||
|
inFlight: false,
|
||||||
|
interruptPayload: null,
|
||||||
|
output: null,
|
||||||
|
trace: null,
|
||||||
|
missingDeploymentMessage: null,
|
||||||
|
recordingId: null,
|
||||||
|
canStart: true,
|
||||||
|
setMode: vi.fn(),
|
||||||
|
start: vi.fn(),
|
||||||
|
pause: vi.fn(),
|
||||||
|
play: vi.fn(),
|
||||||
|
next: vi.fn(),
|
||||||
|
submitSelectedIssues: vi.fn(),
|
||||||
|
cancelReview: vi.fn(),
|
||||||
|
restart: vi.fn(),
|
||||||
|
primeReplayToStage: vi.fn(),
|
||||||
|
} as unknown as DemoTimelineController;
|
||||||
|
|
||||||
|
describe("GuidedProductMoment", () => {
|
||||||
|
it("makes approval the primary product decision", () => {
|
||||||
|
render(
|
||||||
|
<GuidedProductMoment
|
||||||
|
beat={findBeat("interrupt-evidence", "approval")!}
|
||||||
|
demo={demo}
|
||||||
|
contract={contract}
|
||||||
|
operation={null}
|
||||||
|
approvalActions={{
|
||||||
|
state: "ready",
|
||||||
|
canSubmit: true,
|
||||||
|
canCancel: true,
|
||||||
|
submit: vi.fn(async () => {}),
|
||||||
|
cancel: vi.fn(async () => {}),
|
||||||
|
}}
|
||||||
|
openEvidence={vi.fn()}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("makes resume operation proof primary on resume beat", () => {
|
||||||
|
render(
|
||||||
|
<GuidedProductMoment
|
||||||
|
beat={findBeat("interrupt-evidence", "resume")!}
|
||||||
|
demo={demo}
|
||||||
|
contract={contract}
|
||||||
|
operation={resumeOperation}
|
||||||
|
openEvidence={vi.fn()}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveAttribute("data-moment", "resume");
|
||||||
|
expect(screen.getByLabelText("workflow.runs.resume operation")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("marks the primary surface for visual hierarchy", () => {
|
||||||
|
render(
|
||||||
|
<GuidedProductMoment
|
||||||
|
beat={findBeat("interrupt-evidence", "approval")!}
|
||||||
|
demo={demo}
|
||||||
|
contract={contract}
|
||||||
|
operation={null}
|
||||||
|
approvalActions={{
|
||||||
|
state: "ready",
|
||||||
|
canSubmit: true,
|
||||||
|
canCancel: true,
|
||||||
|
submit: vi.fn(async () => {}),
|
||||||
|
cancel: vi.fn(async () => {}),
|
||||||
|
}}
|
||||||
|
openEvidence={vi.fn()}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveClass("guided-product-moment");
|
||||||
|
expect(screen.getByLabelText("typed interrupt contract")).toHaveAttribute("data-hero", "true");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
|
||||||
|
import type { DemoApprovalActions } from "./demo-approval-actions.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";
|
||||||
|
|
||||||
|
export type GuidedProductMomentProps = {
|
||||||
|
readonly beat: SceneBeatDefinition;
|
||||||
|
readonly demo: DemoTimelineController;
|
||||||
|
readonly contract: InterruptContractPresentation | null;
|
||||||
|
readonly operation: OperationPresentation | null;
|
||||||
|
readonly approvalActions?: DemoApprovalActions | undefined;
|
||||||
|
readonly openEvidence: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const momentForBeat = (beatId: string): "approval" | "resume" | "output" | "trace" =>
|
||||||
|
beatId === "resume" || beatId === "output" || beatId === "trace" ? beatId : "approval";
|
||||||
|
|
||||||
|
const statusCopy = (
|
||||||
|
moment: ReturnType<typeof momentForBeat>,
|
||||||
|
approvalActions?: DemoApprovalActions,
|
||||||
|
): string => {
|
||||||
|
if (moment !== "approval") return "Same persisted run; inspect the proof below.";
|
||||||
|
if (approvalActions?.state === "submitted") return "Submitted. Same run resumed.";
|
||||||
|
if (approvalActions?.state === "cancelled") {
|
||||||
|
return "Cancelled in presentation replay. No resume evidence is shown.";
|
||||||
|
}
|
||||||
|
return "Run is paused. Submit resumes this same run.";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GuidedProductMoment = ({
|
||||||
|
beat,
|
||||||
|
demo,
|
||||||
|
contract,
|
||||||
|
operation,
|
||||||
|
approvalActions,
|
||||||
|
openEvidence,
|
||||||
|
}: GuidedProductMomentProps) => {
|
||||||
|
const moment = momentForBeat(beat.id);
|
||||||
|
const lens = demoBeatLensForBeat(beat.id);
|
||||||
|
const runResume = demo.state.events.find((event) => event.stage === "run_resume");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="guided-product-moment" aria-label="current product moment" data-moment={moment}>
|
||||||
|
<header className="guided-product-moment__header">
|
||||||
|
<span>{lens.eyebrow}</span>
|
||||||
|
<strong>{lens.headline}</strong>
|
||||||
|
<p>{statusCopy(moment, approvalActions)}</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="guided-product-moment__primary">
|
||||||
|
{moment === "approval" && contract ? (
|
||||||
|
<InterruptContractPreview
|
||||||
|
contract={contract}
|
||||||
|
mode="approval"
|
||||||
|
hero
|
||||||
|
approvalActions={approvalActions}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{moment === "resume" && runResume ? (
|
||||||
|
<OperationBlock
|
||||||
|
event={runResume}
|
||||||
|
variant="expanded"
|
||||||
|
openEvidence={openEvidence}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{(moment === "output" || moment === "trace") ? (
|
||||||
|
<DemoOutcomePanel
|
||||||
|
beatId={beat.id}
|
||||||
|
lens={lens}
|
||||||
|
operation={operation}
|
||||||
|
contract={contract}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user