Repository navigation
refactor: App.tsx god render-root → thin composition root + surface registry (#494) - #505
Merged
Merged
Conversation
…n plan (#494) 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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #494.
What
src/renderer/src/app/App.tsxgoes 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
app/surfaces/registry.tsxaggregates self-contained wrappers (one file per surface, living in the owning feature'ssurfaces/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).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).useThemeSync,useAiWorkspaceOpenRequests,useDevDebugConfigSync,useCaffeinateSync,useDictationHotkeySync,useRenderedLeaseHygiene,useDebugAutosave, plus the path-picker prefill effect moving intoPathPickerSurface.agentViewModePickerSessionId→ uiShell slice; caffeinate status/message →features/caffeinate/store.ts; dev-debug config →features/debug/devDebugConfig.ts.features/debug/surfaces/DebugSurfaces.tsx): not fetched until a panel flag first turns on, so debug code leaves the prod tree and initial bundle.app/shell/):RestoreBanner,SettingsBar,MainSurface(mode routing incl. GlobalEditorShell + placement overlay),WelcomeEmpty; barrel re-exportsSetupGateso App has no feature-surface imports.Reviewer attention points (deliberate decisions, documented in the plan/design comments)
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.DebugSurfaces.tsxif wanted.position:fixedwith 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.npm run test— 474/474 tests pass. Two file-level failures exist but are unrelated to this branch (zero changes underpackages/):codex-headless JsonlTailer.test.tsis flaky under parallel load (passes in isolation), andagent-voice-dictation hotkeyBinding.test.tscontains no test suite in the pinned submodule (reproduces outside this branch).wc -l App.tsx= 74; nouseEffect/useStatein App.tsx; no leftover effect bodies (configureDictationHotkey|autosaveActiveAgentDebugBundles|getCaffeinateStatus|remoteSetThemeSettings|onAiWorkspaceOpenRequest) underapp/.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/feedinternals). This PR deliberately touches none of those files; the only overlap risk is App-adjacent import lines (e.g.AppearanceMenunow imported fromapp/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