Suite is now 51 files / 259 tests, 25.68% lines / 21.43% branches.
Floor: lines 24->25, functions 21->22, branches 19->21,
statements 23->24. Inspector/index.tsx ends at 172 LOC, the only
other > 250 LOC file in MazeNET/ is NodeInspector (362) — the
node branch was the bulk of the original 606 LOC and its 7
add-service / tarpit form states stay co-located there.
Drop unused icon/api/useEffect/Tag imports left behind by the
fingerprint, behaviour, and IntelPanel extractions. AttackerDetail.tsx
ends at 450 LOC across Phase 10 (down from 1652 / 73% reduction).
Coverage floor: lines 23->24, functions 20->21, branches 17->19,
statements 22->23.
Credentials.tsx: 487 -> 231 LOC. Page now composes CredsTable +
ReuseTable + useCredentials hook; URL-derived state (tab, query,
service, page) and selection/sort UI are the only concerns left
in the shell.
SwarmHosts.tsx: 513 -> 161 LOC. Page now composes EnrollmentWizard
+ useSwarmHosts hook; only the arm/confirm UI affordance and the
busy-set tracking remain in the shell.
Webhooks.tsx: 642 -> 387 LOC. Page now composes FormRow + SecretModal
+ useWebhooks hook; toast policy is the only UI concern left in the
shell. Multi-select delete uses the hook's reload internally.
PersonaGeneration.tsx: 875 -> 357 LOC. Page now composes the data
hook + PersonaCard + PersonaEditor; bulk-import helpers stay in
helpers.ts; toast policy is the only UI concern left in the shell.
Final integration step. The MazeNET page shell is now a thinner
composition of the existing module-level hooks (useMazeApi,
useMazeInteraction, useTopologyEditor, useTopologyStream,
useLayoutPersistor) PLUS the three new ones from this phase
(useFullscreenMode, useTopologyData, useMazeContextMenu).
- MazeNET.tsx: 980 -> 715 LOC. The fullscreen + body-class
effects, the topology hydrate / SSE stream / deploy /
flashErr plumbing, and the four context-menu builders are
all gone from the shell.
- Page still owns the per-operation editor callbacks
(removeNet/Node/Edge, duplicateNode, addServiceToNode, etc.)
because they need direct access to setNodes/setEdges/setNets
for optimistic patches alongside their REST calls — those
setters are exposed by useTopologyData for that reason.
Coverage floor bumped after the phase:
lines 17 -> 19
functions 15 -> 17
branches 13 -> 14
statements 16 -> 18
Phase 5 final scoreboard: 37 test files, 172 tests, all green.
Final integration. The page shell is now a thin composition of
useConfig + the previously-extracted children:
- Config.tsx: 989 -> 131 LOC. Page owns only the activeTab state
(and the "drop the users tab if the server didn't send users"
effect). Every form lives inside its tab; toast wiring lives
in AppearanceTab; window.alert calls live inside UsersTab.
- Tabs receive their `onSave* / onAddUser / ...` callbacks
directly from the hook — no intermediate wrapper handlers.
Coverage floor bumped after the split:
lines 14 -> 17
functions 13 -> 15
branches 11 -> 13
statements 13 -> 16
Phase 4 final scoreboard: 34 test files, 156 tests, all green.
Final integration step. The page shell is now a thin composition
of useCanaryTokens + the previously-extracted children:
- CanaryTokens.tsx: 1,334 -> 210 LOC. Page owns only the
pure-UI state (tab, search/state/scope filters, modal
visibility, drawer selection, local fileDrops log) and the
thin handlers that translate hook results into confirm/alert
prompts. Initial parallel fetch + deleteBlob mutation moved
to useCanaryTokens in the prior commit.
- Modals plug directly into the hook's optimistic helpers
(prependToken / prependBlob / markTokenRevoked) so the page
doesn't reach into the data shape.
Coverage floor bumped after the split:
lines 11 -> 14
functions 10 -> 13
branches 8 -> 11
statements 11 -> 13
Phase 3 final scoreboard: 28 test files, 131 tests, all green.
Phase 2 lands. DeckyFleet.tsx dropped from 1,674 to 274 LOC; the
fleet page is now a thin composition of useDeckyFleet + 6
extracted children (DeckyInspectPanel, IntervalEditor, DeckyCard,
DeployWizard, DeckyFilters, DeckyGridEmpty), each with co-located
tests.
Lock the gain by bumping the threshold floor in vite.config.ts:
lines 7 -> 11
functions 6 -> 10
branches 5 -> 8
statements 7 -> 11
Phase 2 final scoreboard: 21 test files, 98 tests, all green.
Phase 1 of the UI refactor is in. AttackerDetail dropped from
2,579 LOC inline data + JSX to a 408-LOC shell composed of
extracted sections, each with co-located tests. Lock the gain by
bumping the threshold floor in vite.config.ts:
lines 0 -> 7
functions 0 -> 6
branches 0 -> 5
statements 0 -> 7
Future PRs raise these; never lower. Phase 1 final scoreboard:
9 test files, 45 tests, all green.
Phase 0 of the decnet_web refactor: stand up an MSW server, fixtures,
and a router-aware render helper so the upcoming god-component splits
(AttackerDetail first) can land with same-commit test coverage.
- msw devDep + setupServer wired into src/test/setup.ts
- src/test/server.ts re-exports server, http, HttpResponse, apiUrl()
- src/test/fixtures/{attacker,decky,canary,topology}.ts factories
- src/test/renderWithRouter.tsx wraps MemoryRouter + ToastProvider
- baseline coverage thresholds (0%) in vite.config.ts; raise per PR
- coverage/ added to decnet_web/.gitignore
Existing Orchestrator/AttackerDetail/ThemeLab tests stay on vi.mock
and continue to pass; new tests use MSW.
Wire vitest 4 + jsdom + @testing-library/{react,jest-dom,user-event}
+ @vitest/coverage-v8 through vite.config.ts (defineConfig from
vitest/config). src/test/setup.ts registers jest-dom matchers and
RTL cleanup. tsconfig.app.json picks up vitest/globals types.
Seed suite Orchestrator.test.tsx covers the three regressions
called out in DEBT-043: empty-state render, kind-filter toggling
triggers a scoped refetch, mocked stream callback prepends a row.
Single-bundle build was tripping vite's 500 kB warning per chunk and
forcing every user to re-download the entire app on every deploy.
Manual chunks split the bundle along natural library boundaries so:
- Rarely-changing vendor libs (react-dom, react-router, lucide-react,
asciinema-player) cache across deploys.
- App code lives in its own `index-*.js` that's the only chunk that
changes when we ship feature work.
Split shape (manualChunks fn in vite.config.ts):
- charts — recharts + d3-*
- player — asciinema-player
- icons — lucide-react
- router — react-router / react-router-dom
- react-dom, react
- vendor — everything else in node_modules
Resulting bundle sizes (gzip):
index (app): 246 kB (gz 63)
react-dom: 182 kB (gz 57)
player: 176 kB (gz 65)
router: 42 kB (gz 15)
vendor: 36 kB (gz 14)
icons: 29 kB (gz 10)
Every chunk under the 600 kB ceiling we now set explicitly. The old
~705 kB single-chunk deploy is gone. No code changes — config only.