From d6a0638c5941c9f53e8e3b2e6a393784411faa19 Mon Sep 17 00:00:00 2001 From: "soroush.asadi" Date: Wed, 17 Jun 2026 08:33:59 +0330 Subject: [PATCH] Harden API client against stale-backend HTML fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the running backend is missing a route, the SPA fallback serves index.html with a 200, and the api helper returned undefined — which crashed pages on .map/.length (the Delivery pipeline white-screened against an old build). Now a non-JSON 2xx on an /api call throws a clear 'API is running an older build' error instead, and PipelinePage defensively coerces its lists to arrays. Co-Authored-By: Claude Opus 4.8 --- client/src/lib/api.ts | 13 ++++++++++++- client/src/pages/PipelinePage.tsx | 4 ++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts index 73c196c..4204ef1 100644 --- a/client/src/lib/api.ts +++ b/client/src/lib/api.ts @@ -21,7 +21,18 @@ async function request(method: string, url: string, body?: unknown): Promise< } const contentType = response.headers.get('content-type') ?? '' - return contentType.includes('application/json') ? ((await response.json()) as T) : (undefined as T) + if (contentType.includes('application/json')) { + return (await response.json()) as T + } + + // A non-JSON 2xx on an /api call is almost always the SPA fallback (index.html) — i.e. the route + // doesn't exist on the backend that's running (a stale build). Fail loudly so callers surface it, + // instead of returning undefined and letting the page crash on `.map`/`.length`. + if (url.startsWith('/api') && contentType.includes('text/html')) { + throw new Error(`Unexpected HTML from ${url}. The API is likely running an older build — restart the server.`) + } + + return undefined as T } export const api = { diff --git a/client/src/pages/PipelinePage.tsx b/client/src/pages/PipelinePage.tsx index 7c03ecc..a4007e2 100644 --- a/client/src/pages/PipelinePage.tsx +++ b/client/src/pages/PipelinePage.tsx @@ -97,8 +97,8 @@ export function PipelinePage() { api.get(`/api/orgboard/change-requests?organizationId=${organizationId}`), api.get(`/api/orgboard/divisions?organizationId=${organizationId}`).catch(() => []), ]) - setRequests(list) - setDivisions(divs) + setRequests(Array.isArray(list) ? list : []) + setDivisions(Array.isArray(divs) ? divs : []) } catch (err) { toast.error((err as Error).message) }