fix: make presentation workflow graph factual
This commit is contained in:
@@ -132,7 +132,8 @@ describe("DemoWorkflowScene", () => {
|
||||
it("passes run proof into full graph beats", () => {
|
||||
const { unmount } = renderBeat("graph");
|
||||
expect(screen.getByLabelText("workflow graph proof")).toHaveTextContent("run_recorded_lda_report");
|
||||
expect(screen.getByLabelText("workflow graph proof")).toHaveTextContent("9 workflow nodes");
|
||||
expect(screen.getByLabelText("workflow graph proof")).toHaveTextContent("11 plan nodes");
|
||||
expect(screen.getByLabelText("workflow graph proof")).toHaveTextContent("3 trace frames");
|
||||
unmount();
|
||||
|
||||
renderBeat("output", "resume-output-evidence");
|
||||
|
||||
@@ -73,7 +73,8 @@ export const DemoWorkflowScene = ({
|
||||
|
||||
const runProof = {
|
||||
runId: runStart?.resultingIds.runId ?? null,
|
||||
traceLabel: "9 workflow nodes",
|
||||
planLabel: "11 plan nodes",
|
||||
traceLabel: "3 trace frames",
|
||||
evidenceLabel: "JSON-RPC evidence",
|
||||
};
|
||||
|
||||
|
||||
@@ -20,11 +20,55 @@ describe("WorkflowGraphStage", () => {
|
||||
expect(selectNode).toHaveBeenCalledWith("review_issues");
|
||||
});
|
||||
|
||||
it("renders the prepared report workflow plan nodes", () => {
|
||||
render(
|
||||
<WorkflowGraphStage
|
||||
execution={{ completedNodeIds: [], currentNodeId: "read_docs" }}
|
||||
selectedNodeId={null}
|
||||
selectNode={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const graph = screen.getByRole("group", { name: /workflow graph/i });
|
||||
expect(graph).toHaveTextContent("Read docs");
|
||||
expect(graph).toHaveTextContent("Reset board");
|
||||
expect(graph).toHaveTextContent("Analyze");
|
||||
expect(graph).toHaveTextContent("Build report");
|
||||
expect(graph).toHaveTextContent("Draft issues");
|
||||
expect(graph).toHaveTextContent("Issue review");
|
||||
expect(graph).toHaveTextContent("Create issues");
|
||||
expect(graph).toHaveTextContent("Finalise");
|
||||
expect(graph).toHaveTextContent("Revision requested");
|
||||
expect(graph).toHaveTextContent("Completed");
|
||||
expect(graph).toHaveTextContent("Cancelled");
|
||||
expect(screen.getAllByRole("button", { name: /queued|current|completed|interrupt/i })).toHaveLength(11);
|
||||
});
|
||||
|
||||
it("labels graph proof as plan nodes and trace frames separately", () => {
|
||||
render(
|
||||
<WorkflowGraphStage
|
||||
execution={{ completedNodeIds: ["read_docs"], currentNodeId: "analyze" }}
|
||||
selectedNodeId={null}
|
||||
selectNode={vi.fn()}
|
||||
proof={{
|
||||
runId: "run_recorded_lda_report",
|
||||
planLabel: "11 plan nodes",
|
||||
traceLabel: "3 trace frames",
|
||||
evidenceLabel: "JSON-RPC evidence",
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const proof = screen.getByLabelText("workflow graph proof");
|
||||
expect(proof).toHaveTextContent("11 plan nodes");
|
||||
expect(proof).toHaveTextContent("3 trace frames");
|
||||
});
|
||||
|
||||
it("distinguishes completed, current interrupt, and future nodes semantically", () => {
|
||||
render(
|
||||
<WorkflowGraphStage
|
||||
execution={{
|
||||
completedNodeIds: ["read_docs", "build_report"],
|
||||
completedNodeIds: ["read_docs", "reset_board", "analyze", "build_report", "draft_issues"],
|
||||
currentNodeId: "review_issues",
|
||||
}}
|
||||
selectedNodeId={null}
|
||||
@@ -32,65 +76,64 @@ describe("WorkflowGraphStage", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
const readDocs = screen.getByRole("button", { name: /read documents/i });
|
||||
const buildReport = screen.getByRole("button", { name: /build report/i });
|
||||
const readDocs = screen.getByRole("button", { name: /read docs/i });
|
||||
const reviewIssues = screen.getByRole("button", { name: /issue review/i });
|
||||
const createIssues = screen.getByRole("button", { name: /create issues/i });
|
||||
const revisionReq = screen.getByRole("button", { name: /revision requested/i });
|
||||
|
||||
expect(readDocs).toHaveAttribute("data-execution-state", "completed");
|
||||
expect(buildReport).toHaveAttribute("data-execution-state", "completed");
|
||||
expect(reviewIssues).toHaveAttribute("data-execution-state", "current");
|
||||
expect(reviewIssues).toHaveAttribute("data-current-interrupt", "true");
|
||||
expect(reviewIssues).toHaveTextContent("Current interrupt");
|
||||
expect(createIssues).toHaveAttribute("data-execution-state", "future");
|
||||
expect(revisionReq).toHaveAttribute("data-execution-state", "future");
|
||||
});
|
||||
|
||||
it("renders connectors between nodes", () => {
|
||||
render(
|
||||
<WorkflowGraphStage
|
||||
execution={{ completedNodeIds: ["read_docs"], currentNodeId: "build_report" }}
|
||||
execution={{ completedNodeIds: ["read_docs"], currentNodeId: "reset_board" }}
|
||||
selectedNodeId={null}
|
||||
selectNode={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const connectors = screen.getAllByTestId("workflow-connector");
|
||||
expect(connectors).toHaveLength(4);
|
||||
expect(connectors).toHaveLength(10);
|
||||
expect(connectors.filter((connector) => connector.dataset.active === "true")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("keeps all graph nodes inside the visible percentage frame", () => {
|
||||
for (const node of presentationNodes) {
|
||||
expect(node.x).toBeGreaterThanOrEqual(14);
|
||||
expect(node.x).toBeLessThanOrEqual(86);
|
||||
expect(node.y).toBeGreaterThanOrEqual(28);
|
||||
expect(node.y).toBeLessThanOrEqual(72);
|
||||
expect(node.x).toBeGreaterThanOrEqual(8);
|
||||
expect(node.x).toBeLessThanOrEqual(92);
|
||||
expect(node.y).toBeGreaterThanOrEqual(34);
|
||||
expect(node.y).toBeLessThanOrEqual(78);
|
||||
}
|
||||
});
|
||||
|
||||
it("renders compact run proof inside the graph", () => {
|
||||
render(
|
||||
<WorkflowGraphStage
|
||||
execution={{ completedNodeIds: ["read_docs"], currentNodeId: "build_report" }}
|
||||
execution={{ completedNodeIds: ["read_docs"], currentNodeId: "reset_board" }}
|
||||
selectedNodeId={null}
|
||||
selectNode={vi.fn()}
|
||||
proof={{ runId: "run_recorded_lda_report", traceLabel: "5 nodes", evidenceLabel: "JSON-RPC captured" }}
|
||||
proof={{ runId: "run_recorded_lda_report", planLabel: "11 plan nodes", traceLabel: "3 trace frames", evidenceLabel: "JSON-RPC captured" }}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByLabelText("workflow graph proof")).toHaveTextContent("run_recorded_lda_report");
|
||||
expect(screen.getByLabelText("workflow graph proof")).toHaveTextContent("5 nodes");
|
||||
expect(screen.getByLabelText("workflow graph proof")).toHaveTextContent("11 plan nodes");
|
||||
expect(screen.getByLabelText("workflow graph proof")).toHaveTextContent("3 trace frames");
|
||||
expect(screen.getByLabelText("workflow graph proof")).toHaveTextContent("JSON-RPC captured");
|
||||
});
|
||||
|
||||
it("marks compact graph mode and suppresses proof chips", () => {
|
||||
render(
|
||||
<WorkflowGraphStage
|
||||
execution={{ completedNodeIds: ["read_docs", "build_report"], currentNodeId: "review_issues" }}
|
||||
execution={{ completedNodeIds: ["read_docs", "reset_board"], currentNodeId: "analyze" }}
|
||||
selectedNodeId={null}
|
||||
selectNode={vi.fn()}
|
||||
variant="compact"
|
||||
proof={{ runId: "run_recorded_lda_report", traceLabel: "9 workflow nodes", evidenceLabel: "JSON-RPC evidence" }}
|
||||
proof={{ runId: "run_recorded_lda_report", planLabel: "11 plan nodes", traceLabel: "3 trace frames", evidenceLabel: "JSON-RPC evidence" }}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -101,7 +144,7 @@ describe("WorkflowGraphStage", () => {
|
||||
it("keeps full graph mode as the default", () => {
|
||||
render(
|
||||
<WorkflowGraphStage
|
||||
execution={{ completedNodeIds: ["read_docs"], currentNodeId: "build_report" }}
|
||||
execution={{ completedNodeIds: ["read_docs"], currentNodeId: "reset_board" }}
|
||||
selectedNodeId={null}
|
||||
selectNode={vi.fn()}
|
||||
/>,
|
||||
@@ -116,7 +159,7 @@ describe("WorkflowGraphStage", () => {
|
||||
execution={{ completedNodeIds: [], currentNodeId: null }}
|
||||
selectedNodeId={null}
|
||||
selectNode={vi.fn()}
|
||||
proof={{ runId: null, traceLabel: "trace label", evidenceLabel: "evidence label" }}
|
||||
proof={{ runId: null, planLabel: "11 plan nodes", traceLabel: "trace label", evidenceLabel: "evidence label" }}
|
||||
/>,
|
||||
);
|
||||
|
||||
|
||||
@@ -12,26 +12,39 @@ export type PresentationNode = {
|
||||
};
|
||||
|
||||
export const presentationNodes: ReadonlyArray<PresentationNode> = [
|
||||
{ id: "read_docs", label: "Read documents", detail: "5 selected", kind: "node", x: 14, y: 58 },
|
||||
{ id: "build_report", label: "Build report", detail: "Markdown", kind: "node", x: 34, y: 36 },
|
||||
{ id: "review_issues", label: "Issue review", detail: "Typed interrupt", kind: "interrupt", x: 52, y: 58 },
|
||||
{ id: "create_issues", label: "Create issues", detail: "Selected only", kind: "node", x: 70, y: 36 },
|
||||
{ id: "end_completed", label: "Completed", detail: "Persisted run", kind: "end", x: 86, y: 58 },
|
||||
{ id: "read_docs", label: "Read docs", detail: "document source", kind: "node", x: 8, y: 54 },
|
||||
{ id: "reset_board", label: "Reset board", detail: "issue board", kind: "node", x: 20, y: 34 },
|
||||
{ id: "analyze", label: "Analyze", detail: "report source", kind: "node", x: 32, y: 54 },
|
||||
{ id: "build_report", label: "Build report", detail: "markdown", kind: "node", x: 44, y: 34 },
|
||||
{ id: "draft_issues", label: "Draft issues", detail: "proposals", kind: "node", x: 56, y: 54 },
|
||||
{ id: "review_issues", label: "Issue review", detail: "typed interrupt", kind: "interrupt", x: 68, y: 34 },
|
||||
{ id: "create_issues", label: "Create issues", detail: "selected only", kind: "node", x: 80, y: 54 },
|
||||
{ id: "finalise", label: "Finalise", detail: "state output", kind: "node", x: 92, y: 34 },
|
||||
{ id: "revision_requested", label: "Revision requested", detail: "operator branch", kind: "end", x: 68, y: 78 },
|
||||
{ id: "end_completed", label: "Completed", detail: "persisted run", kind: "end", x: 92, y: 72 },
|
||||
{ id: "end_cancelled", label: "Cancelled", detail: "no submitted output", kind: "end", x: 80, y: 78 },
|
||||
];
|
||||
|
||||
type PresentationEdge = readonly [from: string, to: string];
|
||||
|
||||
const presentationEdges: ReadonlyArray<PresentationEdge> = [
|
||||
["read_docs", "build_report"],
|
||||
["build_report", "review_issues"],
|
||||
["read_docs", "reset_board"],
|
||||
["reset_board", "analyze"],
|
||||
["analyze", "build_report"],
|
||||
["build_report", "draft_issues"],
|
||||
["draft_issues", "review_issues"],
|
||||
["review_issues", "create_issues"],
|
||||
["create_issues", "end_completed"],
|
||||
["review_issues", "revision_requested"],
|
||||
["review_issues", "end_cancelled"],
|
||||
["create_issues", "finalise"],
|
||||
["finalise", "end_completed"],
|
||||
];
|
||||
|
||||
type NodeExecutionState = "completed" | "current" | "future";
|
||||
|
||||
export type WorkflowGraphProof = {
|
||||
readonly runId: string | null;
|
||||
readonly planLabel: string;
|
||||
readonly traceLabel: string;
|
||||
readonly evidenceLabel: string;
|
||||
};
|
||||
@@ -83,6 +96,7 @@ export const WorkflowGraphStage = ({
|
||||
{variant === "full" && proof && (
|
||||
<div className="workflow-graph-stage__proof" aria-label="workflow graph proof">
|
||||
<span><b>Run</b><code>{proof.runId ?? "run unavailable"}</code></span>
|
||||
<span><b>Plan</b>{proof.planLabel}</span>
|
||||
<span><b>Trace</b>{proof.traceLabel}</span>
|
||||
<span><b>Evidence</b>{proof.evidenceLabel}</span>
|
||||
</div>
|
||||
|
||||
@@ -181,22 +181,22 @@ export const graphExecutionForBeat = (
|
||||
case "operation":
|
||||
return { completedNodeIds: [], currentNodeId: "read_docs" };
|
||||
case "graph":
|
||||
return { completedNodeIds: ["read_docs"], currentNodeId: "build_report" };
|
||||
return { completedNodeIds: ["read_docs", "reset_board"], currentNodeId: "analyze" };
|
||||
case "interrupt":
|
||||
case "approval":
|
||||
return {
|
||||
completedNodeIds: ["read_docs", "build_report"],
|
||||
completedNodeIds: ["read_docs", "reset_board", "analyze", "build_report", "draft_issues"],
|
||||
currentNodeId: "review_issues",
|
||||
};
|
||||
case "resume":
|
||||
return {
|
||||
completedNodeIds: ["read_docs", "build_report", "review_issues"],
|
||||
completedNodeIds: ["read_docs", "reset_board", "analyze", "build_report", "draft_issues", "review_issues"],
|
||||
currentNodeId: "create_issues",
|
||||
};
|
||||
case "output":
|
||||
case "trace":
|
||||
return {
|
||||
completedNodeIds: ["read_docs", "build_report", "review_issues", "create_issues"],
|
||||
completedNodeIds: ["read_docs", "reset_board", "analyze", "build_report", "draft_issues", "review_issues", "create_issues", "finalise"],
|
||||
currentNodeId: "end_completed",
|
||||
};
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user