From 5d488fd7f5045d024f3b31649348fdf2f5c8eabd Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Fri, 8 May 2026 00:38:54 -0400 Subject: [PATCH] Add terminal extraction refactor plan - Document target terminal module layout and dependency rules - Outline test split, facade contract, and follow-up bd issues --- plans/terminal-extraction-refactor.md | 372 ++++++++++++++++++++++++++ 1 file changed, 372 insertions(+) create mode 100644 plans/terminal-extraction-refactor.md diff --git a/plans/terminal-extraction-refactor.md b/plans/terminal-extraction-refactor.md new file mode 100644 index 0000000..6125127 --- /dev/null +++ b/plans/terminal-extraction-refactor.md @@ -0,0 +1,372 @@ +# 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