From fbf2a4cb9e4edb70353243bd9241b5557dc8437e Mon Sep 17 00:00:00 2001 From: "detail-app[bot]" <180357370+detail-app[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 10:13:34 +0000 Subject: [PATCH 1/3] fix(desktop): refresh the one-shot referenced trace on error banner retry --- desktop/src/App.test.tsx | 35 +++++++++++++++++++++++++++++++++++ desktop/src/App.tsx | 1 + 2 files changed, 36 insertions(+) diff --git a/desktop/src/App.test.tsx b/desktop/src/App.test.tsx index 0131e97..1572c3a 100644 --- a/desktop/src/App.test.tsx +++ b/desktop/src/App.test.tsx @@ -373,6 +373,41 @@ it("preserves the comparison baseline when a note refreshes the session detail", expect(screen.getByLabelText("Compare to")).toHaveValue("run-2"); }); +it("retry re-fetches a one-shot referenced trace after a transient failure", async () => { + const user = userEvent.setup(); + const original = vi.mocked(api.read).getMockImplementation()!; + const recentPage = Array.from({ length: 200 }, (_, i) => ({ + ...trace, + trace_id: `ffff${String(i + 1).padStart(28, "0")}`, + root_span_id: `ffff${String(i + 1).padStart(28, "0")}`, + })); + let referencedAttempts = 0; + vi.mocked(api.read).mockImplementation(async (path) => { + if (path.includes("/traces?limit=200")) + return { traces: recentPage, total: 250, next_offset: "200" }; + if (path.includes("/traces?q=")) { + referencedAttempts += 1; + if (referencedAttempts === 1) throw new Error("receiver unreachable"); + return { traces: [trace], total: 1, next_offset: null }; + } + return original(path); + }); + render(); + await screen.findByText("· 1 expectation failed"); + await user.selectOptions(screen.getByLabelText("Run"), "run-2"); + await waitFor(() => expect(api.read).toHaveBeenCalledWith("runs/run-2")); + 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( + "receiver unreachable", + ); + await user.click(screen.getByRole("button", { name: "Retry" })); + await waitFor(() => + expect(screen.queryByRole("alert")).not.toBeInTheDocument(), + ); +}); + it("collapses a branch without hiding its parent or losing selected evidence", async () => { const original = vi.mocked(api.read).getMockImplementation()!; const child = { diff --git a/desktop/src/App.tsx b/desktop/src/App.tsx index 7fccfab..58a5478 100644 --- a/desktop/src/App.tsx +++ b/desktop/src/App.tsx @@ -107,6 +107,7 @@ export default function App() { detail.refresh(); report.refresh(); traces.refresh(); + referenced.refresh(); }} > Retry From 59a4d4f4d63661ef50d0cfcb870ab85e6691f687 Mon Sep 17 00:00:00 2001 From: "detail-app[bot]" Date: Sun, 6 Sep 2026 10:56:47 +0000 Subject: [PATCH 2/3] fix(desktop): declare referenced useLive hook and fix test url patterns --- desktop/src/App.test.tsx | 13 +++---------- desktop/src/App.tsx | 37 +++++++++++++++++++------------------ 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/desktop/src/App.test.tsx b/desktop/src/App.test.tsx index 1572c3a..9c215e0 100644 --- a/desktop/src/App.test.tsx +++ b/desktop/src/App.test.tsx @@ -376,16 +376,11 @@ it("preserves the comparison baseline when a note refreshes the session detail", it("retry re-fetches a one-shot referenced trace after a transient failure", async () => { const user = userEvent.setup(); const original = vi.mocked(api.read).getMockImplementation()!; - const recentPage = Array.from({ length: 200 }, (_, i) => ({ - ...trace, - trace_id: `ffff${String(i + 1).padStart(28, "0")}`, - root_span_id: `ffff${String(i + 1).padStart(28, "0")}`, - })); let referencedAttempts = 0; vi.mocked(api.read).mockImplementation(async (path) => { - if (path.includes("/traces?limit=200")) - return { traces: recentPage, total: 250, next_offset: "200" }; - if (path.includes("/traces?q=")) { + if (path.startsWith("traces?limit=200")) + return { traces: [trace], total: 1, next_offset: null }; + if (path.startsWith("traces?q=")) { referencedAttempts += 1; if (referencedAttempts === 1) throw new Error("receiver unreachable"); return { traces: [trace], total: 1, next_offset: null }; @@ -394,8 +389,6 @@ it("retry re-fetches a one-shot referenced trace after a transient failure", asy }); render(); await screen.findByText("· 1 expectation failed"); - await user.selectOptions(screen.getByLabelText("Run"), "run-2"); - await waitFor(() => expect(api.read).toHaveBeenCalledWith("runs/run-2")); 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 ↗" })); diff --git a/desktop/src/App.tsx b/desktop/src/App.tsx index 58a5478..250fd2e 100644 --- a/desktop/src/App.tsx +++ b/desktop/src/App.tsx @@ -41,34 +41,35 @@ export default function App() { const [requestedSpan, setRequestedSpan] = useState(null); const [showConnection, setShowConnection] = useState(false); const [showRun, setShowRun] = useState(false); - const [evidenceError, setEvidenceError] = useState(""); + const [evidenceRef, setEvidenceRef] = useState(null); + const referenced = useLive( + evidenceRef, + () => api.read(`traces?q=${evidenceRef}`), + 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]); const error = connection.error || traces.error || report.error || detail.error || - evidenceError; + referenced.error; const failures = report.data?.verification.filter((v) => v.status === "failed").length ?? 0; const unknown = report.data?.verification.filter((v) => v.status === "unknown").length ?? 0; - async function inspectEvidence(reference: string) { + function inspectEvidence(reference: string) { const [trace, span] = reference.split("/"); - try { - const page = await api.read( - `traces?limit=200&q=${trace}`, - ); - const target = page.traces.find( - (t) => t.run_id === runId && t.trace_id === trace, - ); - if (!target) throw new Error("Referenced trace was not found."); - setSelection(target); - setRequestedSpan(span); - setShowRun(false); - setEvidenceError(""); - } catch (error) { - setEvidenceError(String(error)); - } + setEvidenceRef(trace); + setRequestedSpan(span); + setShowRun(false); } return (
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); }} >