You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
src/workstation/surfaces/workspace/runtime.ts is a 900-LOC file that mixes the Ink boot sequence, the diagnostic ring buffer, the non-TTY snapshot fallback, the React component, and six async workflow callbacks. Splitting it into focused modules makes each piece testable in isolation and brings the file closer to src/workstation/runtime/app.ts's shape.
Snapshot fallback: renderWorkspaceSnapshot (~25 LOC) — used in non-TTY environments.
Debug ring buffer: WORKSPACE_DEBUG_BUFFER, flushWorkspaceTrace, installWorkspaceDebugHandlers, workspaceDebug, plus six process.on handlers (~70 LOC, lines 295-374).
The Ink input handler with the modal-focus dispatch.
The mount-effect, refresh, confirmDelete's refresh-tail, and commitAddRepo's refresh-tail all repeat the same pattern: set-loading → load → replace-overview → write-cache → load-prs (with onRepoComplete) → set-status. That's ~120 LOC of repetition.
Proposed split
runtime/
index.ts — re-exports startWorkspace / WorkspaceExitResult / WorkspaceResumeState
app.tsx — WorkspaceInkApp (~300 LOC after extraction)
boot.ts — startWorkspace + loadWorkspaceInkRuntime
snapshot.ts — renderWorkspaceSnapshot (non-TTY path)
debugTrace.ts — ring buffer + flushWorkspaceTrace + process handlers
workflows.ts — Six async callbacks rewritten as pure functions:
(deps, state) => Promise<WorkspaceAction[]>
Tested without React. Each returns the list of
actions the component should dispatch.
refreshCycle.ts — Shared "discover + PR fetch with per-repo callback"
helper. Eliminates the ~120 LOC duplication.
The eight useRef mirrors disappear once workflows are factored out — they only exist because the component owns async work and needs to dodge React's stale-closure trap.
Acceptance criteria
runtime.ts is gone (or shrinks to a single-line re-export from runtime/index.ts).
WorkspaceInkApp is under 350 LOC.
Each workflow (refresh, refreshRow, drillIn, requestDelete, confirmDelete, commitAddRepo, openAddRepo) is a pure function with its own unit tests (no React, no Ink).
The shared refreshCycle helper is tested independently.
Debug trace module has its own test (file write on flush, ring-buffer bound at 500).
Public API at src/workstation/surfaces/workspace/index.ts unchanged — startWorkspace, WorkspaceExitResult, WorkspaceResumeState still re-exported with identical shapes.
No snapshot drift (the rendered tree is identical).
Full Jest suite still passes.
Manual TTY check: drill-in loop, refresh cycle, add-repo, remove-repo, debug log all still work.
Files affected
src/workstation/surfaces/workspace/runtime.ts (split into the new directory layout)
src/workstation/surfaces/workspace/runtime.test.ts (likely splits to match new module boundaries)
src/commands/workspace/handler.ts (imports workspaceDebug from runtime.ts; will need to follow the new path)
Risk
High. This is the riskiest item on the audit punch list. The runtime is the integration point between the input system, the reducer, Ink, async workflows, and the lifecycle handlers. A subtle regression here (missed dispatch, double-fire on a workflow, lifecycle handler not installed) is hard to catch without manual TTY testing.
The ring-buffer + signal handlers are global side effects — moving them needs care to avoid double-installation if a future caller starts two workspaces in one process.
The drill-in loop's process.exit(0) from commands/workspace/handler.ts already shapes the lifecycle; the split mustn't change the exit semantics.
Effort estimate
~4 hours including test refactoring + manual TTY verification.
Why defer
The current runtime works. It's ugly but stable — 191 suites pass, 13 PRs of polish are landed, and we just shipped to users.
Memoization (Memoize re-render scope in the workspace Ink runtime #1079) overlaps with the component split here — both want to break WorkspaceInkApp into smaller pieces. Picking one up should probably coordinate with the other to avoid two consecutive disruptive PRs against the same file.
Best timing: when we're about to add another major workflow to the surface (e.g., cross-repo commit / multi-select / shared workspace cache). The split pays off when we have a new thing to integrate cleanly.
Coordination
When this is picked up, do it BEFORE or COMBINED WITH:
Summary
src/workstation/surfaces/workspace/runtime.tsis a 900-LOC file that mixes the Ink boot sequence, the diagnostic ring buffer, the non-TTY snapshot fallback, the React component, and six async workflow callbacks. Splitting it into focused modules makes each piece testable in isolation and brings the file closer tosrc/workstation/runtime/app.ts's shape.Current state
runtime.tscontains:loadWorkspaceInkRuntime(~10 LOC),startWorkspace(~80 LOC).renderWorkspaceSnapshot(~25 LOC) — used in non-TTY environments.WORKSPACE_DEBUG_BUFFER,flushWorkspaceTrace,installWorkspaceDebugHandlers,workspaceDebug, plus sixprocess.onhandlers (~70 LOC, lines 295-374).WorkspaceInkApp): ~500 LOC. Inside it:refresh,refreshRow,drillIn,requestDelete,confirmDelete,commitAddRepo,openAddRepo.useRefmirrors so the stableuseInputhandler can reach the latest closure.useEffectblocks: mount discovery, spinner tick, resume ref, preferences persistence.The mount-effect,
refresh,confirmDelete's refresh-tail, andcommitAddRepo's refresh-tail all repeat the same pattern:set-loading → load → replace-overview → write-cache → load-prs (with onRepoComplete) → set-status. That's ~120 LOC of repetition.Proposed split
The eight
useRefmirrors disappear once workflows are factored out — they only exist because the component owns async work and needs to dodge React's stale-closure trap.Acceptance criteria
runtime.tsis gone (or shrinks to a single-line re-export fromruntime/index.ts).WorkspaceInkAppis under 350 LOC.refreshCyclehelper is tested independently.src/workstation/surfaces/workspace/index.tsunchanged —startWorkspace,WorkspaceExitResult,WorkspaceResumeStatestill re-exported with identical shapes.Files affected
src/workstation/surfaces/workspace/runtime.ts(split into the new directory layout)src/workstation/surfaces/workspace/runtime.test.ts(likely splits to match new module boundaries)src/workstation/surfaces/workspace/index.ts(re-export paths)src/commands/workspace/handler.ts(importsworkspaceDebugfromruntime.ts; will need to follow the new path)Risk
process.exit(0)fromcommands/workspace/handler.tsalready shapes the lifecycle; the split mustn't change the exit semantics.Effort estimate
~4 hours including test refactoring + manual TTY verification.
Why defer
WorkspaceInkAppinto smaller pieces. Picking one up should probably coordinate with the other to avoid two consecutive disruptive PRs against the same file.Coordination
When this is picked up, do it BEFORE or COMBINED WITH:
WorkspaceInkAppinternals.Sourced from the post-#880 workspace surface audit.