fix: separate Scene 11 interrupt context from approval
This commit is contained in:
@@ -93,9 +93,12 @@ describe("DemoWorkflowScene", () => {
|
||||
renderBeat("interrupt", "typed-human-boundary");
|
||||
|
||||
const moment = screen.getByRole("region", { name: /current product moment/i });
|
||||
expect(moment).toHaveAttribute("data-moment", "approval");
|
||||
expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
|
||||
expect(moment).toHaveAttribute("data-moment", "interrupt");
|
||||
expect(screen.getByText("Workflow input")).toBeInTheDocument();
|
||||
expect(screen.getByText("Interrupt payload")).toBeInTheDocument();
|
||||
expect(screen.queryByRole("group", { name: /operator resume decision/i })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Submit" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Request revision" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("carries the contract into Scene 10 approval via guided product moment", () => {
|
||||
@@ -164,7 +167,9 @@ describe("DemoWorkflowScene", () => {
|
||||
it("renders approval and interrupt beats via guided product moment", () => {
|
||||
const interrupt = renderBeat("interrupt", "typed-human-boundary");
|
||||
expect(screen.queryByLabelText("workflow graph")).not.toBeInTheDocument();
|
||||
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", "interrupt");
|
||||
expect(screen.queryByRole("button", { name: "Submit" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Request revision" })).not.toBeInTheDocument();
|
||||
interrupt.unmount();
|
||||
|
||||
renderBeat("approval", "typed-human-boundary");
|
||||
@@ -209,10 +214,13 @@ describe("DemoWorkflowScene", () => {
|
||||
expect(screen.getByText("Workflow input")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders interrupt decision form but no raw schema in guided product moment", () => {
|
||||
it("renders interrupt facts but no decision controls in guided product moment", () => {
|
||||
renderBeat("interrupt", "typed-human-boundary");
|
||||
expect(screen.queryByText("Resume schema")).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
|
||||
expect(screen.getByText("Interrupt payload")).toBeInTheDocument();
|
||||
expect(screen.queryByRole("group", { name: /operator resume decision/i })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Submit" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Request revision" })).not.toBeInTheDocument();
|
||||
cleanup();
|
||||
|
||||
renderBeat("approval", "typed-human-boundary");
|
||||
|
||||
@@ -53,6 +53,26 @@ const demoWithAppliedCount = (appliedCount: number): DemoTimelineController => (
|
||||
});
|
||||
|
||||
describe("GuidedProductMoment", () => {
|
||||
it("renders interrupt context without decision controls", () => {
|
||||
render(
|
||||
<GuidedProductMoment
|
||||
beat={findBeat("typed-human-boundary", "interrupt")!}
|
||||
demo={demo}
|
||||
contract={contract}
|
||||
operation={null}
|
||||
openEvidence={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const moment = screen.getByRole("region", { name: /current product moment/i });
|
||||
expect(moment).toHaveAttribute("data-moment", "interrupt");
|
||||
expect(screen.getByText("Workflow input")).toBeInTheDocument();
|
||||
expect(screen.getByText("Interrupt payload")).toBeInTheDocument();
|
||||
expect(screen.queryByRole("group", { name: /operator resume decision/i })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Submit" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Request revision" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("makes approval the primary product decision with factual panels", () => {
|
||||
render(
|
||||
<GuidedProductMoment
|
||||
|
||||
@@ -26,13 +26,20 @@ export type GuidedProductMomentProps = {
|
||||
readonly openEvidence: () => void;
|
||||
};
|
||||
|
||||
const momentForBeat = (beatId: string): "approval" | "resume" | "output" | "trace" =>
|
||||
beatId === "resume" || beatId === "output" || beatId === "trace" ? beatId : "approval";
|
||||
type GuidedProductMoment = "interrupt" | "approval" | "resume" | "output" | "trace";
|
||||
|
||||
const momentForBeat = (beatId: string): GuidedProductMoment => {
|
||||
if (beatId === "interrupt" || beatId === "approval" || beatId === "resume" || beatId === "output" || beatId === "trace") {
|
||||
return beatId;
|
||||
}
|
||||
return "approval";
|
||||
};
|
||||
|
||||
const statusCopy = (
|
||||
moment: ReturnType<typeof momentForBeat>,
|
||||
approvalActions?: DemoApprovalActions,
|
||||
): string => {
|
||||
if (moment === "interrupt") return "Run paused at the typed interrupt; inspect the context before deciding.";
|
||||
if (moment !== "approval") return "Same persisted run; inspect the proof below.";
|
||||
if (approvalActions?.state === "submitted") return "Submitted. Same run resumed.";
|
||||
if (approvalActions?.state === "revision_requested") {
|
||||
@@ -43,6 +50,8 @@ const statusCopy = (
|
||||
|
||||
const hierarchyForMoment = (moment: ReturnType<typeof momentForBeat>) => {
|
||||
switch (moment) {
|
||||
case "interrupt":
|
||||
return { primary: "interrupt-context", support: "input-facts" };
|
||||
case "approval":
|
||||
return { primary: "interrupt-approval", support: "input-facts" };
|
||||
case "resume":
|
||||
@@ -88,6 +97,12 @@ export const GuidedProductMoment = ({
|
||||
</header>
|
||||
|
||||
<div className="guided-product-moment__primary">
|
||||
{moment === "interrupt" ? (
|
||||
<div className="guided-product-moment__interrupt-grid">
|
||||
<RunInputFacts facts={facts} density="compact" />
|
||||
<InterruptPayloadFacts facts={facts} priority="primary" />
|
||||
</div>
|
||||
) : null}
|
||||
{moment === "approval" && contract ? (
|
||||
<div className="guided-product-moment__approval-grid">
|
||||
<aside className="guided-product-moment__input-rail" aria-label="workflow input context">
|
||||
|
||||
@@ -1244,6 +1244,7 @@
|
||||
}
|
||||
|
||||
.guided-product-moment[data-moment="approval"],
|
||||
.guided-product-moment[data-moment="interrupt"],
|
||||
.guided-product-moment[data-moment="resume"],
|
||||
.guided-product-moment[data-moment="output"],
|
||||
.guided-product-moment[data-moment="trace"] {
|
||||
@@ -1260,6 +1261,10 @@
|
||||
gap: 0.55rem;
|
||||
}
|
||||
|
||||
.guided-product-moment[data-moment="interrupt"] {
|
||||
gap: 0.55rem;
|
||||
}
|
||||
|
||||
.guided-product-moment[data-moment="approval"] .guided-product-moment__header {
|
||||
grid-template-columns: minmax(0, 0.42fr) minmax(0, 1fr);
|
||||
padding: 0.55rem 0.75rem;
|
||||
@@ -1291,6 +1296,17 @@
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.guided-product-moment__interrupt-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(14rem, 0.55fr) minmax(0, 1.45fr);
|
||||
gap: 0.85rem;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.guided-product-moment__interrupt-grid > * {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.guided-product-moment__approval-grid > * {
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -1570,6 +1586,7 @@
|
||||
|
||||
@container presentation-canvas (max-width: 1050px) {
|
||||
.guided-product-moment__approval-grid,
|
||||
.guided-product-moment__interrupt-grid,
|
||||
.guided-product-moment__resume-grid,
|
||||
.guided-product-moment__trace-grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
|
||||
Reference in New Issue
Block a user