refactor(app): central API client in src/lib/api.ts#8531
Conversation
- apiGet/apiPost: typed fetch wrappers throwing ApiError on non-2xx; endpoints registry builds every backend path (code, plots/filter, specs, insights, feedback, download, ...) from CONFIG.api.baseUrl - fetchWithAuth (debug API, CF Access cookie + X-Admin-Token) moves out of DebugPage into lib/api as a named export - All 14 raw fetch call sites across hooks/pages/components migrated; non-2xx and abort code paths preserved exactly per call site - useCodeFetch: isLoading is now a pending-request counter — a completing request no longer clears the loading state while another is in flight (deferred Copilot finding from #8519) Part 5 of the frontend modernization roadmap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Refactors the frontend to centralize HTTP access through a typed API client (apiGet/apiPost, ApiError, and an endpoints registry) and migrates existing pages/hooks/components off raw fetch() calls, while preserving prior user-visible error/abort behavior.
Changes:
- Migrates multiple call sites (pages, hooks, components) from
fetch()+API_URLstring-building toapiGet/apiPost+endpoints. - Moves the debug/admin authenticated fetch helper out of
DebugPageinto the shared API layer (fetchWithAuth). - Fixes overlapping-request loading state in
useCodeFetchby tracking an in-flight counter instead of a boolean, and updates unit tests accordingly.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| app/src/pages/StatsPage.tsx | Uses apiGet + endpoints for dashboard/visitors fetching; preserves prior error strings. |
| app/src/pages/SpecsListPage.tsx | Uses apiGet for images fetch with abort handling; preserves prior non-2xx behavior. |
| app/src/pages/SpecPage.tsx | Uses apiGet for spec loading and apiUrl(endpoints.download(...)) for downloads; preserves 404 messaging. |
| app/src/pages/MapPage.tsx | Uses apiGet with AbortController and maps ApiError back to prior "HTTP <status>" banner text. |
| app/src/pages/DebugPage.tsx | Replaces local adminFetch with shared fetchWithAuth for debug/admin endpoints. |
| app/src/hooks/usePlotOfTheDay.ts | Switches plot-of-the-day fetch to apiGet. |
| app/src/hooks/useFilterFetch.ts | Switches /plots/filter query building to endpoints.plotsFilter(...) + apiGet. |
| app/src/hooks/useFeaturedSpecs.ts | Switches featured-specs image fetch to apiGet. |
| app/src/hooks/useCodeFetch.ts | Refactors code fetching to apiGet + endpoints.code(...) and fixes loading state for concurrent requests. |
| app/src/hooks/useCodeFetch.test.ts | Adjusts expectations for the new fetch wrapper call signature/URLs. |
| app/src/components/SpecTabs.tsx | Switches cached tag-count fetch to apiGet while keeping silent-skip behavior. |
| app/src/components/RelatedSpecs.tsx | Switches related-specs fetch to apiGet + endpoints.relatedSpecs(...). |
| app/src/components/PlotOfTheDay.tsx | Switches plot-of-the-day fetch to apiGet. |
| app/src/components/Layout.tsx | Switches initial metadata prefetching to apiGet with per-endpoint non-2xx compatibility behavior. |
| app/src/components/FeedbackWidget.tsx | Switches feedback submission POSTs to apiPost + endpoints.feedback. |
| ThemeContext, | ||
| } from 'src/hooks/useLayoutContext'; | ||
| import { useThemeMode } from 'src/hooks/useThemeMode'; | ||
| import { ApiError, apiGet, endpoints } from 'src/lib/api'; |
There was a problem hiding this comment.
Confirmed and fixed — the root .gitignore's Python build-artifact rule (lib/) silently excluded app/src/lib/, so the module never made it into the commit. Added a scoped negation (!app/src/lib/) and committed the file; CI is re-running.
src/lib/api.ts was silently excluded by the root .gitignore's Python build-artifact rule, so the API-client branch type-checked locally but failed on CI with TS2307. Add a scoped negation and commit the file. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
| const url = apiUrl(path); | ||
| const response = await fetch(url, { | ||
| ...init, | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json', ...(init?.headers as Record<string, string>) }, | ||
| body: JSON.stringify(body), | ||
| }); |
| const headers: Record<string, string> = { ...((init.headers as Record<string, string>) || {}) }; | ||
| if (token) headers['X-Admin-Token'] = token; | ||
| if (init.body && !headers['Content-Type']) headers['Content-Type'] = 'application/json'; | ||
| return fetch(url, { credentials: 'include', ...init, headers }); | ||
| } |
| images?: PlotImage[]; | ||
| }>(endpoints.plotsFilter(params.toString()), { signal: abortController.signal }); | ||
|
|
||
| if (abortController.signal.aborted) return; | ||
|
|
Summary
Part 5 of the frontend modernization roadmap.
src/lib/api.ts:apiGet/apiPosttyped fetch wrappers that throwApiErroron non-2xx, plus anendpointsregistry building every backend path fromCONFIG.api.baseUrl(code, plots/filter, specs, insights, feedback, download, …)fetchWithAuth(debug API: CF Access cookie +X-Admin-Token) moves out ofDebugPageintolib/apias a named exportfetch()call sites across hooks/pages/components migrated; every non-2xx and abort code path preserved per call site (silent-skip stays silent, user-visible error strings unchanged)useCodeFetch:isLoadingis now a pending-request counter — a completing request no longer clears the loading state while another request is in flight. This resolves the deferred Copilot finding from chore(app): frontend tooling baseline — Prettier, import sorting, CI lint/type-check gates #8519.globalThis.fetch, so they still assert the real constructed URLs end-to-endVerification
yarn lint✓ ·yarn fm:check✓ ·yarn type-check(app + tests) ✓ ·yarn test✓ ·yarn build✓ · grep: no rawfetch(outsidelib/api.ts🤖 Generated with Claude Code