Severity: Low
Labels: tech-debt, frontend, P3
Location: frontend/src/lib/api.ts:15-29, frontend/src/lib/sse.ts:33-37; consumer frontend/src/components/ErrorBoundary.tsx:44-53
Description
Both the JSON client and the SSE consumer discard the numeric HTTP status and throw a plain Error whose only signal is a stringified body; ErrorFallback then re-derives intent by substring-matching the message. This is brittle — 403/429/500 all collapse to a generic branch, and a body containing "session" misclassifies as "session expired." streamSSE also attaches no AbortController/timeout, so a stalled stream hangs indefinitely.
Suggested fix
Throw a typed class ApiError extends Error { status: number } from fetchJSON and streamSSE; branch ErrorFallback on status. Add an AbortController with an idle timeout to streamSSE.
Acceptance criteria
- Error UI branches on HTTP status, not substrings; a stalled SSE stream aborts after a timeout.
Severity: Low
Labels: tech-debt, frontend, P3
Location:
frontend/src/lib/api.ts:15-29,frontend/src/lib/sse.ts:33-37; consumerfrontend/src/components/ErrorBoundary.tsx:44-53Description
Both the JSON client and the SSE consumer discard the numeric HTTP status and throw a plain
Errorwhose only signal is a stringified body;ErrorFallbackthen re-derives intent by substring-matching the message. This is brittle — 403/429/500 all collapse to a generic branch, and a body containing "session" misclassifies as "session expired."streamSSEalso attaches noAbortController/timeout, so a stalled stream hangs indefinitely.Suggested fix
Throw a typed
class ApiError extends Error { status: number }fromfetchJSONandstreamSSE; branchErrorFallbackonstatus. Add anAbortControllerwith an idle timeout tostreamSSE.Acceptance criteria