islandflow/plans/terminal-extraction-refactor.md
dirtydishes 5d488fd7f5 Add terminal extraction refactor plan
- Document target terminal module layout and dependency rules
- Outline test split, facade contract, and follow-up bd issues
2026-05-08 00:38:54 -04:00

372 lines
11 KiB
Markdown

# Terminal Extraction Plan
## Summary
Refactor [`apps/web/app/terminal.tsx`](/Users/kell/Cloud/dev/islandflow/apps/web/app/terminal.tsx:1) from a single 7,974-line client module into a feature folder at `apps/web/terminal/*`, while keeping `apps/web/app/terminal.tsx` as a temporary compatibility facade in the first pass.
This first extraction is a medium-scope, behavior-preserving refactor:
- no product behavior changes
- no route behavior changes
- no visual redesign
- no data model changes
- no immediate deletion of the old import surface
Current baseline is healthy and must remain healthy after the refactor:
- `bun test apps/web/app/terminal.test.ts apps/web/app/routes.test.ts` passes
- `bun --cwd=apps/web run build` passes
## Target Structure
Create this feature layout:
```text
apps/web/terminal/
index.ts
state.tsx
shell.tsx
routes.tsx
core/
format.ts
filters.ts
route-config.ts
tape-data.ts
signals.ts
live-manifest.ts
hooks/
use-tape-data.ts
use-live-session.ts
use-virtual-tape.ts
components/
chrome.tsx
chart.tsx
drawers.tsx
panes.tsx
tests/
core.test.ts
live-manifest.test.ts
signals.test.ts
tape-data.test.ts
```
Keep this file in place for the first pass:
- `apps/web/app/terminal.tsx`
Its final first-pass role is:
- `"use client"` entrypoint
- thin re-export facade only
- no business logic
- no React state
- no websocket/session logic
- no chart implementation
- target size: under 120 lines
## Dependency Rules
Use this dependency direction and do not violate it:
- `core/*` may depend only on shared types and other `core/*`
- `hooks/*` may depend on `core/*`
- `components/*` may depend on `core/*` and `hooks/*`
- `state.tsx` may depend on `core/*`, `hooks/*`, and `components/*` types only as needed
- `shell.tsx` and `routes.tsx` may depend on `state.tsx` and `components/*`
- `index.ts` re-exports public feature symbols
- `app/terminal.tsx` re-exports from `apps/web/terminal/index.ts`
Do not allow circular imports.
## Module Mapping
Move code out of `terminal.tsx` in this order.
### 1. Pure helpers first
Move non-React helpers into `apps/web/terminal/core/*`:
- `core/route-config.ts`
- `getRouteFeatures`
- `getTapeVirtualConfig`
- `shouldIncludeEquitiesForDarkUnderlyingFallback`
- `core/live-manifest.ts`
- `getLiveManifest`
- `getLiveHistoryRetentionCap`
- `getScopedLiveAutoHydrationChannels`
- `getLiveFeedStatus`
- `getHotChannelFeedStatus`
- `core/tape-data.ts`
- `mergeNewestWithOverflow`
- `composeTapeItems`
- `reducePausableTapeData`
- `flushPausableTapeData`
- `appendHistoryTail`
- `projectPausableTapeState`
- `findAnchorRestoreIndex`
- `shouldRetainLiveSnapshotHistory`
- `shouldShowEquitiesSilentFeedWarning`
- tape/history support types used only by these helpers
- `core/format.ts`
- `formatCompactUsd`
- `formatOptionContractLabel`
- `getOptionTableSnapshot`
- price/size/time/date/contract formatting helpers that support UI rendering
- `core/signals.ts`
- `normalizeAlertSeverity`
- `deriveAlertDirection`
- `getAlertWindowAnchorTs`
- `selectPrimaryClassifierHit`
- `classifierToneForFamily`
- `smartMoneyToneForProfile`
- `smartMoneyProfileLabel`
- `core/filters.ts`
- `buildDefaultFlowFilters`
- `countActiveFlowFilterGroups`
- `toggleFilterValue`
- `nextFlowFilterPopoverState`
These files must not include `"use client"`.
### 2. Extract hooks and session logic
Move React hooks into `apps/web/terminal/hooks/*`:
- `hooks/use-virtual-tape.ts`
- `useListScroll`
- `useScrollAnchor`
- `useVirtualHistoryGate`
- `useTapeVirtualList`
- `hooks/use-tape-data.ts`
- `useTape`
- `usePausableTapeView`
- `useLiveStream`
- `useFlowStream`
- `statusLabel`
- internal tape state types
- `hooks/use-live-session.ts`
- `useLiveSession`
- live history endpoint constants
- live history query builders
- subscription dedupe helpers
- session-local types
Keep signatures stable unless a change is required to break a circular dependency. If a signature changes, update all callers in the same PR.
### 3. Extract UI components
Move rendering code into `apps/web/terminal/components/*`:
- `components/chrome.tsx`
- `TapeStatus`
- `TapeControls`
- `PageFrame`
- `Pane`
- `ShellMetricStrip`
- `FlowFilterPopover`
- local filter UI helpers
- `components/chart.tsx`
- `CandleChart`
- chart-only local types and overlay helpers
- isolate `lightweight-charts` usage here
- `components/drawers.tsx`
- `AlertSeverityStrip`
- `AlertDrawer`
- `ClassifierHitDrawer`
- `SmartMoneyDrawer`
- `DarkDrawer`
- `components/panes.tsx`
- `OptionsPane`
- `EquitiesPane`
- `FlowPane`
- `AlertsPane`
- `ClassifierPane`
- `DarkPane`
- `ChartPane`
- `FocusPane`
- `ReplayConsole`
### 4. Extract state orchestration
Create `apps/web/terminal/state.tsx` for:
- `useTerminalState`
- `TerminalContext`
- `useTerminal`
This file owns:
- route-aware feature selection
- filter input state
- selected entity/drawer state
- scroll-anchor wiring
- assembly of hook outputs into the single terminal state object
Keep `useTerminalState` internal. Do not export it from the feature barrel.
### 5. Extract shell and routes
Create:
- `apps/web/terminal/shell.tsx`
- `TerminalAppShell`
- `apps/web/terminal/routes.tsx`
- `NAV_ITEMS`
- `OverviewRoute`
- `TapeRoute`
- `SignalsRoute`
- `ChartsRoute`
- `ReplayRoute`
Important first-pass rule:
- keep existing route behavior exactly as-is
- [app/page.tsx](/Users/kell/Cloud/dev/islandflow/apps/web/app/page.tsx:1), [app/layout.tsx](/Users/kell/Cloud/dev/islandflow/apps/web/app/layout.tsx:4), and [app/tape/page.tsx](/Users/kell/Cloud/dev/islandflow/apps/web/app/tape/page.tsx:1) may continue importing from `./terminal` / `../terminal`
- [app/signals/page.tsx](/Users/kell/Cloud/dev/islandflow/apps/web/app/signals/page.tsx:1), [app/charts/page.tsx](/Users/kell/Cloud/dev/islandflow/apps/web/app/charts/page.tsx:1), and [app/replay/page.tsx](/Users/kell/Cloud/dev/islandflow/apps/web/app/replay/page.tsx:1) must remain redirect pages in this pass
## Facade Contract
Replace `apps/web/app/terminal.tsx` with a facade that re-exports from `apps/web/terminal/index.ts`.
The facade must continue exporting these symbols in the first pass:
- `getTapeVirtualConfig`
- `shouldIncludeEquitiesForDarkUnderlyingFallback`
- `getRouteFeatures`
- `mergeNewestWithOverflow`
- `composeTapeItems`
- `reducePausableTapeData`
- `flushPausableTapeData`
- `appendHistoryTail`
- `getLiveHistoryRetentionCap`
- `getScopedLiveAutoHydrationChannels`
- `getLiveFeedStatus`
- `getHotChannelFeedStatus`
- `findAnchorRestoreIndex`
- `formatCompactUsd`
- `formatOptionContractLabel`
- `normalizeAlertSeverity`
- `deriveAlertDirection`
- `getAlertWindowAnchorTs`
- `buildDefaultFlowFilters`
- `countActiveFlowFilterGroups`
- `toggleFilterValue`
- `nextFlowFilterPopoverState`
- `projectPausableTapeState`
- `shouldShowEquitiesSilentFeedWarning`
- `shouldRetainLiveSnapshotHistory`
- `selectPrimaryClassifierHit`
- `classifierToneForFamily`
- `smartMoneyToneForProfile`
- `smartMoneyProfileLabel`
- `getOptionTableSnapshot`
- `statusLabel`
- `getLiveManifest`
- `NAV_ITEMS`
- `FlowFilterPopover`
- `TerminalAppShell`
- `OverviewRoute`
- `TapeRoute`
- `SignalsRoute`
- `ChartsRoute`
- `ReplayRoute`
Do not add new facade-only exports.
## Test Plan
Restructure tests so pure logic is tested from its final home instead of through the facade.
### Keep
- `apps/web/app/routes.test.ts`
- still verifies redirect behavior for `/signals`, `/charts`, `/replay`
### Split `app/terminal.test.ts` into feature tests
- `apps/web/terminal/tests/live-manifest.test.ts`
- route feature mapping
- manifest composition
- nav items if still treated as route metadata
- `apps/web/terminal/tests/tape-data.test.ts`
- merge/dedupe logic
- pausable tape behavior
- history seam behavior
- anchor restore behavior
- retention cap behavior
- scoped history behavior
- `apps/web/terminal/tests/core.test.ts`
- option contract formatting
- compact USD formatting
- option table snapshot formatting
- flow filter helpers
- `apps/web/terminal/tests/signals.test.ts`
- alert severity normalization
- direction derivation
- alert window anchor
- classifier/smart-money label and tone helpers
- live status labeling if kept outside tape-data tests
Optional and recommended:
- add one tiny `apps/web/app/terminal-facade.test.ts` that imports the facade and asserts a few critical exports exist, so we notice accidental facade breakage during the transition
## Validation Gates
Implementation is not complete unless all of these pass:
1. `bun test apps/web/terminal/tests apps/web/app/routes.test.ts`
2. `bun --cwd=apps/web run build`
3. Existing behavior smoke check:
- `/` still renders the shell and overview
- `/tape` still renders shell and tape panes
- `/signals`, `/charts`, `/replay` still redirect to `/`
4. `apps/web/app/terminal.tsx` is a facade only and contains no moved logic
5. No extracted pure helper file contains React imports
6. No new circular imports are introduced
## Non-Goals For This Pass
Do not do these in the first extraction:
- redesign panes or drawers
- change websocket or replay behavior
- change route inventory
- remove unused legacy route exports
- change CSS structure beyond import fixes
- optimize bundle size as a separate objective
- rewrite tests to different testing tools
## Beads Follow-Up Issues To File
Create these `bd` issues during implementation if they do not already exist:
1. `task`, priority `2`
Title: `Remove temporary apps/web/app/terminal.tsx facade after terminal imports are migrated`
Description: track deletion of the compatibility facade once route/layout/test imports point at final `apps/web/terminal/*` modules
2. `task`, priority `3`
Title: `Audit and remove dead terminal route exports no longer used by app redirects`
Description: verify whether `SignalsRoute`, `ChartsRoute`, and `ReplayRoute` should be deleted since App Router pages now redirect to `/`
If additional cleanup is discovered during extraction, create linked `bd` tasks with `discovered-from` dependencies rather than expanding this refactor mid-flight.
## Acceptance Criteria
The first extraction is successful when:
- terminal logic is split into the target `apps/web/terminal/*` structure
- `apps/web/app/terminal.tsx` remains only as a thin compatibility layer
- app entrypoints continue to work without behavior changes
- tests target the new module homes for pure logic
- build and tests pass
- follow-up `bd` issues exist for facade removal and dead-export cleanup
## Assumptions And Defaults
- Chosen scope: medium slice, not full architectural rewrite
- Chosen transition: keep `apps/web/app/terminal.tsx` as a temporary facade
- Chosen module home: `apps/web/terminal/*`, not `apps/web/app/terminal/*`
- Default behavior requirement: strict behavioral parity
- Default testing approach: split existing monolithic helper tests by concern and colocate them under `apps/web/terminal/tests`
- Default routing approach: keep redirect pages untouched in the first pass