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
#3439 was closed when the R1 foundation (#4088, corrected by #4249) merged, with 9 of its 12 checklist items still open. Four controller migrations (#4315, #4316, #4491, #4498) and the Usage slice (#4425 / #4440) currently reference a closed issue. This issue is the successor and carries the remaining work.
The definition of done changes, following the evidence Astro-Han posted on #3439 and #4109: extracting a feature into features/<name>/ does not change the scope of any state. A slice is done when
none of its hooks is called in the render body of AppShell or AppShellContent (the entry disappears from scripts/check-app-shell-hooks.mjs, or shrinks to a single equality-selected read that the shell genuinely needs);
its window.maka.* paths are gone from app-shell.tsx (the bridgePaths entry in renderer-architecture.json shrinks);
the same PR deletes the prop threading and helpers the move makes redundant.
Not a fifth condition, but a convention worth following where it is cheap (Astro-Han, below): a migrated slice is testable without Electron — its ports.ts names the capabilities and testing.ts ships fakes — so when a slice moves, move the E2E coverage that only exists because the behaviour lived in the shell into the slice's own tests in the same PR. goals (#4316) is the finished example; features/ has 12 slices and no test files yet. This also orders the list: WorkHub router unlocks workhub-layout and workhub-reconstruction, and Conversation host covers streaming-remount (where #4664 sits). Layering background: #4727.
For reference, app-shell.tsx had 23 bridge references when #3439 opened; the five extraction PRs did not stop new product work (#3427, #3497, #3635, #3764, #3798) from landing bridge calls in the shell. The R1 ratchet has held the count since 2026-08-30.
Progress
2026-09-03 (6c8e749d3)
2026-09-05 (cbeb1a93c)
app-shell.tsx
3,324 lines, 105 imports
3,293 lines, 101 imports
hooks in AppShell + AppShellContent
42 hooks / 78 call sites
41 hooks / 77 call sites
window.maka.* paths in app-shell.tsx
32 distinct / 33 references
32 distinct / 33 references
registered controllerOwners
none
useModuleHubController (#4491 and #4498 add Task Entry and App Update)
feature slices
9
12 (usage from #4440; conversation and network-proxy landed from other work)
Session Navigation residue — useSessionNavigationReads (a selector read; keep only if the shell body still needs it after Conversation/Session controls move)
Shell-owned state that has no slice yet
App Update — inline useEffect/useState; bridge app.installUpdate, app.retryUpdateDownload, app.subscribeUpdateStatus, app.updateStatus → refactor(desktop): move App Update controller below AppShell #4498 (open, CI green; AppUpdateProvider registered with the guard, services context on the shared factory)
Raw shell state — useState (15 + 2), useEffect (14), useLayoutEffect (2), useStableActions (6) converge to the region/provider that reads them; this item closes last
Shared infrastructure (do before the next three slices)
Collapse the per-slice join cost (Astro-Han, refactor(desktop): make AppShell a renderer composition root #3439). Provide one createServicesContext<S>(name) outside the renderer zones (checker treats packages/ui and src/shared as external) and reduce Desktop adapters to { ns: bridge.ns } or { ...bridge.ns, <real adaptation> }. Measured on 6c8e749d3 with tsc -p tsconfig.renderer.json: 81 of 119 adapter forwarders are identity. Replacing identity forwarders with ...bridge.<ns> and keeping the real adaptation verbatim produced zero new type errors for goals, module-hub (skills, scheduledTasks), session-navigation, task-entry, and workbar (terminal, todo, browser, attachments); together that is about 80 lines removed across 5 adapters. The blocks that mix identity forwarders with multi-line adaptation (workbarreview/artifacts/inspector/sideChat, module-hubdailyReview, runtime-host-managementpeerMesh) need a hand pass; connection-settings, session-collaboration, and session-settings do real adaptation and stay as they are. Bridge namespaces are plain objects (no this in preload), so passing them whole is runtime-safe. Done in refactor(desktop): share the feature services context and pass bridge namespaces through #4590 (merged 2026-09-03):createServicesContext lives in application/contracts, every slice context is four lines, and adapters hand a bridge namespace through where the port is a structural subset (33 identity forwarders removed). Blocks that rename, guard, or translate stay hand-written: the adapter tests drive Proxy-based bridge recorders with no own keys, so a spread would copy nothing.
One gate for AppShell hooks (needs @Astro-Han's agreement — the hook gate's no---write friction is deliberate). Proposal: the ledger gains a component-scoped hookCalls for AppShell/AppShellContent with exact-match semantics, controllerOwners (refactor(desktop): move Module Hub controller below AppShell #4315) is the provenance authority, and scripts/check-app-shell-hooks.mjs retires. Until then both gates run in CI.
No product, visual, IPC, storage-schema, copy, or shortcut changes. Composer mount semantics, bootstrap behavior, streaming isolation, Session-switch races, and Workbar resource lifecycles stay compatible. Performance evidence, where given, is measured in one running Electron instance with alternating configurations and paired trials, and the claim is bounded to what was measured.
Non-goals
No new global state library or service locator; no IPC redesign; no packages/ui extraction; no mechanical moves into features/ that leave the hook call site in the shell.
Why a new issue
#3439 was closed when the R1 foundation (#4088, corrected by #4249) merged, with 9 of its 12 checklist items still open. Four controller migrations (#4315, #4316, #4491, #4498) and the Usage slice (#4425 / #4440) currently reference a closed issue. This issue is the successor and carries the remaining work.
The definition of done changes, following the evidence Astro-Han posted on #3439 and #4109: extracting a feature into
features/<name>/does not change the scope of any state. A slice is done whenAppShellorAppShellContent(the entry disappears fromscripts/check-app-shell-hooks.mjs, or shrinks to a single equality-selected read that the shell genuinely needs);window.maka.*paths are gone fromapp-shell.tsx(thebridgePathsentry inrenderer-architecture.jsonshrinks);controllerOwnersentry once refactor(desktop): move Module Hub controller below AppShell #4315 lands;Not a fifth condition, but a convention worth following where it is cheap (Astro-Han, below): a migrated slice is testable without Electron — its
ports.tsnames the capabilities andtesting.tsships fakes — so when a slice moves, move the E2E coverage that only exists because the behaviour lived in the shell into the slice's own tests in the same PR.goals(#4316) is the finished example;features/has 12 slices and no test files yet. This also orders the list: WorkHub router unlocksworkhub-layoutandworkhub-reconstruction, and Conversation host coversstreaming-remount(where #4664 sits). Layering background: #4727.Baseline (upstream/main
6c8e749d3, 2026-09-03)app-shell.tsxAppShell+AppShellContentrender bodiescheck:app-shell-hooks)window.maka.*paths inapp-shell.tsxbridgePaths)connection-settings,goals,module-hub,runtime-host-management,session-collaboration,session-navigation,session-settings,task-entry,workbar)For reference,
app-shell.tsxhad 23 bridge references when #3439 opened; the five extraction PRs did not stop new product work (#3427, #3497, #3635, #3764, #3798) from landing bridge calls in the shell. The R1 ratchet has held the count since 2026-08-30.Progress
6c8e749d3)cbeb1a93c)app-shell.tsxAppShell+AppShellContentwindow.maka.*paths inapp-shell.tsxcontrollerOwnersuseModuleHubController(#4491 and #4498 add Task Entry and App Update)usagefrom #4440;conversationandnetwork-proxylanded from other work)Merged so far under this issue: #4440, #4590, #4315. Open: #4491, #4498, #4584.
Checklist
Each item names the hooks and bridge paths that must leave
AppShell/AppShellContent. Claim an item by commenting; one slice per PR.Controllers already extracted, call site still in the shell
useModuleHubController→ refactor(desktop): move Module Hub controller below AppShell #4315 (merged 2026-09-04; firstcontrollerOwnersregistration)useGoalController→ refactor(desktop): move Goal controller ownership below AppShell #4316 (merged 2026-09-01)useTaskEntryController→ refactor(desktop): move Task Entry controller below AppShell #4491 (open, CI green;TaskEntryRootowns the controller and is registered with the guard)useWorkbarController;WorkbarHoststill receivesmodelfrom the shell → claimed by @theSatvik, refactor(desktop): move Workbar ownership below AppShell #4692 (open)useSessionNavigationReads(a selector read; keep only if the shell body still needs it after Conversation/Session controls move)Shell-owned state that has no slice yet
useEffect/useState; bridgeapp.installUpdate,app.retryUpdateDownload,app.subscribeUpdateStatus,app.updateStatus→ refactor(desktop): move App Update controller below AppShell #4498 (open, CI green;AppUpdateProviderregistered with the guard, services context on the shared factory)settings/usage-settings-page.tsx→ refactor(desktop): move Settings → Usage into a renderer feature slice #4440 (merged 2026-09-03; extraction only, the pricing editor stays with feat(desktop): add editable Runtime Host-backed pricing settings #2015)useAppShellTurnPresentation,useShellLiveTurn,useActiveSessionEvents,useTurnActionRegistry,useActiveExecutionBoundary,useSessionEventHealthPolling,useShellRunUpdates,useShellResume,useShellMemoryPill,useShellChatModel; bridgetranscripts(2),sessions.listActiveInteractions,sessions.subscribeActiveInteractions,sessions.listTurnLandmarks,sessions.compact,notifications.runEndeduseTaskSubmissionReadiness,useComposerAttachments,useAppShellComposerQuotes,useNewTaskChoice; bridgeattachments,attachments.readBytes,sessions.updateQueueEntry,sessions.retractQueueEntry,sessions.promoteQueueEntry,sessions.reorderQueueEntriesuseSessionSettingIntent,useSessionCollaborationDialog,usePlanModeState,useSettingsModal; bridgesessions.getPlanState,sessions.abandonPlanProposal,sessions.setCollaborationModeuseAppShellSessionWorkspace,useAppShellSessionUiReads,useAppShellProjectContext,useAppShellNavRefSync; bridgesessions(whole namespace handed down)workHub.resolveCoordinationSession,workHub.record,workHub.candidates,workHub.act,settings.getClient,settings.subscribeClientChanged,runtimeHostProfiles.subscribeChanges,connections.subscribeEvents(coordinate with tracking(desktop): evolve WorkHub into a per-Runtime-Host conversational coordinator #3492)useAppShellBootstrapSubscriptions,useAppShellHostEffects,useAppShellPersistenceEffects,useOnboardingSnapshot,useShellConnections(3),useShellAppearance,useSystemUiLocale; bridgediagnostics.copyReport,diagnostics.copyPreviousMainProcessInterruption,diagnostics.takePreviousMainProcessInterruption,onboarding.setMilestoneuseCommandPalette,useKeyboardHelp,useShellSearch,useToastuseState(15 + 2),useEffect(14),useLayoutEffect(2),useStableActions(6) converge to the region/provider that reads them; this item closes lastShared infrastructure (do before the next three slices)
createServicesContext<S>(name)outside the renderer zones (checker treatspackages/uiandsrc/sharedas external) and reduce Desktop adapters to{ ns: bridge.ns }or{ ...bridge.ns, <real adaptation> }. Measured on6c8e749d3withtsc -p tsconfig.renderer.json: 81 of 119 adapter forwarders are identity. Replacing identity forwarders with...bridge.<ns>and keeping the real adaptation verbatim produced zero new type errors forgoals,module-hub(skills,scheduledTasks),session-navigation,task-entry, andworkbar(terminal,todo,browser,attachments); together that is about 80 lines removed across 5 adapters. The blocks that mix identity forwarders with multi-line adaptation (workbarreview/artifacts/inspector/sideChat,module-hubdailyReview,runtime-host-managementpeerMesh) need a hand pass;connection-settings,session-collaboration, andsession-settingsdo real adaptation and stay as they are. Bridge namespaces are plain objects (nothisin preload), so passing them whole is runtime-safe. Done in refactor(desktop): share the feature services context and pass bridge namespaces through #4590 (merged 2026-09-03):createServicesContextlives inapplication/contracts, every slice context is four lines, and adapters hand a bridge namespace through where the port is a structural subset (33 identity forwarders removed). Blocks that rename, guard, or translate stay hand-written: the adapter tests drive Proxy-based bridge recorders with no own keys, so a spread would copy nothing.--writefriction is deliberate). Proposal: the ledger gains a component-scopedhookCallsforAppShell/AppShellContentwith exact-match semantics,controllerOwners(refactor(desktop): move Module Hub controller below AppShell #4315) is the provenance authority, andscripts/check-app-shell-hooks.mjsretires. Until then both gates run in CI.CI; run the base commit's checker against the head tree as a cross-check so a PR cannot lower both sides by weakening a rule; add the two-commit git-fixture test fix(desktop): ratchet renderer debt against the base tree, not its ledger #4249 deferred. → fix(desktop): harden the renderer architecture ratchet base comparison #4584 (open, CI green).Invariants (unchanged from #3439)
No product, visual, IPC, storage-schema, copy, or shortcut changes. Composer mount semantics, bootstrap behavior, streaming isolation, Session-switch races, and Workbar resource lifecycles stay compatible. Performance evidence, where given, is measured in one running Electron instance with alternating configurations and paired trials, and the claim is bounded to what was measured.
Non-goals
No new global state library or service locator; no IPC redesign; no
packages/uiextraction; no mechanical moves intofeatures/that leave the hook call site in the shell.