fix: harden presentation trace evidence
This commit is contained in:
+2
@@ -1,5 +1,7 @@
|
|||||||
# Presentation Evidence Handoff Hardening Plan
|
# Presentation Evidence Handoff Hardening Plan
|
||||||
|
|
||||||
|
Status: Completed on 2026-07-11.
|
||||||
|
|
||||||
> **For agentic workers:** execute this plan task-by-task, keep the canonical
|
> **For agentic workers:** execute this plan task-by-task, keep the canonical
|
||||||
> replay as the source of truth, and run the review skill before completion.
|
> replay as the source of truth, and run the review skill before completion.
|
||||||
|
|
||||||
@@ -332,6 +332,14 @@ describe("useDemoTimeline", () => {
|
|||||||
|
|
||||||
expect(result.current.state.phase).toBe("completed");
|
expect(result.current.state.phase).toBe("completed");
|
||||||
expect(result.current.trace?.frames).toHaveLength(1);
|
expect(result.current.trace?.frames).toHaveLength(1);
|
||||||
|
expect(mockedCallOperation.mock.calls.map(([operation]) => operation)).toEqual([
|
||||||
|
"workflow.deployments.inspect",
|
||||||
|
"workflow.runs.start",
|
||||||
|
"workflow.runs.resume",
|
||||||
|
"workflow.runs.trace",
|
||||||
|
]);
|
||||||
|
await act(async () => vi.advanceTimersByTimeAsync(1800));
|
||||||
|
expect(mockedCallOperation).toHaveBeenCalledTimes(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows a failed phase when a live operation rejects", async () => {
|
it("shows a failed phase when a live operation rejects", async () => {
|
||||||
@@ -441,6 +449,20 @@ describe("useDemoTimeline", () => {
|
|||||||
expect(result.current.state.events[result.current.state.appliedCount - 1]?.stage).toBe("run_resume");
|
expect(result.current.state.events[result.current.state.appliedCount - 1]?.stage).toBe("run_resume");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("primes replay to trace_read with all canonical trace frames", () => {
|
||||||
|
const { result } = renderHook(() => useDemoTimeline("http://127.0.0.1:8765/rpc", vi.fn()));
|
||||||
|
|
||||||
|
act(() => result.current.start("replay"));
|
||||||
|
act(() => result.current.primeReplayToStage("trace_read"));
|
||||||
|
|
||||||
|
expect(result.current.state.events[result.current.state.appliedCount - 1]?.stage).toBe("trace_read");
|
||||||
|
expect(result.current.trace?.frames.map((frame) => frame.nodeId)).toEqual([
|
||||||
|
"list_documents",
|
||||||
|
"review_issues",
|
||||||
|
"finalise_report",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it("does not prime live timelines", () => {
|
it("does not prime live timelines", () => {
|
||||||
const { result } = renderHook(() => useDemoTimeline("http://127.0.0.1:8765/rpc", vi.fn()));
|
const { result } = renderHook(() => useDemoTimeline("http://127.0.0.1:8765/rpc", vi.fn()));
|
||||||
|
|
||||||
|
|||||||
@@ -366,6 +366,22 @@ describe("PresentationRoute", () => {
|
|||||||
|
|
||||||
expect(await screen.findByRole("region", { name: /workflow trace frames/i })).toBeInTheDocument();
|
expect(await screen.findByRole("region", { name: /workflow trace frames/i })).toBeInTheDocument();
|
||||||
expect(screen.queryByText("No trace frames captured.")).not.toBeInTheDocument();
|
expect(screen.queryByText("No trace frames captured.")).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByText("list_documents")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("review_issues")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("finalise_report")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps trace frames when navigating from output to trace", async () => {
|
||||||
|
window.location.hash = "#scene/resume-output-evidence/output";
|
||||||
|
const { PresentationRoute } = await import("./PresentationRoute.js");
|
||||||
|
render(<PresentationRoute />);
|
||||||
|
|
||||||
|
expect(await screen.findByRole("region", { name: /workflow output report/i })).toBeInTheDocument();
|
||||||
|
window.location.hash = "#scene/resume-output-evidence/trace";
|
||||||
|
fireEvent(window, new Event("hashchange"));
|
||||||
|
|
||||||
|
expect(await screen.findByText("finalise_report")).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText("No trace frames captured.")).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("navigates to Scene 8 request beat via hash", async () => {
|
it("navigates to Scene 8 request beat via hash", async () => {
|
||||||
|
|||||||
@@ -232,6 +232,7 @@ describe("RunTraceFacts", () => {
|
|||||||
|
|
||||||
expect(screen.getByText("list_documents")).toBeDefined();
|
expect(screen.getByText("list_documents")).toBeDefined();
|
||||||
expect(screen.getByText("review_issues")).toBeDefined();
|
expect(screen.getByText("review_issues")).toBeDefined();
|
||||||
|
expect(screen.getByText("2 captured")).toBeInTheDocument();
|
||||||
expect(screen.getAllByText("captured as empty object").length).toBeGreaterThanOrEqual(2);
|
expect(screen.getAllByText("captured as empty object").length).toBeGreaterThanOrEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,9 @@ type RunTraceFactsProps = {
|
|||||||
|
|
||||||
export const RunTraceFacts = ({ facts }: RunTraceFactsProps) => (
|
export const RunTraceFacts = ({ facts }: RunTraceFactsProps) => (
|
||||||
<div className="run-facts-card run-trace-facts" role="region" aria-label="workflow trace proof">
|
<div className="run-facts-card run-trace-facts" role="region" aria-label="workflow trace proof">
|
||||||
<h3>Trace frames</h3>
|
<h3>
|
||||||
|
Trace frames <span className="run-facts-count">{facts.trace.frames.length} captured</span>
|
||||||
|
</h3>
|
||||||
{facts.trace.frames.length === 0 ? (
|
{facts.trace.frames.length === 0 ? (
|
||||||
<p>No trace frames captured.</p>
|
<p>No trace frames captured.</p>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -2206,6 +2206,13 @@
|
|||||||
color: var(--accent-cyan);
|
color: var(--accent-cyan);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.run-facts-count {
|
||||||
|
margin-left: 0.4rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.72em;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.run-facts-dl {
|
.run-facts-dl {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
|
|||||||
@@ -1453,4 +1453,23 @@
|
|||||||
.guided-product-moment__trace-grid {
|
.guided-product-moment__trace-grid {
|
||||||
grid-template-columns: minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.guided-product-moment[data-moment="trace"] .guided-product-moment__trace-grid {
|
||||||
|
grid-template-rows: minmax(0, 1fr) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-product-moment[data-moment="trace"] .run-facts-card[data-output-priority="summary"] {
|
||||||
|
max-height: 7.25rem;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 0.65rem 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-product-moment[data-moment="trace"] .run-facts-card[data-output-priority="summary"] h3 {
|
||||||
|
margin-bottom: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-product-moment[data-moment="trace"] .run-facts-card[data-output-priority="summary"] .run-facts-dl {
|
||||||
|
gap: 0.2rem 0.55rem;
|
||||||
|
font-size: 0.76rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user