- Today:
apps/mission-control/src/App.tsxis ~3,745 lines and contains almost all UI + state + data fetching + WebSocket handling. - That is not “slow” at runtime by itself, but it is slow and risky to change: hard to review, easy to break, and guaranteed merge-conflict bait.
- Goal: split it into clean modules (pages, features, hooks, and shared UI) without changing behavior.
- No feature redesign in this refactor.
- No new dependencies required.
- Keep all existing behavior (tabs, boards drag/drop, mail/chatrooms, cockpit layout studio, WS event tail).
- Keep validations green each step:
npm run typecheck,npm run lint,npm run build, andcargo checkinapps/mission-control/src-tauri.
- File:
apps/mission-control/src/App.tsx(~126KB / 3,745 LOC). - Tabs in-file:
boards,calendar,focus,events,mail,chatrooms,cockpit. - Only separate component today:
BoardLane()(and it uses TanStack Virtual). src/components/exists but is empty.- Lint warnings (not errors):
react-hooks/incompatible-libraryarounduseVirtualizer().
Create these directories and move code into them.
apps/mission-control/src/
app/
App.tsx
AppShell.tsx
tabs.ts
useAppController.ts
useGatewayEvents.ts
ui/
Chip.tsx
Surface.tsx
InlineActions.tsx
EmptyState.tsx
utils/
datetime.ts
files.ts
text.ts
features/
boards/
BoardsPage.tsx
BoardLane.tsx
CardDrawer.tsx
boardModel.ts
useBoardsController.ts
calendar/
CalendarPage.tsx
useCalendarController.ts
focus/
FocusPage.tsx
useFocusController.ts
events/
EventsPage.tsx
EventStream.tsx
agentMail/
MailPage.tsx
ChatroomsPage.tsx
ThreadPane.tsx
MessagePane.tsx
ComposeBox.tsx
LeasesPane.tsx
agentMailSummary.ts
useAgentMailController.ts
cockpit/
CockpitPage.tsx
cockpitLayout.ts
widgets/
HealthWidget.tsx
FocusWidget.tsx
BreakersWidget.tsx
JobsWidget.tsx
ChannelsWidget.tsx
ProfilesWidget.tsx
SkillsWidget.tsx
PluginsWidget.tsx
EventsWidget.tsx
Notes:
- Keep
src/lib/*andsrc/types.tsas-is. - If you want fewer files, collapse
calendar/focus/eventsinto a singlefeatures/missionControl/directory.
- Controllers (hooks) own side effects (API calls, timers, WS refresh triggers) and expose “state + actions”.
useAppControllerremains the top-level app-shell controller (tabs + connection/session shell state) and composes feature controllers.useMissionControlControlleris the chosen single controller for Mission Control read models and actions in this refactor wave (calendar + focus + provider/plugin controls) to keep cross-surface orchestration centralized.- Pages are mostly presentational: they render UI, call actions, and keep only tiny UI-local state (input drafts).
- Shared UI primitives go in
src/ui/(buttons can stay as plain<button>for now; focus on structure). - Pure helpers go in
src/utils/or feature-local*Model.tsmodules.
Move these helpers out of App.tsx first (pure functions, no React state):
normalizeWidgetSpan,sanitizeCockpitPages,loadCockpitPagesFromStorage,persistCockpitPagesToStorage->features/cockpit/cockpitLayout.ts.- Board helpers
toCardsByColumn,withUpsertCard,withOptimisticMove->features/boards/boardModel.ts. toInputDateTimeValue,fromInputDateTimeValue,formatDateTime->utils/datetime.ts.fileToBase64,formatBytes->utils/files.ts.truncateText,parsePrincipalCsv->utils/text.ts.buildThreadSummaryNote->features/agentMail/agentMailSummary.ts.
Move these UI blocks into tab pages (minimize behavior change):
{activeTab === "boards" ? (...) : null}->features/boards/BoardsPage.tsx.{activeTab === "calendar" ? (...) : null}->features/calendar/CalendarPage.tsx.{activeTab === "focus" ? (...) : null}->features/focus/FocusPage.tsx.{activeTab === "events" ? (...) : null}->features/events/EventsPage.tsx.{activeTab === "mail" ? (...) : null}->features/agentMail/MailPage.tsx.{activeTab === "chatrooms" ? (...) : null}->features/agentMail/ChatroomsPage.tsx.{activeTab === "cockpit" ? (...) : null}->features/cockpit/CockpitPage.tsx.
Move these “action clusters” into controllers:
- Boards actions (
handleBoardChange,handleDropCard,handleCreateCard,saveCardDraft,runCard,uploadAsset,previewAsset, plusrefreshBoard/queueBoardRefresh) ->features/boards/useBoardsController.ts. - Mission-control read models (
loadMissionControlReadModels,queueMissionControlRefresh,runCalendarJobNow,toggleCalendarJob,resolveFocusApproval,reconnectFocusChannel, provider ordering, plugin/skill toggles) ->app/useMissionControlController.tsor split acrossuseCalendarController,useFocusController. - Agent mail actions (
loadAgentMailReadModels,queueAgentMailRefresh,loadMailThreadById,createMailThread,sendThreadMessage,acknowledgeMessage,acknowledgeRoomUnread,postRoomReaction,summarizeSelectedMailThread,downloadMailAttachment, leases APIs) ->features/agentMail/useAgentMailController.ts. - Cockpit actions (
addCockpitWidget,removeCockpitWidget,moveCockpitWidget,resizeCockpitWidget,resetCockpitLayout,addCockpitPage,exportCockpitLayout,importCockpitLayout) ->features/cockpit/useCockpitController.ts(renderer remains split into widget components).
Move WebSocket wiring to a dedicated hook:
- The
connectGatewayEvents(...)useEffect->app/useGatewayEvents.ts. useGatewayEventsshould accept stable callbacks.useGatewayEventstrigger: event stream append.useGatewayEventstrigger: board incremental updates.useGatewayEventstrigger: “refresh read models” debounced timers.
Each PR should be small enough to review and must keep the app running.
- Optional: add a “no behavior change” checklist to the PR template if you have one.
- Add a short note in
apps/mission-control/README.md(or create one) documentingnpm run typecheck,npm run lint,npm run build, andnpm run tauri:dev.
Acceptance:
npm run typechecknpm run lintnpm run buildcd src-tauri && cargo check
- Create
src/utils/*and move pure helper functions out ofApp.tsx. - Create
features/boards/boardModel.tsandfeatures/cockpit/cockpitLayout.ts. - Keep exports local and update imports in
App.tsx.
Acceptance:
- Same commands as PR 0.
- Move
BoardLanetofeatures/boards/BoardLane.tsx. - Add an explicit lint suppression for TanStack Virtual warnings if desired.
- Use
// eslint-disable-next-line react-hooks/incompatible-libraryonly on theuseVirtualizerline(s).
Acceptance:
- Same commands as PR 0.
- Create page components that accept props (state + handlers) and render the existing JSX.
- Keep the data/state in the original
App.tsxfor this PR to minimize risk. - After this PR,
App.tsxshould be mostly “layout + controller + tab switch”.
Acceptance:
- Same commands as PR 0.
- Introduce
app/useAppController.tsthat composes feature controllers. - Move feature state and actions from
App.tsxintouseBoardsController. - Move feature state and actions from
App.tsxintouseAgentMailController. - Move feature state and actions from
App.tsxintouseCockpitController(optional). - Keep
App.tsxas a thin wrapper:const { state, actions } = useAppController(). - Keep
App.tsxas a thin wrapper:return <AppShell ...>.
Acceptance:
- Same commands as PR 0.
- Move WS logic into
app/useGatewayEvents.ts. - Avoid stale-closure bugs by using
useRefto hold latest handlers, or by passing stable callbacks from controllers. - Ensure cleanup closes the socket.
Acceptance:
- Same commands as PR 0.
- Create
src/ui/*for repeated patterns (chips, surfaces, empty states, action rows). - Keep styles in
styles.cssfor now.
Acceptance:
- Same commands as PR 0.
App.tsx<= ~200 lines and contains no large JSX blocks.- Each tab is its own file, and each feature owns its own controller hook.
- No behavior changes: board drag/drop still works.
- No behavior changes: board card drawer editing, run, upload/preview assets still works.
- No behavior changes: calendar/focus/events render the same.
- No behavior changes: mail and chatrooms still load threads/messages, send messages, handle attachments, leases.
- No behavior changes: cockpit pages still persist to localStorage and widgets render correctly.
- Repo is “lint clean” or warnings are explicitly documented and localized.
- Switch board drag/drop from native HTML DnD to
@dnd-kit/*(already installed) for better cross-platform behavior. - Add component tests (React Testing Library): board move optimistic update.
- Add component tests (React Testing Library): mail thread selection + compose.
- Add component tests (React Testing Library): cockpit layout persistence.