refactor: decompose oversized frontend files into tested helpers #7

Merged
dries merged 6 commits from chore-fe into main 2026-05-05 08:55:52 +02:00
Owner

Summary

Implements the bulk of spec/frontend-refactor/ — extracts pure
helpers, types, and SSE / message-conversion logic out of the five
largest frontend files into focused, fully-tested lib/ modules.
No behaviour change; every consumer keeps importing from the same
paths via re-exports where needed.

What landed

  • Phase 1: shared useSyncRef, sseMessageHelpers,
    sessionStatus, taskId, mutedTools, sidebarHelpers.
  • Phase 2: threadHelpers, convertMessages, sseHelpers,
    plus composer pure modules (encodeWav, contextWindows,
    builtinCommands).
  • Phase 3: api.ts types split out into api.types.ts
    with re-exports for back-compat.
  • Phase 4.1: SessionDetail.tsx relocated to
    pages/session-detail/ with an index.tsx re-export.
  • Phase 6: chartConfig.ts + format.ts extensions
    (renderModel, shortSessionID) pulled out of Dashboard.tsx.
  • Phase 7: every stub-only test replaced with real behavioural
    coverage via a shared React + DebouncedTrigger mock harness.

File-size deltas

File Before After Δ
SessionDetail.tsx 3 574 3 096 −478
Composer.tsx 1 374 1 273 −101
OcmanRuntimeProvider.tsx 701 107 −594
AssistantThread.tsx 1 215 913 −302
api.ts 1 197 606 −591
Dashboard.tsx 1 303 1 197 −106

Total frontend tests: 345 → 658 (+313).

Verification

make test, make lint, make build all pass.

Deferred to follow-ups

  • Phase 4.2–4.11 (11 SessionDetail hooks) and Phase 5
    (7 Composer hooks) are out of scope for this PR. Those
    extractions touch deeply-intertwined state (45 useState /
    43 useEffect in SessionDetail alone) where stale-closure or
    dep-array bugs are easy to introduce and hard to catch without
    @testing-library/react (forbidden by NFR-3) or a manual
    smoke-test pass. The biggest mechanical wins — pure-function
    extraction, type split, helper deduplication — have all
    landed and are unit-tested. The hook decomposition is a clean
    follow-up once an integration-test approach is decided.
  • FR-9 Dashboard per-tab split: chart options + format
    helpers are out, the per-tab file split (StatsTab.tsx,
    UsageTab.tsx, …) is deferred.
  • FR-10 useSyncRef is shipped + tested; adopting it across
    the 25+ existing ref-sync sites is deferred.

See spec/frontend-refactor/ (added in this PR) for the full
requirements + architecture documents.

## Summary Implements the bulk of `spec/frontend-refactor/` — extracts pure helpers, types, and SSE / message-conversion logic out of the five largest frontend files into focused, fully-tested `lib/` modules. No behaviour change; every consumer keeps importing from the same paths via re-exports where needed. ## What landed - **Phase 1**: shared `useSyncRef`, `sseMessageHelpers`, `sessionStatus`, `taskId`, `mutedTools`, `sidebarHelpers`. - **Phase 2**: `threadHelpers`, `convertMessages`, `sseHelpers`, plus composer pure modules (`encodeWav`, `contextWindows`, `builtinCommands`). - **Phase 3**: `api.ts` types split out into `api.types.ts` with re-exports for back-compat. - **Phase 4.1**: `SessionDetail.tsx` relocated to `pages/session-detail/` with an `index.tsx` re-export. - **Phase 6**: `chartConfig.ts` + `format.ts` extensions (`renderModel`, `shortSessionID`) pulled out of `Dashboard.tsx`. - **Phase 7**: every stub-only test replaced with real behavioural coverage via a shared React + DebouncedTrigger mock harness. ## File-size deltas | File | Before | After | Δ | |----------------------------|-------:|------:|-------:| | `SessionDetail.tsx` | 3 574 | 3 096 | −478 | | `Composer.tsx` | 1 374 | 1 273 | −101 | | `OcmanRuntimeProvider.tsx` | 701 | 107 | −594 | | `AssistantThread.tsx` | 1 215 | 913 | −302 | | `api.ts` | 1 197 | 606 | −591 | | `Dashboard.tsx` | 1 303 | 1 197 | −106 | Total frontend tests: **345 → 658** (+313). ## Verification `make test`, `make lint`, `make build` all pass. ## Deferred to follow-ups - **Phase 4.2–4.11** (11 SessionDetail hooks) and **Phase 5** (7 Composer hooks) are out of scope for this PR. Those extractions touch deeply-intertwined state (45 `useState` / 43 `useEffect` in SessionDetail alone) where stale-closure or dep-array bugs are easy to introduce and hard to catch without `@testing-library/react` (forbidden by NFR-3) or a manual smoke-test pass. The biggest mechanical wins — pure-function extraction, type split, helper deduplication — have all landed and are unit-tested. The hook decomposition is a clean follow-up once an integration-test approach is decided. - **FR-9** Dashboard per-tab split: chart options + format helpers are out, the per-tab file split (`StatsTab.tsx`, `UsageTab.tsx`, …) is deferred. - **FR-10** `useSyncRef` is shipped + tested; adopting it across the 25+ existing ref-sync sites is deferred. See `spec/frontend-refactor/` (added in this PR) for the full requirements + architecture documents.
Pulls SSE message merge/insert/upsert/status logic, session status
derivation, task-ID extraction, muted-tool lists, and sidebar
hash/rollup out of SessionDetail.tsx, AssistantThread.tsx and
OcmanRuntimeProvider.tsx into focused, fully-tested lib/ modules.

Also introduces useSyncRef as the canonical replacement for the
useRef + useEffect ref-sync boilerplate that recurs throughout
SessionDetail and Composer.

Adds spec/frontend-refactor/ (requirements + architecture).

No behaviour change. SessionDetail drops from 3 574 to 3 404 lines;
total frontend test count rises from 345 to 432 (+87).
Lifts the 14 thread-rendering helpers, the 497-line convertMessages
pipeline, the SSE event extractors, the WAV encoder, the model
context-window table, and the slash-command/agent built-ins into
focused, fully-tested lib/ modules.

Each consumer (AssistantThread, OcmanRuntimeProvider, SessionDetail,
Composer) now imports the helpers it needs instead of redefining them
inline. No behaviour change.

Per-file deltas:
- AssistantThread.tsx        1 215 → 913   (-302)
- OcmanRuntimeProvider.tsx     701 → 107   (-594)
- SessionDetail.tsx          3 404 → 3 096 (-308)
- Composer.tsx               1 374 → 1 273 (-101)

Total frontend tests: 432 → 612 (+180).
Moves the 50 wire-shape interfaces out of frontend/src/lib/api.ts
into a dedicated frontend/src/lib/api.types.ts module. api.ts now
contains only the AuthError class, fetchJSON / postJSON helpers,
and the api command object; it re-exports every type from the
new module so existing imports of '../lib/api' continue to work.

api.ts: 1 197 → 606 lines (-591)
api.types.ts: new, 686 lines

No behaviour change. 612 tests still pass.
Moves frontend/src/pages/SessionDetail.{tsx,css} into a dedicated
pages/session-detail/ directory and adds an index.tsx re-export so
imports of '../pages/SessionDetail' keep resolving. Updates the
single consumer (App.tsx) to import from the new directory path.

Sets up the location for the per-page hooks extracted in the
remaining Phase 4 steps.
Pulls the 11 chart-options constants, the categorical / stop-reason
colour palettes, and the renderModel / shortSessionID display
helpers out of Dashboard.tsx into focused, tested lib/ modules:

- lib/chartConfig.ts: BAR_OPTIONS_*, LINE_OPTIONS_*, DOUGHNUT_OPTIONS,
  CHART_X_TICKS, CHART_COLORS, STOP_REASON_COLORS.
- lib/format.ts: gains renderModel + shortSessionID alongside the
  existing formatters.

Dashboard.tsx imports the new constants instead of declaring them
inline. The tab-component split (StatsTab.tsx / UsageTab.tsx /
ProjectsTab.tsx / SettingsTab.tsx / SessionsTab.tsx) is deferred
to a follow-up PR — the per-tab files are already exported, so
moving them is a mechanical change once the dashboard data flow
settles.

Dashboard.tsx: 1 303 → 1 197 lines (-106). Tests: 612 → 626 (+14).
test: replace stub tests with real behavioural coverage (Phase 7)
All checks were successful
CI / Frontend (pull_request) Successful in 1m30s
CI / Backend (pull_request) Successful in 2m3s
CI / Playwright E2E (pull_request) Successful in 3m29s
CI / Build (pull_request) Successful in 2m2s
CI / Semantic Tag (pull_request) Has been skipped
048a5920f4
The five stub-only test files now exercise actual hook behaviour
through a shared React + DebouncedTrigger mock harness:

- useCapabilities.test.ts:    network dedup, lazy-init cache,
                              per-platform fallback, multi-platform
                              detection, worktree-sessions flag.
- useSessionInfo.test.ts:     EMPTY_INFO fallback, initial fetch,
                              abort on cleanup, refresh() flush.
- useSessionChanges.test.ts:  same shape, applied to file-changes.
- useWorkingTreeDiff.test.ts: same shape, applied to git-diff.
- useInfiniteRows.test.ts:    initial-clamp math, hasMore derivation,
                              no-IO graceful degradation, sentinel
                              intersection bumps visibleCount.

Total frontend tests: 626 → 658 (+32).
make test / make lint / make build all pass.
dries merged commit d435d89195 into main 2026-05-05 08:55:52 +02:00
dries deleted branch chore-fe 2026-05-05 08:55:52 +02:00
Sign in to join this conversation.
No description provided.