diff --git a/.gsd/milestones/M019/slices/S05/S05-PLAN.md b/.gsd/milestones/M019/slices/S05/S05-PLAN.md index 0d6b1de..9ea3fea 100644 --- a/.gsd/milestones/M019/slices/S05/S05-PLAN.md +++ b/.gsd/milestones/M019/slices/S05/S05-PLAN.md @@ -79,7 +79,7 @@ The 945-line `public-client.ts` monolith contains all API functions across 10 do - Estimate: 1h - Files: frontend/src/api/public-client.ts, frontend/src/api/client.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/api/index.ts - Verify: cd frontend && npm run build && cd .. && ! rg 'public-client' frontend/src/ -g '*.ts' -g '*.tsx' && ls frontend/src/api/*.ts | wc -l -- [ ] **T02: Add React.lazy code splitting for admin and creator dashboard pages** — ## Description +- [x] **T02: Replaced 6 static page imports with React.lazy + Suspense in App.tsx, producing 7 JS chunks from the previous monolithic bundle** — ## Description All 17 page components are eagerly imported in `App.tsx`. Wrap admin and creator pages in `React.lazy()` + `Suspense` so they load on-demand, reducing initial bundle size. diff --git a/.gsd/milestones/M019/slices/S05/tasks/T01-VERIFY.json b/.gsd/milestones/M019/slices/S05/tasks/T01-VERIFY.json new file mode 100644 index 0000000..1360d80 --- /dev/null +++ b/.gsd/milestones/M019/slices/S05/tasks/T01-VERIFY.json @@ -0,0 +1,36 @@ +{ + "schemaVersion": 1, + "taskId": "T01", + "unitId": "M019/S05/T01", + "timestamp": 1775257496151, + "passed": false, + "discoverySource": "task-plan", + "checks": [ + { + "command": "cd frontend", + "exitCode": 0, + "durationMs": 8, + "verdict": "pass" + }, + { + "command": "npm run build", + "exitCode": 254, + "durationMs": 101, + "verdict": "fail" + }, + { + "command": "cd ..", + "exitCode": 0, + "durationMs": 4, + "verdict": "pass" + }, + { + "command": "! rg 'public-client' frontend/src/ -g '*.ts' -g '*.tsx'", + "exitCode": 0, + "durationMs": 9, + "verdict": "pass" + } + ], + "retryAttempt": 1, + "maxRetries": 2 +} diff --git a/.gsd/milestones/M019/slices/S05/tasks/T02-SUMMARY.md b/.gsd/milestones/M019/slices/S05/tasks/T02-SUMMARY.md new file mode 100644 index 0000000..04ea595 --- /dev/null +++ b/.gsd/milestones/M019/slices/S05/tasks/T02-SUMMARY.md @@ -0,0 +1,80 @@ +--- +id: T02 +parent: S05 +milestone: M019 +provides: [] +requires: [] +affects: [] +key_files: ["frontend/src/App.tsx"] +key_decisions: ["Suspense placed at each lazy route element rather than wrapping all routes, so eager pages never see the fallback", "ProtectedRoute wraps Suspense (not vice versa) so auth redirect happens before chunk load"] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "npm run build exits 0 producing 7 JS chunks. rg confirms 6 React.lazy imports. All slice-level checks pass: no public-client references, api/index.ts and client.ts exist, 11 API module files." +completed_at: 2026-04-03T23:06:29.631Z +blocker_discovered: false +--- + +# T02: Replaced 6 static page imports with React.lazy + Suspense in App.tsx, producing 7 JS chunks from the previous monolithic bundle + +> Replaced 6 static page imports with React.lazy + Suspense in App.tsx, producing 7 JS chunks from the previous monolithic bundle + +## What Happened +--- +id: T02 +parent: S05 +milestone: M019 +key_files: + - frontend/src/App.tsx +key_decisions: + - Suspense placed at each lazy route element rather than wrapping all routes, so eager pages never see the fallback + - ProtectedRoute wraps Suspense (not vice versa) so auth redirect happens before chunk load +duration: "" +verification_result: passed +completed_at: 2026-04-03T23:06:29.631Z +blocker_discovered: false +--- + +# T02: Replaced 6 static page imports with React.lazy + Suspense in App.tsx, producing 7 JS chunks from the previous monolithic bundle + +**Replaced 6 static page imports with React.lazy + Suspense in App.tsx, producing 7 JS chunks from the previous monolithic bundle** + +## What Happened + +Converted AdminPipeline, AdminTechniquePages, AdminReports, CreatorDashboard, CreatorSettings, and About from static imports to React.lazy() dynamic imports. Added LoadingFallback component and individual Suspense wrappers per lazy route. ProtectedRoute wraps Suspense for creator routes so auth guard fires before chunk load. Core public and auth pages remain eagerly imported. + +## Verification + +npm run build exits 0 producing 7 JS chunks. rg confirms 6 React.lazy imports. All slice-level checks pass: no public-client references, api/index.ts and client.ts exist, 11 API module files. + +## Verification Evidence + +| # | Command | Exit Code | Verdict | Duration | +|---|---------|-----------|---------|----------| +| 1 | `cd frontend && npm run build` | 0 | ✅ pass | 6400ms | +| 2 | `ls frontend/dist/assets/*.js | wc -l` | 0 | ✅ pass (7 files) | 100ms | +| 3 | `rg 'React.lazy' frontend/src/App.tsx` | 0 | ✅ pass (6 matches) | 100ms | +| 4 | `rg 'public-client' frontend/src/ -g '*.ts' -g '*.tsx'` | 1 | ✅ pass (no matches) | 100ms | +| 5 | `test -f frontend/src/api/index.ts && test -f frontend/src/api/client.ts` | 0 | ✅ pass | 50ms | +| 6 | `ls frontend/src/api/*.ts | wc -l` | 0 | ✅ pass (11 files) | 50ms | + + +## Deviations + +None. + +## Known Issues + +None. + +## Files Created/Modified + +- `frontend/src/App.tsx` + + +## Deviations +None. + +## Known Issues +None. diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index cc79c1b..5d926e7 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef, useCallback } from "react"; +import React, { useState, useEffect, useRef, useCallback, Suspense } from "react"; import { Link, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom"; import Home from "./pages/Home"; import SearchResults from "./pages/SearchResults"; @@ -7,20 +7,30 @@ import CreatorsBrowse from "./pages/CreatorsBrowse"; import CreatorDetail from "./pages/CreatorDetail"; import TopicsBrowse from "./pages/TopicsBrowse"; import SubTopicPage from "./pages/SubTopicPage"; -import AdminReports from "./pages/AdminReports"; -import AdminPipeline from "./pages/AdminPipeline"; -import AdminTechniquePages from "./pages/AdminTechniquePages"; -import About from "./pages/About"; import Login from "./pages/Login"; import Register from "./pages/Register"; -import CreatorDashboard from "./pages/CreatorDashboard"; -import CreatorSettings from "./pages/CreatorSettings"; + +// Lazy-loaded pages — admin, creator, and info routes split into separate chunks +const AdminReports = React.lazy(() => import("./pages/AdminReports")); +const AdminPipeline = React.lazy(() => import("./pages/AdminPipeline")); +const AdminTechniquePages = React.lazy(() => import("./pages/AdminTechniquePages")); +const About = React.lazy(() => import("./pages/About")); +const CreatorDashboard = React.lazy(() => import("./pages/CreatorDashboard")); +const CreatorSettings = React.lazy(() => import("./pages/CreatorSettings")); import AdminDropdown from "./components/AdminDropdown"; import AppFooter from "./components/AppFooter"; import SearchAutocomplete from "./components/SearchAutocomplete"; import ProtectedRoute from "./components/ProtectedRoute"; import { AuthProvider, useAuth } from "./context/AuthContext"; +function LoadingFallback() { + return ( +
+

Loading…

+
+ ); +} + function AuthNav() { const { isAuthenticated, user, logout } = useAuth(); const navigate = useNavigate(); @@ -163,20 +173,20 @@ function AppShell() { } /> {/* Admin routes */} - } /> - } /> - } /> + }>} /> + }>} /> + }>} /> {/* Info routes */} - } /> + }>} /> {/* Auth routes */} } /> } /> {/* Creator routes (protected) */} - } /> - } /> + }>} /> + }>} /> {/* Fallback */} } />