From 147a5ee783b732556c4aacbcbd6cc125c2f2348f Mon Sep 17 00:00:00 2001 From: Nandana Dileep <110280757+nandanadileep@users.noreply.github.com> Date: Thu, 6 Aug 2026 12:36:08 +0530 Subject: [PATCH] fix(react-ui): stop traces page crash when switching trace tabs (#11387) Switching from Backend Traces back to API Traces crashed the page with "can't access property status, e.response is undefined" (#11376). The API table briefly renders the previous tab's backend rows while the refetch effect is still pending, and those rows carry no `response` envelope. The status column dereferenced it unguarded. Render a neutral placeholder instead of throwing, and cover the tab-switch scenario with a regression spec. Assisted-by: opencode:big-pickle Signed-off-by: Nandana Dileep <110280757+nandanadileep@users.noreply.github.com> --- core/http/react-ui/e2e/traces-live.spec.js | 43 ++++++++++++++++++++++ core/http/react-ui/src/pages/Traces.jsx | 2 + 2 files changed, 45 insertions(+) diff --git a/core/http/react-ui/e2e/traces-live.spec.js b/core/http/react-ui/e2e/traces-live.spec.js index b668d8a65..1dfa6e5ec 100644 --- a/core/http/react-ui/e2e/traces-live.spec.js +++ b/core/http/react-ui/e2e/traces-live.spec.js @@ -20,3 +20,46 @@ test('marks an API trace with no response status as in progress', async ({ page await expect(row.locator('[title="In progress"]')).toBeVisible() await expect(row.locator('.fa-check-circle')).toHaveCount(0) }) + +// Regression for #11376: switching from Backend Traces back to API Traces +// used to crash the page. `traces` holds whichever list was fetched last, so +// right after `setActiveTab('api')` — before the refetch effect lands — the +// API table renders the previous tab's backend rows, which carry no +// `response` envelope. The status column must tolerate that instead of +// dereferencing `trace.response.status` and tearing down the React tree. +test('switching from backend to API traces with a response-less row does not crash', async ({ page }) => { + const pageErrors = [] + page.on('pageerror', (e) => pageErrors.push(e.message)) + + await page.route('**/api/traces?*', route => route.fulfill({ + json: [{ + id: 'api-1', + timestamp: '2026-08-05T02:00:00Z', + request: { method: 'POST', path: '/v1/chat/completions' }, + response: { status: 200 }, + }], + headers: { 'X-Total-Count': '1' }, + })) + await page.route('**/api/backend-traces?*', route => route.fulfill({ + json: [{ + id: 'backend-1', + type: 'llm', + timestamp: '2026-08-05T02:00:00Z', + model_name: 'mock-model', + summary: 'generated a reply', + }], + headers: { 'X-Total-Count': '1' }, + })) + + await page.goto('/app/traces') + await expect(page.locator('tbody tr').filter({ hasText: '/v1/chat/completions' })).toBeVisible() + + await page.getByRole('button', { name: /Backend Traces/ }).click() + await expect(page.locator('tbody tr').filter({ hasText: 'generated a reply' })).toBeVisible() + + await page.getByRole('button', { name: /API Traces/ }).click() + // The stale backend row renders in the API table for one frame; the status + // column falls back to a neutral placeholder rather than throwing. + await expect(page.locator('tbody tr').filter({ hasText: '/v1/chat/completions' })).toBeVisible() + expect(pageErrors).toEqual([]) +}) diff --git a/core/http/react-ui/src/pages/Traces.jsx b/core/http/react-ui/src/pages/Traces.jsx index 18980d348..c53aadce9 100644 --- a/core/http/react-ui/src/pages/Traces.jsx +++ b/core/http/react-ui/src/pages/Traces.jsx @@ -667,6 +667,8 @@ export default function Traces() {