Skip to content

refactor: App.tsx god render-root → thin composition root + surface registry (#494) - #505

Merged
Juliusolsson05 merged 14 commits into
mainfrom
refactor/app-shell-composition-root
Jul 9, 2026
Merged

Juliusolsson05 merged 14 commits into
mainfrom
refactor/app-shell-composition-root

Conversation

@Juliusolsson05

Copy link
Copy Markdown
Owner

Closes #494.

What

src/renderer/src/app/App.tsx goes from a 1152-line god render-root (52 imports, ~34 directly-mounted feature surfaces, 13 root effects, a 70-prop CommandPalette relay) to a 74-line composition root: named cross-cutting hooks → useWorkspace + WorkspaceProvider → layout shell → registry-driven surface groups. Pure extraction — no behavior change intended; every effect body and WHY comment moved verbatim.

How it's structured now

  • Surface registry — app/surfaces/registry.tsx aggregates self-contained wrappers (one file per surface, living in the owning feature's surfaces/ folder) into three groups placed at App's exact old DOM positions: <GlobalModals/>, <GlobalOverlays/>, <SidePanels/>. Adding a surface = 1 wrapper file + 1 registry line; App.tsx is never edited. Mirrors the command-registry pattern rather than side-effect self-registration (grep-able, compiler-checked).
  • WorkspaceContext (workspace/WorkspaceContext.tsx) — first real workspace context; wrappers consume it instead of prop-drilling. Same re-render semantics as the old props (value is the per-render hook object; documented in the file).
  • CommandPalette is self-contained — the ~76-prop relay is deleted; the palette sources store actions/flags, workspace context, and the caffeinate/dev-debug stores itself. CommandContext's shape and every command definition are untouched (keeps this disjoint from Plug-and-play provider architecture: modularize and standardize all provider implementations #394 §7's future command rewrite).
  • 13 root effects → feature hooks: useThemeSync, useAiWorkspaceOpenRequests, useDevDebugConfigSync, useCaffeinateSync, useDictationHotkeySync, useRenderedLeaseHygiene, useDebugAutosave, plus the path-picker prefill effect moving into PathPickerSurface.
  • New homes for App-local state: agentViewModePickerSessionId → uiShell slice; caffeinate status/message → features/caffeinate/store.ts; dev-debug config → features/debug/devDebugConfig.ts.
  • Debug panels → one lazy chunk (features/debug/surfaces/DebugSurfaces.tsx): not fetched until a panel flag first turns on, so debug code leaves the prod tree and initial bundle.
  • Shell pieces (app/shell/): RestoreBanner, SettingsBar, MainSurface (mode routing incl. GlobalEditorShell + placement overlay), WelcomeEmpty; barrel re-exports SetupGate so App has no feature-surface imports.

Reviewer attention points (deliberate decisions, documented in the plan/design comments)

  1. No when(state) on registry entries (deviation from the issue sketch): mount semantics are load-bearing — modals stay always-mounted-with-open (internal state survives close/reopen), panels stay conditionally mounted. Each wrapper replicates its exact old gating.
  2. Debug surfaces are NOT hard-gated on dev-debug (issue sketch said so, but 4 of the 5 panels open without dev-debug today and this refactor is behavior-preserving). The lazy chunk delivers the "out of the prod tree" goal; a hard gate is a one-line follow-up in DebugSurfaces.tsx if wanted.
  3. Three fixed-position siblings changed DOM order (TiledDispatchCount overlay + caffeinate toast now render in the overlays group before the modals group; palette is first modal entry). All are position:fixed with explicit z-index; needs one eyeball in the manual pass below.

Gates (all run per-task, 12 commits)

  • npx tsc -b (node + web projects) — clean at every commit.
  • npm run test:renderer — 14/14 green at every commit.
  • Full npm run test — 474/474 tests pass. Two file-level failures exist but are unrelated to this branch (zero changes under packages/): codex-headless JsonlTailer.test.ts is flaky under parallel load (passes in isolation), and agent-voice-dictation hotkeyBinding.test.ts contains no test suite in the pinned submodule (reproduces outside this branch).
  • Grep gates: wc -l App.tsx = 74; no useEffect/useState in App.tsx; no leftover effect bodies (configureDictationHotkey|autosaveActiveAgentDebugBundles|getCaffeinateStatus|remoteSetThemeSettings|onAiWorkspaceOpenRequest) under app/.

Manual pass checklist (recommended before merge)

Open each once via palette/keybind: command palette, path picker (new tab ⌘T + resume), tile-tabs, reorder-tabs, pin-agents (pinned-first ordering), bury prompt, debug-bundle note, recording note, view prompts, prompt search, agent activity, close-old-agents, bulk provider switch, agent view-mode picker, rewind-to-prompt, usage modal, tiled-dispatch count, voice dictation, caffeinate toast (auto-dismiss 5s), git bar, worktrees bar, agent status panel, remote panel, 4 debug panels without dev-debug + dev-debug panel with it, Settings/Reader/Spotlight/Global editor (editor state survives mode flips), placement overlay (Esc clears all three flows), theme flip (desktop + remote match), keybind spot-check (⌘T, ⌘1..9, ⌥h/j/k/l, dispatch chords).

#493 coexistence

Another branch is restructuring the feed layers (workspace/semantic, rendering/, features/feed internals). This PR deliberately touches none of those files; the only overlap risk is App-adjacent import lines (e.g. AppearanceMenu now imported from app/shell/SettingsBar.tsx). Whichever lands second rebases trivially.

Plan: docs/superpowers/plans/2026-07-09-app-shell-composition-root.md (committed on this branch).

🤖 Generated with Claude Code

Juliusolsson05 and others added 14 commits July 9, 2026 10:11
…n plan (#494)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…494)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…#494)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#494)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…elay (#494)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r, delete laundering barrel, fix stale perf comment

1. Paint-order regression (MINOR): the tiled-dispatch count prompt and
   caffeinate toast had moved into the overlays group, which renders
   BEFORE the modals group — reversing pre-refactor DOM order for
   same-z-50 fixed siblings and burying the count prompt behind the
   command palette (which deliberately stays open). Both entries now
   sit in modalSurfaces at their exact pre-refactor positions (after
   path-picker, before tile-tabs), restoring byte-exact old paint
   order: above palette/path-picker, below the tile-tabs..usage
   modals. Registry + App.tsx now document that mount order IS the
   z-order contract.

2. Dependency laundering (MINOR): app/shell/index.ts re-exported
   SetupGate so App could claim zero @renderer/features/* imports —
   false, since App imports six feature hooks directly. Barrel
   deleted; App imports the three shell pieces and SetupGate
   directly, and the header comment now states the real invariant
   (no feature SURFACES mounted directly; feature hooks are fine).

3. Stale comment (NIT): PinAgentsSurface claimed the moved row memo
   recomputes less often — false; the wrapper is always mounted and
   re-renders with App via WorkspaceContext, deps unchanged. Comment
   now describes the real benefit (cohesion), not a perf win.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Juliusolsson05
Juliusolsson05 merged commit 2b7cb86 into main Jul 9, 2026
@Juliusolsson05
Juliusolsson05 deleted the refactor/app-shell-composition-root branch July 9, 2026 09:57
Juliusolsson05 added a commit that referenced this pull request Jul 9, 2026
App.tsx conflict: this branch predates #505's thin-composition-root
refactor — took main's App.tsx wholesale and re-applied the #508 toast
surfacing into features/voice-dictation/useDictationHotkeySync.ts, where
the dictation-configure effect lives post-#505. Same behavior, new home.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Juliusolsson05 added a commit that referenced this pull request Jul 9, 2026
App.tsx conflict: branch predates #505's thin composition root — took
main's App.tsx and re-applied the rawCapture wiring into
features/debug/devDebugConfig.ts's one-shot hydrate, where the dev-debug
probe lives post-#505. Same behavior, new home.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Juliusolsson05 added a commit that referenced this pull request Jul 28, 2026
…cut)

WS7's substantive content — custom feed/tool renderers — was cut (Decision 5;
extension tool output uses standard MCP rendering). What remains is the surface
ordering contract, and extension modal/panel views already ride the app-host slot
(WS4) and the pane path (WS6). This adds the one hardening the plan called for.

SurfaceEntry gains an optional `layer` band, and GlobalModals stably sorts by it
before render. Every first-party entry omits `layer` (→ 0), so the stable sort
leaves their documented, load-bearing order exactly as-is — nothing about today's
paint order changes. The point is forward safety: an extension-contributed surface
gets EXTENSION_SURFACE_LAYER (a distinct band above the first-party stack) so it can
never tie-break INTO the first-party z-50 stack and silently reorder it — the exact
class of bug PR #505 hit when semantic grouping fought paint order. First-party
surfaces are documented not to set the field.

This is additive and behavior-preserving; a contributed surface has no producer yet
(extensions ride app-host), so the band is the contract, ready for when one lands.

Verified: tsc -p tsconfig.web.json --noEmit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor App.tsx god render-root into a thin composition root + surface registry

1 participant