9 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 26-pwa-performance | 01 | execute | 1 |
|
true |
|
|
Purpose: Reduces the main bundle from ~1.4 MB to ~200-400 KB, achieving PERF-01 (initial load < 2s broadband, < 5s on 3G) and contributing to PERF-05 (cached load < 1s with smaller chunks to cache). Output: Lazy-loaded App.tsx, vendor-chunked vite.config.ts, verified build output.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/26-pwa-performance/26-RESEARCH.md @ui/src/App.tsx @ui/vite.config.ts Task 1: Convert App.tsx page imports to React.lazy with Suspense ui/src/App.tsx - ui/src/App.tsx - ui/src/components/ui/skeleton.tsx 1. Add `lazy, Suspense` to the React import at the top of App.tsx (import from "react").-
Convert ALL page component imports (lines 9-47 in current file) from eager to lazy. Each page import becomes:
const Dashboard = lazy(() => import("./pages/Dashboard")); const Companies = lazy(() => import("./pages/Companies")); const Agents = lazy(() => import("./pages/Agents")); const AgentDetail = lazy(() => import("./pages/AgentDetail")); const Projects = lazy(() => import("./pages/Projects")); const ProjectDetail = lazy(() => import("./pages/ProjectDetail")); const ProjectWorkspaceDetail = lazy(() => import("./pages/ProjectWorkspaceDetail")); const Issues = lazy(() => import("./pages/Issues")); const IssueDetail = lazy(() => import("./pages/IssueDetail")); const Routines = lazy(() => import("./pages/Routines")); const RoutineDetail = lazy(() => import("./pages/RoutineDetail")); const ExecutionWorkspaceDetail = lazy(() => import("./pages/ExecutionWorkspaceDetail")); const Goals = lazy(() => import("./pages/Goals")); const GoalDetail = lazy(() => import("./pages/GoalDetail")); const Approvals = lazy(() => import("./pages/Approvals")); const ApprovalDetail = lazy(() => import("./pages/ApprovalDetail")); const Costs = lazy(() => import("./pages/Costs")); const Activity = lazy(() => import("./pages/Activity")); const Inbox = lazy(() => import("./pages/Inbox")); const CompanySettings = lazy(() => import("./pages/CompanySettings")); const SkillBrowser = lazy(() => import("./pages/SkillBrowser")); const SkillDetail = lazy(() => import("./pages/SkillDetail")); const CompanyExport = lazy(() => import("./pages/CompanyExport")); const CompanyImport = lazy(() => import("./pages/CompanyImport")); const DesignGuide = lazy(() => import("./pages/DesignGuide")); const InstanceGeneralSettings = lazy(() => import("./pages/InstanceGeneralSettings")); const InstanceSettings = lazy(() => import("./pages/InstanceSettings")); const InstanceExperimentalSettings = lazy(() => import("./pages/InstanceExperimentalSettings")); const PluginManager = lazy(() => import("./pages/PluginManager")); const PluginSettings = lazy(() => import("./pages/PluginSettings")); const PluginPage = lazy(() => import("./pages/PluginPage")); const RunTranscriptUxLab = lazy(() => import("./pages/RunTranscriptUxLab")); const OrgChart = lazy(() => import("./pages/OrgChart")); const NewAgent = lazy(() => import("./pages/NewAgent")); const AuthPage = lazy(() => import("./pages/Auth")); const BoardClaimPage = lazy(() => import("./pages/BoardClaim")); const CliAuthPage = lazy(() => import("./pages/CliAuth")); const InviteLandingPage = lazy(() => import("./pages/InviteLanding")); const NotFoundPage = lazy(() => import("./pages/NotFound")); -
Keep ALL non-page imports as eager (Layout, OnboardingWizard, authApi, healthApi, queryKeys, context hooks, lib utils). These are needed for the app shell and should not be lazy.
-
Ensure each page module uses
export default— check if pages use named exports. If they useexport function PageName, the lazy import syntax needs:lazy(() => import("./pages/PageName").then(m => ({ default: m.PageName }))). Check the actual export style and adjust accordingly. -
Wrap the
<Routes>block inside theApp()function with a<Suspense>boundary:<Suspense fallback={<div className="flex items-center justify-center h-full"><Skeleton className="h-8 w-48" /></div>}> <Routes> {/* existing routes unchanged */} </Routes> </Suspense>Import
Skeletonfrom@/components/ui/skeleton. -
Do NOT lazy-load
OnboardingWizard— it renders outside Routes and is always needed. grep -c "lazy(" ui/src/App.tsx && grep -q "Suspense" ui/src/App.tsx && echo "PASS" <acceptance_criteria>grep -c "lazy(" ui/src/App.tsxreturns 39 or more (one per page component)grep "Suspense" ui/src/App.tsxshows Suspense wrappergrep "Skeleton" ui/src/App.tsxshows skeleton import and usage in fallback- No eager
import { Dashboard }style page imports remain - Non-page imports (Layout, OnboardingWizard, authApi, etc.) remain eager
pnpm --filter @paperclipai/ui buildsucceeds without errors </acceptance_criteria> All page imports converted to React.lazy. Suspense boundary wraps Routes with Skeleton fallback. Build succeeds.
build: {
rollupOptions: {
output: {
manualChunks: {
"vendor-react": ["react", "react-dom"],
"vendor-router": ["react-router-dom"],
"vendor-query": ["@tanstack/react-query"],
"vendor-markdown": ["react-markdown", "remark-gfm"],
},
},
},
},
-
Add chunks one at a time conceptually — but write them all at once. The key concern per RESEARCH Pitfall 5 is circular dependency errors. Start with the safest boundaries (react, react-dom are always clean).
-
Do NOT include
@mdxeditor/editorin manualChunks — it has complex internal imports that may cause circular dependency errors. Let Vite's default splitting handle it. -
Do NOT include
rehype-highlight— it was replaced by manual highlight.js usage in Phase 25. -
Run
pnpm --filter @paperclipai/ui buildafter writing the config to verify no circular dependency errors. -
If the build fails with circular dependency errors on any specific chunk, remove that chunk from
manualChunksand retry. Document which chunks were removed and why in the summary. pnpm --filter @paperclipai/ui build 2>&1 | tail -5 && grep -q "manualChunks" ui/vite.config.ts && echo "PASS" <acceptance_criteria>grep "manualChunks" ui/vite.config.tsshows the configurationgrep "vendor-react" ui/vite.config.tsshows react chunkpnpm --filter @paperclipai/ui buildsucceeds without errors- Build output shows multiple vendor chunk files in dist/assets/ </acceptance_criteria> Vite config has manualChunks for vendor splitting. Build succeeds. Multiple vendor chunks produced in dist/assets/.
<success_criteria> All page imports lazy-loaded. Vendor chunks extracted. Build succeeds. Main entry bundle significantly reduced from ~1.4 MB baseline. </success_criteria>
After completion, create `.planning/phases/26-pwa-performance/26-01-SUMMARY.md`