Symptom
useWorkspace() (src/renderer/src/workspace/hook/index.ts:100) selects the entire workspaceRuntimes map and returns a fresh unmemoised object every render (hook/index.ts:897-1021 → App.tsx:87 → WorkspaceContext.tsx:11-17). Every setRuntimes (app-state/workspace/slice.ts:48-51, new map identity per screen/semantic/jsonl message) re-executes App → MainSurface → GlobalEditorShell → TileTree → every TileLeaf / AgentTerminalLeaf / TerminalLeaf / PaneHeader (none memo), including hidden tiled tabs and the display:none retained tree; PaneHeader receives the entire map (TileLeaf.tsx:795). Only Feed (Feed.tsx:294, memo) bails. Live capture: the two renderers burn 55–75% of a core against main's 13–35%; renderer long tasks p99 7.4 s.
Three effects also run over ALL sessions on every replacement: useFeedDebugPersist (paced by #750), usePickerSanity (hook/invalidation/effects.ts:126-138), the codex transcript observation outbox (lifecycle/codexTranscriptObservationOutbox.ts:184-236); getRuntime returns a new identity per call (helpers.ts:87-92).
Expected
- Panes subscribe to
state.workspaceRuntimes[sessionId] with useShallow (the pattern already used in DispatchAgentList.tsx:298-305); memo on TileTree, TileLeaf, AgentTerminalLeaf, TerminalLeaf, PaneHeader; narrow PaneHeader's selector; split the workspace context into stable actions and state slices.
- The three all-session effects move to per-session subscriptions or a 1 s timer.
- Migrate consumer by consumer (~25 consumers rely on the fresh object today).
Impact
Re-renders per frame N panes → 1; estimated renderer CPU −30–50% in multi-pane workspaces. Prerequisite for the screen-interest work to pay off fully.
Refs #103, #390. Source: architecture audit 2026-09-03 (scratchpad/arch-perf-audit.md).
Symptom
useWorkspace()(src/renderer/src/workspace/hook/index.ts:100) selects the entireworkspaceRuntimesmap and returns a fresh unmemoised object every render (hook/index.ts:897-1021→App.tsx:87→WorkspaceContext.tsx:11-17). EverysetRuntimes(app-state/workspace/slice.ts:48-51, new map identity per screen/semantic/jsonl message) re-executesApp→MainSurface→GlobalEditorShell→TileTree→ everyTileLeaf/AgentTerminalLeaf/TerminalLeaf/PaneHeader(nonememo), including hidden tiled tabs and the display:none retained tree;PaneHeaderreceives the entire map (TileLeaf.tsx:795). OnlyFeed(Feed.tsx:294, memo) bails. Live capture: the two renderers burn 55–75% of a core against main's 13–35%; renderer long tasks p99 7.4 s.Three effects also run over ALL sessions on every replacement:
useFeedDebugPersist(paced by #750),usePickerSanity(hook/invalidation/effects.ts:126-138), the codex transcript observation outbox (lifecycle/codexTranscriptObservationOutbox.ts:184-236);getRuntimereturns a new identity per call (helpers.ts:87-92).Expected
state.workspaceRuntimes[sessionId]withuseShallow(the pattern already used inDispatchAgentList.tsx:298-305);memoonTileTree,TileLeaf,AgentTerminalLeaf,TerminalLeaf,PaneHeader; narrowPaneHeader's selector; split theworkspacecontext into stable actions and state slices.Impact
Re-renders per frame N panes → 1; estimated renderer CPU −30–50% in multi-pane workspaces. Prerequisite for the screen-interest work to pay off fully.
Refs #103, #390. Source: architecture audit 2026-09-03 (scratchpad/arch-perf-audit.md).