From 9d6e62e949db97e89cfbe5765cdae9cc5e883785 Mon Sep 17 00:00:00 2001
From: Nishant Modak
Date: Sun, 6 Sep 2026 17:52:50 -0700
Subject: [PATCH 3/3] fix(desktop): preserve evidence navigation and retry
failures
---
desktop/src/App.test.tsx | 106 +++++++++++++++++++++++++++++++++++++--
desktop/src/App.tsx | 40 ++++++++++-----
2 files changed, 130 insertions(+), 16 deletions(-)
diff --git a/desktop/src/App.test.tsx b/desktop/src/App.test.tsx
index 9c215e0..e13354f 100644
--- a/desktop/src/App.test.tsx
+++ b/desktop/src/App.test.tsx
@@ -378,9 +378,7 @@ it("retry re-fetches a one-shot referenced trace after a transient failure", asy
const original = vi.mocked(api.read).getMockImplementation()!;
let referencedAttempts = 0;
vi.mocked(api.read).mockImplementation(async (path) => {
- if (path.startsWith("traces?limit=200"))
- return { traces: [trace], total: 1, next_offset: null };
- if (path.startsWith("traces?q=")) {
+ if (path === `traces?limit=200&q=${trace.trace_id}`) {
referencedAttempts += 1;
if (referencedAttempts === 1) throw new Error("receiver unreachable");
return { traces: [trace], total: 1, next_offset: null };
@@ -396,6 +394,8 @@ it("retry re-fetches a one-shot referenced trace after a transient failure", asy
"receiver unreachable",
);
await user.click(screen.getByRole("button", { name: "Retry" }));
+ await screen.findByLabelText("Span details");
+ expect(referencedAttempts).toBe(2);
await waitFor(() =>
expect(screen.queryByRole("alert")).not.toBeInTheDocument(),
);
@@ -473,3 +473,103 @@ it("shows automatic findings above the waterfall and links their evidence", asyn
screen.getByText(""),
).toBeInTheDocument();
});
+
+it("reopening the same evidence after selecting another trace returns to its trace", async () => {
+ const user = userEvent.setup();
+ const original = vi.mocked(api.read).getMockImplementation()!;
+ const other = {
+ ...trace,
+ trace_id: "33333333333333333333333333333333",
+ name: "Other request",
+ };
+ vi.mocked(api.read).mockImplementation(async (path) => {
+ if (path.startsWith("traces?limit=200&q="))
+ return { traces: [trace, other], total: 2, next_offset: null };
+ return original(path);
+ });
+ render();
+ await screen.findByText("· 1 expectation failed");
+ const openEvidence = async () => {
+ await user.click(screen.getByRole("button", { name: "Run details" }));
+ await user.click(screen.getByText("One batch query"));
+ await user.click(screen.getByRole("button", { name: "22222222 ↗" }));
+ };
+ await openEvidence();
+ await screen.findByLabelText("Span details");
+ await user.click(screen.getByRole("button", { name: /Other request/ }));
+ expect(screen.getByRole("button", { name: /Other request/ })).toHaveAttribute(
+ "aria-pressed",
+ "true",
+ );
+ await openEvidence();
+ await waitFor(() =>
+ expect(
+ screen.getByRole("button", { name: /Other request/ }),
+ ).toHaveAttribute("aria-pressed", "false"),
+ );
+});
+
+it("missing evidence trace reports a visible error and can be retried", async () => {
+ const user = userEvent.setup();
+ const original = vi.mocked(api.read).getMockImplementation()!;
+ let available = false;
+ vi.mocked(api.read).mockImplementation(async (path) => {
+ if (path === `traces?limit=200&q=${trace.trace_id}`)
+ return {
+ traces: available ? [trace] : [],
+ total: available ? 1 : 0,
+ next_offset: null,
+ };
+ return original(path);
+ });
+ render();
+ await screen.findByText("· 1 expectation failed");
+ await user.click(screen.getByRole("button", { name: "Run details" }));
+ await user.click(screen.getByText("One batch query"));
+ await user.click(screen.getByRole("button", { name: "22222222 ↗" }));
+ expect(await screen.findByRole("alert")).toHaveTextContent(
+ "Referenced trace was not found.",
+ );
+ available = true;
+ await user.click(screen.getByRole("button", { name: "Retry" }));
+ await screen.findByLabelText("Span details");
+ expect(screen.queryByRole("alert")).not.toBeInTheDocument();
+});
+
+it("ignores a pending evidence lookup after selecting another trace", async () => {
+ const user = userEvent.setup();
+ const original = vi.mocked(api.read).getMockImplementation()!;
+ const other = {
+ ...trace,
+ trace_id: "33333333333333333333333333333333",
+ name: "Other request",
+ };
+ let resolveLookup!: (page: {
+ traces: (typeof trace)[];
+ total: number;
+ next_offset: null;
+ }) => void;
+ vi.mocked(api.read).mockImplementation(async (path) => {
+ if (path === `traces?limit=200&q=${trace.trace_id}`)
+ return new Promise((resolve) => {
+ resolveLookup = resolve;
+ });
+ if (path.startsWith("traces?"))
+ return { traces: [trace, other], total: 2, next_offset: null };
+ return original(path);
+ });
+ render();
+ await screen.findByText("· 1 expectation failed");
+ await user.click(screen.getByRole("button", { name: "Run details" }));
+ await user.click(screen.getByText("One batch query"));
+ await user.click(screen.getByRole("button", { name: "22222222 ↗" }));
+ await user.click(screen.getByRole("button", { name: /Other request/ }));
+ await act(async () => {
+ resolveLookup({ traces: [trace], total: 1, next_offset: null });
+ });
+ expect(screen.getByRole("button", { name: /Other request/ })).toHaveAttribute(
+ "aria-pressed",
+ "true",
+ );
+ expect(screen.queryByLabelText("Span details")).not.toBeInTheDocument();
+});
diff --git a/desktop/src/App.tsx b/desktop/src/App.tsx
index 250fd2e..36863f2 100644
--- a/desktop/src/App.tsx
+++ b/desktop/src/App.tsx
@@ -41,20 +41,32 @@ export default function App() {
const [requestedSpan, setRequestedSpan] = useState(null);
const [showConnection, setShowConnection] = useState(false);
const [showRun, setShowRun] = useState(false);
- const [evidenceRef, setEvidenceRef] = useState(null);
- const referenced = useLive(
- evidenceRef,
- () => api.read(`traces?q=${evidenceRef}`),
+ const [evidenceRequest, setEvidenceRequest] = useState<{
+ runId: string;
+ traceId: string;
+ spanId: string;
+ } | null>(null);
+ const referenced = useLive(
+ evidenceRequest ? JSON.stringify(evidenceRequest) : null,
+ async () => {
+ const request = evidenceRequest!;
+ const page = await api.read(
+ `traces?limit=200&q=${request.traceId}`,
+ );
+ const target = page.traces.find(
+ (t) => t.run_id === request.runId && t.trace_id === request.traceId,
+ );
+ if (!target) throw new Error("Referenced trace was not found.");
+ return target;
+ },
0,
);
useEffect(() => {
- if (!referenced.data || !evidenceRef) return;
- const target = referenced.data.traces.find(
- (t) => t.run_id === runId && t.trace_id === evidenceRef,
- );
- if (!target) return;
- setSelection(target);
- }, [referenced.data, evidenceRef, runId]);
+ if (!referenced.data || !evidenceRequest) return;
+ setSelection(referenced.data);
+ setRequestedSpan(evidenceRequest.spanId);
+ setEvidenceRequest(null);
+ }, [referenced.data, evidenceRequest]);
const error =
connection.error ||
traces.error ||
@@ -67,8 +79,9 @@ export default function App() {
report.data?.verification.filter((v) => v.status === "unknown").length ?? 0;
function inspectEvidence(reference: string) {
const [trace, span] = reference.split("/");
- setEvidenceRef(trace);
- setRequestedSpan(span);
+ if (!runId) return;
+ setEvidenceRequest({ runId, traceId: trace, spanId: span });
+ referenced.refresh();
setShowRun(false);
}
return (
@@ -221,6 +234,7 @@ export default function App() {
onClick={() => {
setSelection(t);
setRequestedSpan(null);
+ setEvidenceRequest(null);
}}
>