---
id: S05
parent: M019
milestone: M019
provides:
  - Modular API client structure (frontend/src/api/*.ts) for clean per-domain imports
  - Code-split admin/creator routes reducing initial bundle load
  - Consistent paginated response shape on /topics and /videos endpoints
requires:
  []
affects:
  []
key_files:
  - frontend/src/api/client.ts
  - frontend/src/api/index.ts
  - frontend/src/api/search.ts
  - frontend/src/api/techniques.ts
  - frontend/src/api/creators.ts
  - frontend/src/api/topics.ts
  - frontend/src/api/stats.ts
  - frontend/src/api/reports.ts
  - frontend/src/api/admin-pipeline.ts
  - frontend/src/api/admin-techniques.ts
  - frontend/src/api/auth.ts
  - frontend/src/App.tsx
  - backend/schemas.py
  - backend/routers/topics.py
  - backend/routers/videos.py
key_decisions:
  - topics.ts imports TechniqueListResponse from ./techniques to avoid type duplication across domain modules
  - ProtectedRoute wraps Suspense (not vice versa) so auth redirect fires before chunk download begins
  - TopicListResponse and VideoListResponse as separate typed schemas rather than generic PaginatedResponse — keeps OpenAPI docs type-safe
patterns_established:
  - Domain API module pattern: shared client.ts (request helper + auth) → domain modules → barrel index.ts for backward-compatible imports
  - React.lazy per-route with individual Suspense wrappers — eager pages never see fallback
observability_surfaces:
  - none
drill_down_paths:
  - .gsd/milestones/M019/slices/S05/tasks/T01-SUMMARY.md
  - .gsd/milestones/M019/slices/S05/tasks/T02-SUMMARY.md
  - .gsd/milestones/M019/slices/S05/tasks/T03-SUMMARY.md
duration: ""
verification_result: passed
completed_at: 2026-04-03T23:11:05.685Z
blocker_discovered: false
---

# S05: [A] Sprint 0 Refactoring Tasks

**Split monolithic API client into 10 domain modules, added route-level code splitting for 6 admin/creator pages, and normalized 2 bare-list endpoints to paginated response shape.**

## What Happened

Three structural refactoring tasks from the M018 audit, all frontend-focused with one backend normalization pass.

**T01 — API client decomposition.** The 945-line `public-client.ts` monolith was split into 10 domain modules (`search.ts`, `techniques.ts`, `creators.ts`, `topics.ts`, `stats.ts`, `reports.ts`, `admin-pipeline.ts`, `admin-techniques.ts`, `auth.ts`) plus a shared `client.ts` (request helper, ApiError, auth token injection) and barrel `index.ts`. All 17 consumer files updated to import from the barrel. The original monolith was deleted. One unlisted consumer (`SubTopicPage.tsx`) was discovered and updated.

**T02 — Route-level code splitting.** Six pages converted from static imports to `React.lazy()` + `Suspense`: AdminPipeline, AdminTechniquePages, AdminReports, CreatorDashboard, CreatorSettings, About. Core public pages and auth pages remain eagerly loaded. Build output went from ~2-3 JS files to 7 discrete chunks — AdminPipeline alone is 34KB gzipped to 9.7KB, now loaded only when admins visit that page.

**T03 — Paginated response normalization.** `GET /topics` and `GET /videos` were returning bare lists. Both now return `{items, total}` (videos also includes offset/limit). Added `TopicListResponse` and `VideoListResponse` schemas to `backend/schemas.py`. Updated `fetchTopics()` return type and both frontend consumers (TopicsBrowse, Home) to read `.items`. No frontend code calls the public `/videos` endpoint directly, so only the backend changed there.

## Verification

All slice-level checks pass:
- `npm run build` exits 0 — 80 modules, 7 JS chunks, zero TS errors
- `rg 'public-client' frontend/src/` — no matches (monolith fully removed)
- `frontend/src/api/index.ts` and `client.ts` exist
- 11 `.ts` files in `frontend/src/api/` (client + 9 domain + index)
- 6 `React.lazy` imports in App.tsx confirmed
- `rg 'response_model=list' backend/routers/topics.py backend/routers/videos.py` — no matches

## Requirements Advanced

None.

## Requirements Validated

None.

## New Requirements Surfaced

None.

## Requirements Invalidated or Re-scoped

None.

## Deviations

SubTopicPage.tsx was an additional consumer not listed in the T01 plan — updated the same way as others.

## Known Limitations

Backend tests for T03 require PostgreSQL on ub01 (port 5433) — not runnable locally. The schema changes are straightforward (wrapping list in {items, total}) and the frontend build confirms type compatibility.

## Follow-ups

None.

## Files Created/Modified

- `frontend/src/api/client.ts` — New — shared request helper, ApiError, AUTH_TOKEN_KEY, base URL config
- `frontend/src/api/index.ts` — New — barrel re-exporting all domain modules
- `frontend/src/api/search.ts` — New — search domain API functions
- `frontend/src/api/techniques.ts` — New — technique CRUD API functions
- `frontend/src/api/creators.ts` — New — creator browse/detail API functions
- `frontend/src/api/topics.ts` — New — topics/subtopics API functions, updated return type for paginated response
- `frontend/src/api/stats.ts` — New — stats endpoint function
- `frontend/src/api/reports.ts` — New — report CRUD API functions
- `frontend/src/api/admin-pipeline.ts` — New — pipeline admin API functions
- `frontend/src/api/admin-techniques.ts` — New — admin technique page functions
- `frontend/src/api/auth.ts` — New — auth endpoint functions
- `frontend/src/App.tsx` — 6 pages converted to React.lazy with Suspense wrappers
- `backend/schemas.py` — Added TopicListResponse and VideoListResponse schemas
- `backend/routers/topics.py` — Returns TopicListResponse instead of bare list
- `backend/routers/videos.py` — Returns VideoListResponse with total count instead of bare list
- `frontend/src/pages/TopicsBrowse.tsx` — Reads .items from paginated topics response
- `frontend/src/pages/Home.tsx` — Reads .items from paginated topics response
