diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 6ebd9fe..ea3426a 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -68,6 +68,16 @@ class HttpError extends Error { } } +// Collapse bursts of connection failures (e.g. while the server restarts) into a +// single notification rather than one per failed request. +let lastErrorNotifiedAt = 0; +function notifyErrorThrottled(title: string, message: string): void { + const now = Date.now(); + if (now - lastErrorNotifiedAt < 30_000) return; + lastErrorNotifiedAt = now; + notify({ level: "error", title, message }); +} + async function req(url: string, opts: RequestInit = {}): Promise { const method = opts.method ?? "GET"; let r: Response; @@ -78,22 +88,16 @@ async function req(url: string, opts: RequestInit = {}): Promise { ...opts, }); } catch (e) { - // Network/connection failure — surface it in the notification center. - notify({ - level: "error", - title: "Network error", - message: `Couldn't reach the server (${method} ${url}).`, - }); + notifyErrorThrottled( + "Connection lost", + "Couldn't reach the server — it may be restarting. This will clear once it's back." + ); throw e; } if (!r.ok) { - // Server faults are worth logging; 401/403/404 etc. are handled by callers. + // Server faults are worth surfacing; 401/403/404 etc. are handled by callers. if (r.status >= 500) { - notify({ - level: "error", - title: `Server error ${r.status}`, - message: `${method} ${url}`, - }); + notifyErrorThrottled(`Server error ${r.status}`, `${method} ${url}`); } throw new HttpError(r.status); } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 77d2204..1026675 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -2,10 +2,13 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; // During `vite dev` (host with Node), proxy API calls to the backend container. +// Use 127.0.0.1 (not "localhost") so Node doesn't resolve to IPv6 ::1, which the +// Docker port publish may not answer — that surfaces as proxy connection failures. +const target = "http://127.0.0.1:8080"; const proxy = { - "/api": "http://localhost:8080", - "/auth": "http://localhost:8080", - "/healthz": "http://localhost:8080", + "/api": target, + "/auth": target, + "/healthz": target, }; export default defineConfig({