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

11 KiB

Terminal Extraction Plan

Summary

Refactor apps/web/app/terminal.tsx 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:

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:

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