Frontend Performance Baseline — 2026-08-07 (Phase 3D, slice 1)

Scope: miz-oki-command-center-ui — bundle-analysis wiring, first code-splitting pass on heavy visualization routes, fleet-grid virtualization decision, and the route-size budget this slice establishes.

Truth discipline (TRUTH.md): every number below is a measurement of a local production build (npm run build, Next.js 15.5.21, standalone output, built in the working tree on 2026-08-07). These are not live-infrastructure measurements — nothing here upgrades to a "verified result" serving claim until the same build is deployed and measured on Cloud Run. The budget in §5 is a rule going forward, not an observed guarantee.


1. Method

2. Bundle analyzer (opt-in)

@next/bundle-analyzer (already in devDependencies) is wired in miz-oki-command-center-ui/next.config.mjs behind ANALYZE=1:

ANALYZE=1 npm run build   # writes treemap reports under .next/analyze/
npm run build             # ANALYZE unset -> wrapper disabled, config untouched

Measured 2026-08-07: with ANALYZE unset the exported config's keys are byte-identical to the pre-wrapper config (no webpack hook injected); with ANALYZE=1 the analyzer's webpack hook is present. The Phase-3F security headers block is preserved verbatim.

3. Changed routes — before / after (measured local build)

First Load JS = what a cold client downloads to render the route.

Route First Load JS before First Load JS after Delta Route size before → after
/analytics 589 kB 106 kB −483 kB (−82%) 281 kB → 1.67 kB
/kg/ecosystem 251 kB 175 kB −76 kB (−30%) 41.3 kB → 9.73 kB
/kg/live 193 kB 150 kB −43 kB (−22%) 14.5 kB → 15.8 kB

The heavy libraries did not disappear — they moved into async chunks fetched when the component actually renders (with skeleton fallbacks in the page's own loading idiom). /kg/live's per-route size rose 1.3 kB (the dynamic loader + fallback markup) while its first load dropped 43 kB.

Shared chunks (all routes):

Chunk Before After
chunks/31255-*.js 46 kB 46 kB
chunks/4bd1b696-*.js 54.2 kB 54.2 kB
other shared chunks 2.72 kB 3.6 kB
Total shared by all 103 kB 104 kB
Middleware 92.6 kB 92.6 kB

4. Code-split inventory

Applied (next/dynamic at the importing page only; SSR left at its default in all four cases — measured 2026-08-07, none of these components reads window/document at module scope, so server-render semantics are unchanged):

Import site Component (heavy libs) Fallback idiom
app/analytics/page.tsx AdvancedAnalyticsDashboard (@tensorflow/tfjs, d3, recharts, framer-motion) root app/loading.tsx spinner on the dashboard's slate gradient
app/kg/live/page.tsx GraphCanvas (d3) the page's own RefreshCw spinner over bg-gray-950/80
app/kg/ecosystem/page.tsx KGBrainVisualizer (d3, framer-motion) pulse skeleton on the component's bg-gray-900 rounded-xl footprint
app/kg/ecosystem/page.tsx RoutingFlowDiagram (reactflow + stylesheet) same, h-[600px]
app/kg/ecosystem/page.tsx CausalGraphExplorer (d3, framer-motion) same, h-[600px]

CausalGraphExplorer sits in the same tab group as the two mapped ecosystem targets and carries the same libs, so it was split in the same pass — the three tabs now each load on first activation.

Skipped — already dynamic before this slice (measured):

Site Evidence
app/boss-srpvdal/boss-srpvdal-client.tsx → BossAgentSRPVDAL dynamic(..., { ssr: false }) at line 6; route already 104 kB
components/agent-ide/AgentIDE.tsx:32 and EnhancedAgentIDE.tsx:53 → MonacoEditor both parents already dynamic(...); /agent-ide 143 kB
app/neural-brain/page.tsx:22 → NeuralBrainVisualization (three) already dynamic(...); route 145 kB

Not split: PlanVisualization (also imports GraphCanvas) is imported only by an archive-ui/ page outside the app build — no live route pays for it.

5. Budget rule (established by this slice)

Route Baseline First Load JS
/command-center 125 kB
/command-center/actions 121 kB
/command-center/approvals 121 kB
/command-center/audit 121 kB
/command-center/cells 123 kB
/command-center/decisions 121 kB
/command-center/domains 113 kB
/command-center/events 122 kB
/command-center/kg-live 121 kB
/command-center/learning 109 kB
/command-center/policies 120 kB

(Baseline column from the pre-change build; the after build read each +1 kB via the shared-chunk delta — the budget tracks the route's own weight, so ±1 kB shared-baseline movement does not count against a page.)

6. Fleet-grid virtualization — measured decision: NOT WARRANTED, SKIPPED

The work item named app/cells/page.tsx; that path does not exist in the tree. The real 36-cell fleet surface is app/command-center/cells/page.tsx (Phase 2C), which also sits outside this slice's writable zone. Measured render counts of that page (2026-08-07):

Worst case ≈ 79 rendered rows/cards — far under the ~200-row threshold at which @tanstack/react-virtual pays for its complexity. Virtualization was therefore not applied anywhere in this slice (honesty over checkbox): the library remains installed and unused, ready for a genuinely long surface (for example a future events/audit table that renders hundreds of rows in one scroll container).

7. Remaining candidates (largest routes not yet split)

Baseline First Load JS, measured local build 2026-08-07. Attribution beyond the direct imports noted below needs an ANALYZE=1 treemap — that is what §2 is for.

Route First Load JS Direct heavy imports measured at the page file
/boss/realtime 262 kB via components/graph/BossAgentPanel subtree (attribute with ANALYZE=1)
/operations/email-intelligence 261 kB recharts imported directly by the page
/operations/unified-revenue 252 kB recharts imported directly by the page
/visualize/[kernelId] 251 kB via components/visualizations/* subtree
/operations/cell32-optimizer 231 kB via component subtree
/marketing/campaigns 219 kB via components/relu/CampaignLauncher subtree
/marketing/conversions 201 kB —
/marketing/audiences 199 kB —
/relu 196 kB —
/dashboard 191 kB —

Note for the recharts pair: recharts is chart-per-page here, so the /analytics pattern (dynamic at the page import site) applies directly.

8. Gates at completion of this slice


9. Slice 2 (2026-08-07, session 9) — the recharts pair split

Truth discipline: every number below is a measurement of a local production build (npm run build, Next.js 15.5.21, standalone output, 2026-08-07). Nothing here is a live-infrastructure claim.

9.1 Method

9.2 Noise floor (shared concurrent tree)

399 app routes in both tables — none added or removed. Of the 397 routes this slice did not touch, 389 moved exactly 0 kB and 8 moved exactly +1 kB (/blog, /blogs, /command-center/approvals, /email-agent, /evaluations, /invest, /unauthorized, /vision) — a display-rounding tick consistent with "other shared chunks" moving 3.6 → 3.64 kB while both named shared chunks kept identical content hashes (31255-c7c904d6581c47f1.js, 4bd1b696-bad92808725a934a.js). The floor is therefore ±1 kB; the two changed-route deltas below are 147–156× that floor.

9.3 Changed routes — before / after (measured local build, concurrent tree)

The after column is a real measurement of a completed exit-0 build (2026-08-07, ~20:29–20:35 UTC) taken while both sibling streams were still editing their disjoint zones; §9.2 quantifies exactly how quiet the rest of the table stayed. The wave-close quiet-tree confirmation build (§9.6) is the authoritative close-out number.

Route First Load JS before First Load JS after Delta Route size before → after
/operations/email-intelligence 262 kB 106 kB −156 kB (−60%) 15.4 kB → 2.12 kB
/operations/unified-revenue 253 kB 106 kB −147 kB (−58%) 12.3 kB → 2.17 kB

Shared chunks (all routes):

Chunk Before After
chunks/31255-c7c904d6581c47f1.js 46 kB 46 kB
chunks/4bd1b696-bad92808725a934a.js 54.2 kB 54.2 kB
other shared chunks 3.6 kB 3.64 kB
Total shared by all 104 kB 104 kB
Middleware 92.6 kB 92.6 kB

The recharts weight did not disappear — it moved into async chunks fetched when each dashboard renders. Budget rule (§5) extended: these two routes must not regress above their after-numbers without a measured justification.

9.4 Remaining candidates — §7 re-ranked (this slice's after build)

The §7 baselines drifted +1 kB on several routes between slice 1 and today's builds (intervening session-8/9 merges); the numbers below are from this slice's after build and supersede §7's ranking. The two recharts direct importers are done; the rest need ANALYZE=1 attribution before splitting.

Route First Load JS Attribution status
/boss/realtime 263 kB via components/graph/BossAgentPanel subtree
/visualize/[kernelId] 252 kB via components/visualizations/* subtree
/operations/cell32-optimizer 232 kB via component subtree
/marketing/campaigns 220 kB via components/relu/CampaignLauncher subtree
/marketing/conversions 202 kB —
/marketing/audiences 200 kB —
/relu 197 kB —
/dashboard 192 kB —
/chat 189 kB —
/simulation 184 kB —

9.5 Gates at completion of this slice

9.6 Wave-close quiet-tree confirmation (PENDING)

Reserved for the single authoritative build after BOTH sibling streams land and the tree is quiet (orchestrator-signaled). Before numbers are the orchestrator's baseline log; §9.3's concurrent-tree measurements stand as measured but the cells below are the close-out record.

Route First Load JS before First Load JS after (quiet tree)
/operations/email-intelligence 262 kB 106 kB (route 2.12 kB)
/operations/unified-revenue 253 kB 106 kB (route 2.17 kB)
Total shared by all 104 kB 104 kB

Quiet-tree full-suite rerun: npm test -- --run exit 0 — 91 files / 575 tests, all passed (2026-08-07 wave close; build exit 0, 402 routes).

10. Cache policy (measured, 2026-08-07)

Claim labels: "measured (code)" = read from the tree; "measured (local serve)" = observed on the wire from this slice's built standalone artifact served at 127.0.0.1:3999 (E2E_SKIP_BUILD=1 scripts/e2e-prepare.sh, then node .next/standalone/server.js). No live-infrastructure claims.

10.1 Client cache — React Query (measured, code)

The sole production QueryClient is lib/query-client.tsx, mounted app-wide via app/layout.tsx → components/providers.tsx → QueryProvider (app/journey-intelligence/page.tsx additionally re-mounts the same provider nested — pre-existing, harmless, noted). Global defaults:

Option Value
staleTime 5 min
gcTime 10 min
retry skip 4xx; otherwise up to 3
refetchOnWindowFocus false
refetchOnReconnect true
mutations retry 1

Representative per-hook overrides (measured): hooks/api/useKgReads.ts polls both KG summaries at refetchInterval 60 s with retry: 1; hooks/api/useGovernanceReads.ts polls governance reads at 120 s default with retry: 1; hooks/api/useBOSS.ts sets staleTime per surface from 10 s (queue-ish reads) to 120 s; hooks/agent-ide/useAgentIDE.ts uses 30–60 s staleTime + 60 s polls. Pattern: hooks override freshness per surface; the 5-min global default is the quiet fallback.

10.2 Server emission — route code vs next.config (measured, code + local serve)

10.3 Decision: DOCUMENT-ONLY (criteria measured)

An explicit Cache-Control: no-store at a central BFF emitter was NOT implemented, because both instruction criteria fail: (1) no single central point emits BFF successes — all 31 BFF routes call NextResponse.json inline, so the change would touch >3 files, including the sibling-zone app/api/bff/kg/*; (2) it would change zero wire bytes today — the config catch-all already serves no-cache, no-store, must-revalidate on every API/BFF response (measured, local serve).

Recommendation (recorded for a future slice): the config catch-all is currently the single enforcement point for the no-store posture — do not relax it without first revisiting the 28 route-level values (especially the two public, max-age routes, which would silently become shared-cacheable — a tenant-data risk once auth arms — and the SSE no-transform loss). When a central BFF success emitter is eventually created (One Boss consolidation), bake Cache-Control: no-store into it with a colocated test so the BFF envelope's posture stops depending on the page-cache catch-all.

← All docsView source on GitHub →