feat: add demo outcome proof panel
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
demoBeatLensForBeat,
|
||||
type InterruptContractPresentation,
|
||||
type OperationPresentation,
|
||||
} from "./demo-workflow-model.js";
|
||||
import { DemoOutcomePanel } from "./DemoOutcomePanel.js";
|
||||
|
||||
afterEach(() => cleanup());
|
||||
|
||||
const operation: OperationPresentation = {
|
||||
operation: "workflow.runs.resume",
|
||||
status: "completed",
|
||||
durationMs: 42,
|
||||
command: "uv run wf run resume run_recorded_lda_report",
|
||||
deploymentId: "lda_report_workflow.default",
|
||||
runId: "run_recorded_lda_report",
|
||||
interruptKind: null,
|
||||
};
|
||||
|
||||
const contract: InterruptContractPresentation = {
|
||||
kind: "issue_review",
|
||||
outcomes: ["submitted", "cancelled"],
|
||||
resumeSchema: { type: "object" },
|
||||
runId: "run_recorded_lda_report",
|
||||
};
|
||||
|
||||
describe("DemoOutcomePanel", () => {
|
||||
it("explains the approval beat as a schema-backed decision", () => {
|
||||
render(
|
||||
<DemoOutcomePanel
|
||||
beatId="approval"
|
||||
lens={demoBeatLensForBeat("approval")}
|
||||
operation={null}
|
||||
contract={contract}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByLabelText("demo outcome proof")).toHaveTextContent("schema-backed");
|
||||
expect(screen.getByText("submitted / cancelled")).toBeInTheDocument();
|
||||
expect(screen.getByText("run_recorded_lda_report")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("explains resume as same-run continuation", () => {
|
||||
render(
|
||||
<DemoOutcomePanel
|
||||
beatId="resume"
|
||||
lens={demoBeatLensForBeat("resume")}
|
||||
operation={operation}
|
||||
contract={contract}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Same persisted run")).toBeInTheDocument();
|
||||
expect(screen.getByText("workflow.runs.resume")).toBeInTheDocument();
|
||||
expect(screen.getByText("completed")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("explains output and trace without pretending to run live services", () => {
|
||||
const { rerender } = render(
|
||||
<DemoOutcomePanel
|
||||
beatId="output"
|
||||
lens={demoBeatLensForBeat("output")}
|
||||
operation={null}
|
||||
contract={contract}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("Report markdown")).toBeInTheDocument();
|
||||
expect(screen.getByText("Issue board changes")).toBeInTheDocument();
|
||||
|
||||
rerender(
|
||||
<DemoOutcomePanel
|
||||
beatId="trace"
|
||||
lens={demoBeatLensForBeat("trace")}
|
||||
operation={operation}
|
||||
contract={contract}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("Trace frames")).toBeInTheDocument();
|
||||
expect(screen.getByText("Protocol evidence")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
import type {
|
||||
DemoBeatLens,
|
||||
InterruptContractPresentation,
|
||||
OperationPresentation,
|
||||
} from "./demo-workflow-model.js";
|
||||
|
||||
type DemoOutcomePanelProps = {
|
||||
readonly beatId: string;
|
||||
readonly lens: DemoBeatLens;
|
||||
readonly operation: OperationPresentation | null;
|
||||
readonly contract: InterruptContractPresentation | null;
|
||||
};
|
||||
|
||||
const outcomesText = (contract: InterruptContractPresentation | null): string =>
|
||||
contract?.outcomes.join(" / ") ?? "submitted / cancelled";
|
||||
|
||||
export const DemoOutcomePanel = ({
|
||||
beatId,
|
||||
lens,
|
||||
operation,
|
||||
contract,
|
||||
}: DemoOutcomePanelProps) => {
|
||||
const runId = operation?.runId ?? contract?.runId ?? "run unavailable";
|
||||
|
||||
if (beatId === "approval") {
|
||||
return (
|
||||
<aside className="demo-outcome-panel" aria-label="demo outcome proof">
|
||||
<span>{lens.eyebrow}</span>
|
||||
<strong>Operator sees a schema-backed request</strong>
|
||||
<dl>
|
||||
<div><dt>Run</dt><dd><code>{runId}</code></dd></div>
|
||||
<div><dt>Allowed outcomes</dt><dd>{outcomesText(contract)}</dd></div>
|
||||
</dl>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
if (beatId === "resume") {
|
||||
return (
|
||||
<aside className="demo-outcome-panel" aria-label="demo outcome proof">
|
||||
<span>{lens.eyebrow}</span>
|
||||
<strong>Same persisted run</strong>
|
||||
<dl>
|
||||
<div><dt>Operation</dt><dd><code>{operation?.operation ?? lens.proofLabel}</code></dd></div>
|
||||
<div><dt>Status</dt><dd>{operation?.status ?? "completed"}</dd></div>
|
||||
</dl>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
if (beatId === "output") {
|
||||
return (
|
||||
<aside className="demo-outcome-panel demo-outcome-panel--split" aria-label="demo outcome proof">
|
||||
<span>{lens.eyebrow}</span>
|
||||
<strong>Product state, not chat-only text</strong>
|
||||
<ul>
|
||||
<li><b>Report markdown</b><small>Generated from selected documents.</small></li>
|
||||
<li><b>Issue board changes</b><small>Created only after approval.</small></li>
|
||||
</ul>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className="demo-outcome-panel demo-outcome-panel--split" aria-label="demo outcome proof">
|
||||
<span>{lens.eyebrow}</span>
|
||||
<strong>Auditable after the moment passes</strong>
|
||||
<ul>
|
||||
<li><b>Trace frames</b><small>Node-level execution history.</small></li>
|
||||
<li><b>Protocol evidence</b><small>Raw and interpreted JSON-RPC records.</small></li>
|
||||
</ul>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
@@ -736,6 +736,74 @@
|
||||
font: 600 0.6rem/1.1 var(--font-mono, monospace);
|
||||
}
|
||||
|
||||
.demo-outcome-panel {
|
||||
display: grid;
|
||||
gap: 0.65rem;
|
||||
align-content: start;
|
||||
border: 1px solid oklch(0.76 0.18 70 / 0.38);
|
||||
border-radius: 0.85rem;
|
||||
padding: 0.85rem;
|
||||
background:
|
||||
linear-gradient(145deg, oklch(0.76 0.18 70 / 0.08), transparent 55%),
|
||||
oklch(0.13 0.025 250 / 0.9);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.demo-outcome-panel > span {
|
||||
color: var(--accent-amber);
|
||||
font: 700 0.62rem/1 var(--font-mono, monospace);
|
||||
letter-spacing: 0.11em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.demo-outcome-panel > strong {
|
||||
font-size: 1rem;
|
||||
line-height: 1.18;
|
||||
}
|
||||
|
||||
.demo-outcome-panel dl {
|
||||
display: grid;
|
||||
gap: 0.45rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.demo-outcome-panel dt {
|
||||
color: var(--text-muted);
|
||||
font: 700 0.58rem/1 var(--font-mono, monospace);
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.demo-outcome-panel dd {
|
||||
margin: 0.2rem 0 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.demo-outcome-panel ul {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.demo-outcome-panel li {
|
||||
border: 1px solid var(--stage-line);
|
||||
border-radius: 0.65rem;
|
||||
padding: 0.6rem;
|
||||
background: oklch(0.11 0.022 250 / 0.72);
|
||||
}
|
||||
|
||||
.demo-outcome-panel b {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.demo-outcome-panel small {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
@container presentation-canvas (max-width: 1050px) {
|
||||
.demo-continuity-rail {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
|
||||
Reference in New Issue
Block a user