From 61cad0bcc0953efc8494330cdaa89020bc76fc7b Mon Sep 17 00:00:00 2001 From: chihumyum Date: Sat, 3 Oct 2026 17:45:35 +0800 Subject: [PATCH 1/6] refactor(desktop): seed default-Host connections beside the onboarding authority MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AppShellContent kept one useEffect, marked for removal in M5, that seeded the default Host's connection projection from the onboarding snapshot, or refreshed it when onboarding could not be read. OnboardingConnectionSeed, a render-null watch in the onboarding authority's contract, now does that from the authority itself, with the shell supplying only the projection's seed and refresh commands. AppShellContent loses its last useEffect (gate 25/30 → 24/29) and the retained-root row for it. Refs #4582 Generated-by: Claude Opus 5.5 --- apps/desktop/renderer-architecture.json | 5 ++-- .../__tests__/onboarding-authority.test.ts | 30 +++++++++++++++++++ apps/desktop/src/renderer/README.md | 1 - apps/desktop/src/renderer/app-shell.tsx | 20 +++---------- .../onboarding/onboarding-authority.ts | 30 ++++++++++++++++++- scripts/check-app-shell-hooks.mjs | 1 - 6 files changed, 65 insertions(+), 22 deletions(-) diff --git a/apps/desktop/renderer-architecture.json b/apps/desktop/renderer-architecture.json index d3407ae145..d8a8789ca9 100644 --- a/apps/desktop/renderer-architecture.json +++ b/apps/desktop/renderer-architecture.json @@ -674,7 +674,6 @@ "useAppShellSessionUiReads": 1, "useAppShellSessionWorkspace": 1, "useAppShellTurnPresentation": 1, - "useEffect": 1, "useLayoutEffect": 1, "useRef": 4, "useSessionNavigationReads": 1, @@ -760,8 +759,8 @@ "@maka/ui": 1, "react": 1 }, - "importSpecifiers": 60, - "nonTriviaTokens": 8017 + "importSpecifiers": 59, + "nonTriviaTokens": 7967 }, "src/renderer/use-app-shell-session-list.ts": { "importDeclarations": 4, diff --git a/apps/desktop/src/main/__tests__/onboarding-authority.test.ts b/apps/desktop/src/main/__tests__/onboarding-authority.test.ts index d20a634dfa..b248f9de2f 100644 --- a/apps/desktop/src/main/__tests__/onboarding-authority.test.ts +++ b/apps/desktop/src/main/__tests__/onboarding-authority.test.ts @@ -29,6 +29,9 @@ import { createOnboardingSnapshotPoller, getOnboardingActivationCandidate, OnboardingAuthorityProvider, + OnboardingConnectionSeed, + type OnboardingAuthority, + type OnboardingProjection, OnboardingProjectionRoot, onboardingSnapshotProjectionEqual, type OnboardingShellProjection, @@ -126,6 +129,33 @@ describe('createOnboardingAuthority', () => { assert.equal(fake.calls.at(-1), 'unsubscribe', 'the root is the subscriber that keeps the reads alive'); }); + it('seeds the default Host connections from each new snapshot, and refreshes them when reads fail', async () => { + let projection: OnboardingProjection = { snapshot: null, failed: false }; + const listeners = new Set<() => void>(); + const authority: OnboardingAuthority = { + getProjection: () => projection, + subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); }, + refresh: () => {}, + skipInitialOnboarding: async () => {}, + }; + const publish = (next: OnboardingProjection) => act(() => { projection = next; listeners.forEach((listener) => listener()); }); + const calls: unknown[] = []; + const { root } = installReactRenderer(); + await act(async () => root.render(createElement(OnboardingAuthorityProvider, { value: authority, + children: createElement(OnboardingConnectionSeed, { + seed: (snapshot) => calls.push(snapshot), + refresh: () => calls.push('refresh'), + }) }))); + assert.deepEqual(calls, [], 'nothing to seed before the first read'); + const withConnections = { ...READY_SNAPSHOT, defaultSlug: 'openai' }; + publish({ snapshot: withConnections, failed: false }); + publish({ snapshot: withConnections, failed: false }); + assert.deepEqual(calls.splice(0), [{ connections: [], defaultConnection: 'openai', chatModelChoices: [] }]); + publish({ snapshot: null, failed: true }); + assert.deepEqual(calls.splice(0), ['refresh']); + await act(async () => root.unmount()); + }); + it('fails without its provider instead of holding the first-run gate closed', () => { const { root } = installReactRenderer(); assert.throws(() => act(() => root.render(createElement(OnboardingProjectionRoot, { children: () => null }))), diff --git a/apps/desktop/src/renderer/README.md b/apps/desktop/src/renderer/README.md index 075ee1c1c2..7d150c595e 100644 --- a/apps/desktop/src/renderer/README.md +++ b/apps/desktop/src/renderer/README.md @@ -287,7 +287,6 @@ or reason, which stays with review. | `AppShellContent` | `useAppShellSessionUiReads` | displayed Session chrome | interaction, queue, live-turn and execution chrome; Composer props | Conversation (transitional reader) | fixed-purpose reads of the displayed and owner Session | — | M3 | | `AppShellContent` | `useAppShellSessionWorkspace` | Session workspace | every region's requested, published and owner Session | legacy `use-app-shell-session-workspace.ts` over the Session catalog and Conversation | Session selection and the catalog controller | navigation | — | | `AppShellContent` | `useAppShellTurnPresentation` | `deriveTurnPresentation` | `ChatView` turn footer | application contract `turn-presentation` | derive turn presentation from the transcript projection and pending turn actions | — | M3 | -| `AppShellContent` | `useEffect` | `defaultHostConnections`: onboarding connection seed | default-Host connection projection | AppShell | seed default-Host connections from the onboarding authority's read-only projection | — | M5 | | `AppShellContent` | `useLayoutEffect` | `openSessionInChatRef` publication | turn footer, Module Hub, titlebar parent link | AppShell | publish the current open-Session command into a ref | cross-region command | — | | `AppShellContent` | `useSessionNavigationReads` | rail reads | command palette sessions, titlebar parent, `--maka-sidenav-width` | Session Navigation | revision navigation, the active parent Session and the rail layout | navigation | — | | `AppShellContent` | `useSessionSettingIntent` | selected-Session setting overlay | Composer model and mode controls; new-task settings for creation | Session Settings | an equality-selected overlay read, the new-task settings and setting commands | — | M3 | diff --git a/apps/desktop/src/renderer/app-shell.tsx b/apps/desktop/src/renderer/app-shell.tsx index 5b136e2ca3..de31ce90c0 100644 --- a/apps/desktop/src/renderer/app-shell.tsx +++ b/apps/desktop/src/renderer/app-shell.tsx @@ -22,7 +22,6 @@ import { WorkHubEnablementWatch } from './application/contracts/workhub-workspac import { RuntimeHostHandoffOverlay } from './features/runtime-host-management/index.js'; import { useCallback, - useEffect, useLayoutEffect, useMemo, useRef, @@ -85,6 +84,7 @@ import { NEW_TASK_PENDING_KEY } from './pending-items'; import { desktopSlashCommandAvailability } from './application/contracts/desktop-slash-command.js'; import { getOnboardingActivationCandidate, + OnboardingConnectionSeed, OnboardingProjectionRoot, type OnboardingShellProjection, } from './application/contracts/onboarding/onboarding-authority.js'; @@ -720,21 +720,8 @@ function AppShellContent({ // process (PR110a + PR110b contract) and reactively invalidates on // `sessions:changed` + `connections:event`. The hero renders only // when sessions.length === 0; any session (including archived / - // aborted) takes over with the existing chat surface. - useEffect(() => { - const snapshot = onboarding.snapshot; - if (snapshot) { - defaultHostConnections.seedSnapshot({ - connections: snapshot.connections, - defaultConnection: snapshot.defaultSlug, - chatModelChoices: snapshot.chatModelChoices, - }); - } else if (onboarding.failed) { - // Session bootstrap is independent above. If onboarding itself failed, - // retain the previous connection-specific recovery path as well. - void defaultHostConnections.refreshConnections(); - } - }, [onboarding.failed, onboarding.snapshot]); + // aborted) takes over with the existing chat surface. The default Host's + // connections are seeded from the same snapshot by OnboardingConnectionSeed. // Nothing settled to show while the first snapshot pull is in flight. The // flag keeps the composer hidden and — through `data-maka-content-ready` on // .appFrame — holds the launch overlay until a real frame exists: sessions, @@ -1339,6 +1326,7 @@ function AppShellContent({ > + void defaultHostConnections.refreshConnections()} /> { setWorkHubActive(true); setNavSelection({ section: 'sessions' }); }} onDisabled={exitWorkHub} /> { + if (snapshot) { + seed({ + connections: snapshot.connections, + defaultConnection: snapshot.defaultSlug, + chatModelChoices: snapshot.chatModelChoices, + }); + } else if (failed) { + refresh(); + } + }, [failed, snapshot]); + return null; +} + /** The current snapshot, for readers that check something again whenever onboarding changes. */ export function useCurrentOnboardingSnapshot(): OnboardingSnapshot | null { const authority = useOnboardingAuthority(); diff --git a/scripts/check-app-shell-hooks.mjs b/scripts/check-app-shell-hooks.mjs index 0dcb8d175c..71f1a1002c 100644 --- a/scripts/check-app-shell-hooks.mjs +++ b/scripts/check-app-shell-hooks.mjs @@ -114,7 +114,6 @@ export const ALLOWED = { useAppShellSessionUiReads: 1, useAppShellSessionWorkspace: 1, useAppShellTurnPresentation: 1, - useEffect: 1, useLayoutEffect: 1, // Replaces `useSessionNavigationController`, which is now called inside // `SessionNavigationProvider`. The entry shrinks rather than disappearing, From e485fa592147231762ea68e2a9303144a8250f4e Mon Sep 17 00:00:00 2001 From: chihumyum Date: Sat, 3 Oct 2026 17:48:41 +0800 Subject: [PATCH 2/6] refactor(desktop): keep the project context to what the root reads useAppShellProjectContext still returned the default Host's project list, the Local Host's projects and the raw selected project id, and kept a Local Host project subscription (projects.getLocalSnapshot and subscribeLocalChanges) for them, although no reader remained after project mutations moved to Task Entry (#5937). Drop those outputs and the subscription. The hook stays at the root: it is the per-Host project projection the shell composes into the titlebar, Workbar, Module Hub, command palette and mentions, in the same class as the per-Host connection projections. Its retained-root row now records that as an application lifecycle with its actual consumers instead of an M5 removal. Refs #4582 Generated-by: Claude Opus 5.5 --- apps/desktop/renderer-architecture.json | 10 ++--- .../__tests__/use-project-context.test.ts | 17 +++------ apps/desktop/src/renderer/README.md | 2 +- apps/desktop/src/renderer/app-shell.tsx | 1 - .../src/renderer/use-project-context.ts | 38 +++---------------- 5 files changed, 16 insertions(+), 52 deletions(-) diff --git a/apps/desktop/renderer-architecture.json b/apps/desktop/renderer-architecture.json index d8a8789ca9..fb3246dda3 100644 --- a/apps/desktop/renderer-architecture.json +++ b/apps/desktop/renderer-architecture.json @@ -760,7 +760,7 @@ "react": 1 }, "importSpecifiers": 59, - "nonTriviaTokens": 7967 + "nonTriviaTokens": 7965 }, "src/renderer/use-app-shell-session-list.ts": { "importDeclarations": 4, @@ -3334,16 +3334,14 @@ "bridgePaths": { "window.maka.app.sessionProjectInfo": 1, "window.maka.projects.getDefaultContext": 1, - "window.maka.projects.getLocalSnapshot": 1, "window.maka.projects.getSnapshot": 1, - "window.maka.projects.subscribeChanges": 2, - "window.maka.projects.subscribeLocalChanges": 1 + "window.maka.projects.subscribeChanges": 2 }, "environmentCapabilities": {}, "hookCalls": { - "useEffect": 4, + "useEffect": 3, "useRef": 1, - "useState": 7 + "useState": 6 }, "lifecycleMethods": {}, "unresolvedDependencies": 0, diff --git a/apps/desktop/src/main/__tests__/use-project-context.test.ts b/apps/desktop/src/main/__tests__/use-project-context.test.ts index 8f7273051d..f667b6985e 100644 --- a/apps/desktop/src/main/__tests__/use-project-context.test.ts +++ b/apps/desktop/src/main/__tests__/use-project-context.test.ts @@ -70,22 +70,17 @@ test('discards a pending Project projection after the default Host changes', asy return pendingContext.promise; }, subscribeChanges: () => () => {}, - getLocalSnapshot: async () => ({ - projects: [], - capabilities: NO_PROJECT_CAPABILITIES, - }), - subscribeLocalChanges: () => () => {}, }, }; - let projects: ProjectRecord[] = []; - let selectedProjectId: string | null | undefined; + let projectInfo: unknown; + let currentProjectId: string | null | undefined; function Probe() { const context = projectContext.useAppShellProjectContext({ rendererMountedRef: { current: true }, }); - projects = context.projects; - selectedProjectId = context.selectedProjectId; + projectInfo = context.projectInfo; + currentProjectId = context.currentProjectId; return null; } @@ -110,8 +105,8 @@ test('discards a pending Project projection after the default Host changes', asy await commitGuardChecked.promise; }); - assert.deepEqual(projects, []); - assert.equal(selectedProjectId, undefined); + assert.equal(projectInfo, null); + assert.equal(currentProjectId, undefined); }); afterEach(() => { diff --git a/apps/desktop/src/renderer/README.md b/apps/desktop/src/renderer/README.md index 7d150c595e..810d3b67cd 100644 --- a/apps/desktop/src/renderer/README.md +++ b/apps/desktop/src/renderer/README.md @@ -283,7 +283,7 @@ or reason, which stays with review. | `AppShellContent` | `useAppShellHostEffects` | titlebar modal sync | titlebar | legacy `app-shell-effects.ts` | observe top-layer modals; the `data-os` platform tag is applied by `ShellLifecycleSources` | layout | — | | `AppShellContent` | `useAppShellNavRefSync` | `navSelectionRef` | ownership checks of async results | AppShell | mirror the navigation selection into a ref | navigation | — | | `AppShellContent` | `useAppShellPersistenceEffects` | theme and navigation persistence | `` theme class and palette; stored navigation | legacy `app-shell-effects.ts` | apply the theme preference and palette; persist the navigation state | layout | — | -| `AppShellContent` | `useAppShellProjectContext` | project context | titlebar project name and path; Workbar, Module Hub and palette project inputs; the default-Host project refresh | legacy `use-project-context.ts` | read the owner Session's and the default Host's project projection; project mutations and the open-folder commands belong to Task Entry | — | M5 | +| `AppShellContent` | `useAppShellProjectContext` | project context | titlebar project name, path and open-folder gate; Workbar project id and aliases; Module Hub and command-palette client-path access; composer mentions project path; the default-Host refresh in the lifecycle handlers | legacy `use-project-context.ts` | read the owner Session's project (path, Git state, current project, capabilities) and, with no Session, the default Host's, and re-read it on change; no writes: project mutations and the open-folder commands belong to Task Entry | application lifecycle | — | | `AppShellContent` | `useAppShellSessionUiReads` | displayed Session chrome | interaction, queue, live-turn and execution chrome; Composer props | Conversation (transitional reader) | fixed-purpose reads of the displayed and owner Session | — | M3 | | `AppShellContent` | `useAppShellSessionWorkspace` | Session workspace | every region's requested, published and owner Session | legacy `use-app-shell-session-workspace.ts` over the Session catalog and Conversation | Session selection and the catalog controller | navigation | — | | `AppShellContent` | `useAppShellTurnPresentation` | `deriveTurnPresentation` | `ChatView` turn footer | application contract `turn-presentation` | derive turn presentation from the transcript projection and pending turn actions | — | M3 | diff --git a/apps/desktop/src/renderer/app-shell.tsx b/apps/desktop/src/renderer/app-shell.tsx index de31ce90c0..40491a2eea 100644 --- a/apps/desktop/src/renderer/app-shell.tsx +++ b/apps/desktop/src/renderer/app-shell.tsx @@ -775,7 +775,6 @@ function AppShellContent({ const moduleHubCommands = useMemo(ModuleHub.createModuleHubCommandPort, []); const { projectInfo, - projects, projectCapabilities, activeProjectCapabilities, currentProjectId, diff --git a/apps/desktop/src/renderer/use-project-context.ts b/apps/desktop/src/renderer/use-project-context.ts index 2a63115f56..3a42f04f20 100644 --- a/apps/desktop/src/renderer/use-project-context.ts +++ b/apps/desktop/src/renderer/use-project-context.ts @@ -50,9 +50,11 @@ const NO_PROJECT_CAPABILITIES: DesktopProjectCapabilities = { }; /** - * Owns the workspace projection AppShell reads: the default Host's project, - * the active session's project, and the persistent project catalog behind - * them. Project mutations and the open-folder commands belong to Task Entry. + * The read-only project projection the root composes into the titlebar, + * Workbar, Module Hub, command palette and mentions: the owner Session's + * project (path, Git state, current project, capabilities) and, with no + * Session, the default Host's. Project mutations and the open-folder + * commands belong to Task Entry. */ export function useAppShellProjectContext(options: { rendererMountedRef: RefBox; @@ -64,11 +66,8 @@ export function useAppShellProjectContext(options: { /** Re-reads the default Host's project context; resolves to its projects. */ refreshProjects(): Promise; projectInfo: RendererAppInfo | null; - projects: ProjectRecord[]; projectCapabilities: DesktopProjectCapabilities; activeProjectCapabilities: DesktopProjectCapabilities; - localProjects: readonly ProjectRecord[]; - selectedProjectId: string | null | undefined; currentProjectId: string | null | undefined; currentProject: ProjectRecord | undefined; } { @@ -84,7 +83,6 @@ export function useAppShellProjectContext(options: { const [projects, setProjects] = useState([]); const [projectCapabilities, setProjectCapabilities] = useState(NO_PROJECT_CAPABILITIES); - const [localHostProjects, setLocalHostProjects] = useState([]); const [sessionProjectSnapshot, setSessionProjectSnapshot] = useState<{ sessionId: string; projects: ProjectRecord[]; @@ -139,29 +137,6 @@ export function useAppShellProjectContext(options: { }; }, []); - useEffect(() => { - let cancelled = false; - let refreshGeneration = 0; - const refresh = () => { - const generation = ++refreshGeneration; - return window.maka.projects.getLocalSnapshot().then( - (snapshot) => { - if (cancelled || generation !== refreshGeneration) return; - setLocalHostProjects([...snapshot.projects]); - }, - () => { - // The Local Host may be reconnecting; its ready event retries this read. - }, - ); - }; - const unsubscribe = window.maka.projects.subscribeLocalChanges(() => void refresh()); - void refresh(); - return () => { - cancelled = true; - unsubscribe(); - }; - }, []); - useEffect(() => { if (!sessionId) { setSessionProjectSnapshot(null); @@ -240,11 +215,8 @@ export function useAppShellProjectContext(options: { return { refreshProjects, projectInfo, - projects, projectCapabilities, activeProjectCapabilities, - localProjects: localHostProjects, - selectedProjectId, currentProjectId, currentProject, }; From eae9970426dddbe389ada5a7a200c8df0304c9de Mon Sep 17 00:00:00 2001 From: chihumyum Date: Sat, 3 Oct 2026 17:50:45 +0800 Subject: [PATCH 3/6] refactor(desktop): stop threading props their readers never use After the R2 merges, AppShell still threaded several values nothing read: - seven ChatView props (activeConnectionLabel, activeModel, activeModelLabel, activeProviderType, renderProviderMark, modelChoices, onModelChange) that ChatView declares but never reads. The transcript region passed all but activeModel, and the WorkHub conversation passed activeModel. They are removed from ChatView's props, with the ProviderLogo import and the chat-model values AppShell computed only for them. - uiLocaleOverride on AppShellContent; only AppShell's LocaleProvider reads it. - SessionNavigationPorts.activateSession, which no rail action calls. - the WorkHub rail entry object: its `active` repeated workHubActive and its label was a constant. The rail now takes onOpenWorkHub and builds the entry itself. A type-level test pins the trimmed contracts. Refs #4582 Generated-by: Claude Opus 5.5 --- apps/desktop/renderer-architecture.json | 7 ++-- .../__tests__/app-shell-retired-props.test.ts | 41 +++++++++++++++++++ .../session-navigation-controller.test.ts | 5 +-- apps/desktop/src/renderer/app-shell.tsx | 19 +-------- .../features/session-navigation/ports.ts | 1 - .../ui/session-navigation-provider.tsx | 9 ++-- .../features/workhub/ui/workhub-root.tsx | 1 - apps/desktop/stories/app-shell.stories.tsx | 4 -- packages/ui/src/chat-view.tsx | 14 ------- packages/ui/stories/attachment.stories.tsx | 4 -- packages/ui/stories/quote-comment.stories.tsx | 4 -- 11 files changed, 53 insertions(+), 56 deletions(-) create mode 100644 apps/desktop/src/main/__tests__/app-shell-retired-props.test.ts diff --git a/apps/desktop/renderer-architecture.json b/apps/desktop/renderer-architecture.json index fb3246dda3..45bc9d22c3 100644 --- a/apps/desktop/renderer-architecture.json +++ b/apps/desktop/renderer-architecture.json @@ -659,7 +659,7 @@ "nonTriviaTokens": 858 }, "src/renderer/app-shell.tsx": { - "importDeclarations": 42, + "importDeclarations": 41, "bridgePaths": {}, "environmentCapabilities": { "window.requestAnimationFrame": 3 @@ -736,7 +736,6 @@ "./platform/desktop/default-runtime-host-operation.js": 1, "./scroll-motion-policy": 1, "./settings/provider-brand-marks": 1, - "./settings/provider-display": 1, "./settings/runtime-host-ssh-terminal-dialog.js": 1, "./shell/frame-style": 1, "./use-app-shell-session-ui-reads": 1, @@ -759,8 +758,8 @@ "@maka/ui": 1, "react": 1 }, - "importSpecifiers": 59, - "nonTriviaTokens": 7965 + "importSpecifiers": 58, + "nonTriviaTokens": 7860 }, "src/renderer/use-app-shell-session-list.ts": { "importDeclarations": 4, diff --git a/apps/desktop/src/main/__tests__/app-shell-retired-props.test.ts b/apps/desktop/src/main/__tests__/app-shell-retired-props.test.ts new file mode 100644 index 0000000000..bfb3d414d9 --- /dev/null +++ b/apps/desktop/src/main/__tests__/app-shell-retired-props.test.ts @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; +import type { ComponentProps } from 'react'; +import type { ChatView } from '@maka/ui'; +import type { + SessionNavigationPorts, + SessionNavigationProvider, +} from '../../renderer/features/session-navigation/index.js'; + +// The root stopped threading these: ChatView never read them, the rail derives +// its WorkHub entry from `workHubActive`, and no rail action activates a Session. +type Declares = Extract extends never ? false : true; +const retired = { + chatViewModelProps: false as Declares, + 'activeConnectionLabel' | 'activeModel' | 'activeModelLabel' | 'activeProviderType' + | 'renderProviderMark' | 'modelChoices' | 'onModelChange'>, + railWorkHubEntry: false as Declares, 'workHubEntry'>, + portActivateSession: false as Declares, +} satisfies Record; + +test('the root no longer threads props their readers never use', () => { + assert.deepEqual(Object.values(retired), [false, false, false]); +}); diff --git a/apps/desktop/src/main/__tests__/session-navigation-controller.test.ts b/apps/desktop/src/main/__tests__/session-navigation-controller.test.ts index 5ed13e42f5..77a2ecb2c4 100644 --- a/apps/desktop/src/main/__tests__/session-navigation-controller.test.ts +++ b/apps/desktop/src/main/__tests__/session-navigation-controller.test.ts @@ -156,7 +156,6 @@ function ports( return { sessionsRef: { current: sessions }, acquireAutomaticQueryBlock: () => ({ release: () => undefined }), - activateSession: (sessionId) => calls.push(`activate:${sessionId ?? 'none'}`), clearSessionRendererState: (sessionId) => calls.push(`clear:${sessionId}`), refreshSessions: async () => sessions, toastApi: { @@ -205,7 +204,7 @@ function navigationTree( shell: { activeSessionId: string; workHubActive: boolean; - workHubEntry?: { active: boolean; label: string; onSelect(): void }; + onOpenWorkHub?(): void; }, sibling: ReactNode, child: ReactNode, @@ -674,7 +673,7 @@ describe('SessionNavigationProvider selection', () => { await act(async () => root.render(navigationTree(catalog, { activeSessionId: 'root', workHubActive: false, - workHubEntry: { active: false, label: 'WorkHub', onSelect: () => opened.push('workhub') }, + onOpenWorkHub: () => opened.push('workhub'), }, null, createElement(ChromeProbe), undefined, { workHub: enablement }))); const workHubEntry = () => chrome?.workHubEntry; assert.equal(workHubEntry(), undefined); diff --git a/apps/desktop/src/renderer/app-shell.tsx b/apps/desktop/src/renderer/app-shell.tsx index 40491a2eea..2a46ec240b 100644 --- a/apps/desktop/src/renderer/app-shell.tsx +++ b/apps/desktop/src/renderer/app-shell.tsx @@ -89,7 +89,6 @@ import { type OnboardingShellProjection, } from './application/contracts/onboarding/onboarding-authority.js'; import { ShellLifecycleSubscriptions } from './application/contracts/shell-lifecycle.js'; -import { ProviderLogo } from './settings/provider-display'; import { ProviderBrandMark } from './settings/provider-brand-marks'; import { RuntimeHostSshTerminalDialog } from './settings/runtime-host-ssh-terminal-dialog.js'; import { @@ -185,7 +184,7 @@ export function AppShell() { {(copyManualDiagnosticReport) => ( )} @@ -227,7 +226,6 @@ function AppShellContent({ onboarding, copyManualDiagnosticReport, uiLocale, - uiLocaleOverride, setUiLocaleOverride, setUiLocalePreference, }: { @@ -238,7 +236,6 @@ function AppShellContent({ onboarding: OnboardingShellProjection; copyManualDiagnosticReport: Diagnostics.CopyManualDiagnosticReport; uiLocale: UiLocale; - uiLocaleOverride: UiLocale | null; setUiLocaleOverride: Dispatch>; setUiLocalePreference: Dispatch>; }) { @@ -516,10 +513,7 @@ function AppShellContent({ }); const { chatModelChoices, - activeConnection, - activeConnectionLabel, activeModel, - activeModelLabel, executor, composerModelProps, newChatModel, @@ -904,7 +898,6 @@ function AppShellContent({ const sessionNavigationPorts: SessionNavigationPorts = { sessionsRef, acquireAutomaticQueryBlock: sessionCatalogController.acquireAutomaticQueryBlock, - activateSession: setActiveId, clearSessionRendererState, refreshSessions, toastApi, @@ -1428,7 +1421,7 @@ function AppShellContent({ onSelect={setNavSelection} onOpenSettings={openSettings} onNew={createSession} - workHubEntry={{ active: workHubActive, label: 'WorkHub', onSelect: openWorkHub }} + onOpenWorkHub={openWorkHub} projectActions={projectRowActions} onNewProject={ taskEntry.selectors.canAddProject @@ -1603,14 +1596,6 @@ function AppShellContent({ } - modelChoices={chatModelChoices} - onModelChange={sharedSessionActive ? undefined : (input) => { - if (activeId) void setSessionModel(activeId, input); - }} userLabel={userLabel} memoryActive={memoryActive} onOpenMemorySettings={sharedSessionActive ? undefined : () => openSettingsSection('memory')} diff --git a/apps/desktop/src/renderer/features/session-navigation/ports.ts b/apps/desktop/src/renderer/features/session-navigation/ports.ts index faf9225b5e..37babc6a93 100644 --- a/apps/desktop/src/renderer/features/session-navigation/ports.ts +++ b/apps/desktop/src/renderer/features/session-navigation/ports.ts @@ -152,7 +152,6 @@ export interface SessionNavigationServices { export interface SessionNavigationPorts { sessionsRef: RefObject>; acquireAutomaticQueryBlock(sessionIds: readonly string[]): { release(): void }; - activateSession(sessionId: string | undefined): void; clearSessionRendererState(sessionId: string): void; refreshSessions(): Promise>; toastApi: SessionNavigationToastApi; diff --git a/apps/desktop/src/renderer/features/session-navigation/ui/session-navigation-provider.tsx b/apps/desktop/src/renderer/features/session-navigation/ui/session-navigation-provider.tsx index ab33c14504..4ef3002817 100644 --- a/apps/desktop/src/renderer/features/session-navigation/ui/session-navigation-provider.tsx +++ b/apps/desktop/src/renderer/features/session-navigation/ui/session-navigation-provider.tsx @@ -74,7 +74,7 @@ export interface SessionNavigationChromeInput { moduleMemory?: NavModuleMemory; workHubActive: boolean; /** Shown, and selectable, only while the client WorkHub switch is on. */ - workHubEntry?: { active: boolean; label: string; onSelect(): void }; + onOpenWorkHub?(): void; projectActions?: ProjectRowActions; onSelect(selection: NavSelection): void; onOpenSettings(): void; @@ -325,9 +325,10 @@ export function SessionNavigationProvider(props: SessionNavigationProviderProps) props.onNew(); }, onOpenSettings: props.onOpenSettings, - workHubEntry: workHubEnabled && props.workHubEntry ? { - ...props.workHubEntry, - onSelect: () => { if (workHubEnablement.isEnabled()) props.workHubEntry?.onSelect(); }, + workHubEntry: workHubEnabled && props.onOpenWorkHub ? { + active: props.workHubActive, + label: 'WorkHub', + onSelect: () => { if (workHubEnablement.isEnabled()) props.onOpenWorkHub?.(); }, } : undefined, }; diff --git a/apps/desktop/src/renderer/features/workhub/ui/workhub-root.tsx b/apps/desktop/src/renderer/features/workhub/ui/workhub-root.tsx index 07f629d649..3852f43956 100644 --- a/apps/desktop/src/renderer/features/workhub/ui/workhub-root.tsx +++ b/apps/desktop/src/renderer/features/workhub/ui/workhub-root.tsx @@ -410,7 +410,6 @@ export function WorkHubRoot() { activeTurn={controller.activeTurn} messageLoading={!transcript.ready} activeSession={session} - activeModel={session?.model} emptyOverride={
diff --git a/apps/desktop/stories/app-shell.stories.tsx b/apps/desktop/stories/app-shell.stories.tsx index 48a9333386..efa130c120 100644 --- a/apps/desktop/stories/app-shell.stories.tsx +++ b/apps/desktop/stories/app-shell.stories.tsx @@ -219,10 +219,6 @@ const baseChatProps: ChatViewProps = { messages: conversation, scrollBehavior: 'smooth', activeSession, - activeConnectionLabel: 'Anthropic', - activeModel: 'claude-sonnet-4-5', - activeModelLabel: 'Claude Sonnet 4.5', - modelChoices, userLabel: '你', onNew: noop, onPromptSuggestion: noop, diff --git a/packages/ui/src/chat-view.tsx b/packages/ui/src/chat-view.tsx index 9f1da9c58a..f59203a215 100644 --- a/packages/ui/src/chat-view.tsx +++ b/packages/ui/src/chat-view.tsx @@ -185,20 +185,6 @@ export function ChatView(props: { */ activeTurn?: { readonly turnId: string; readonly awaitingInput?: boolean; readonly compacting?: boolean }; activeSession?: SessionSummary; - activeConnectionLabel?: string; - activeModel?: string; - activeModelLabel?: string; - /** Renders a provider brand mark next to the model name in the chat tab. */ - activeProviderType?: ProviderType; - /** Optional renderer for the provider mark; supplied by the desktop app to - * avoid bringing the full provider SVG library into @maka/ui. */ - renderProviderMark?(type: ProviderType): ReactNode; - modelChoices?: ChatModelChoice[]; - onModelChange?(input: { - llmConnectionId: string; - llmConnectionSlug: string; - model: string; - }): void | Promise; /** Personalized user label shown on user messages. Falls back to "你". */ userLabel?: string; /** diff --git a/packages/ui/stories/attachment.stories.tsx b/packages/ui/stories/attachment.stories.tsx index 4f8c7531d1..878c33509c 100644 --- a/packages/ui/stories/attachment.stories.tsx +++ b/packages/ui/stories/attachment.stories.tsx @@ -116,10 +116,6 @@ const baseChat: ChatViewProps = { messages: [], scrollBehavior: 'smooth', activeSession: session(), - activeConnectionLabel: 'Anthropic', - activeModel: 'claude-sonnet-4-5', - activeModelLabel: 'Claude Sonnet 4.5', - modelChoices, userLabel: '你', onReadAttachmentBytes: mockReadBytes, onNew: noop, diff --git a/packages/ui/stories/quote-comment.stories.tsx b/packages/ui/stories/quote-comment.stories.tsx index 5338be0eef..1f01beb017 100644 --- a/packages/ui/stories/quote-comment.stories.tsx +++ b/packages/ui/stories/quote-comment.stories.tsx @@ -119,10 +119,6 @@ const baseChat: ChatViewProps = { messages: [], scrollBehavior: 'smooth', activeSession: session(), - activeConnectionLabel: 'Anthropic', - activeModel: 'claude-sonnet-4-5', - activeModelLabel: 'Claude Sonnet 4.5', - modelChoices, userLabel: '你', onNew: noop, onPromptSuggestion: noop, From 3681d31d32b52f2feb48bef9aae7a9c3e0989fc8 Mon Sep 17 00:00:00 2001 From: chihumyum Date: Sat, 3 Oct 2026 17:52:02 +0800 Subject: [PATCH 4/6] refactor(desktop): move the detail panel into the shell zone and correct ownership labels AppShellDetailPanel is a stateless frame with no hooks and no dependencies beyond React, which is what the shell zone holds and what the ledger's shell-root-and-frame entry targets. It moves to shell/detail-panel.tsx, so the AppShell family loses one legacy file. Two ownership entries no longer described their files: - mixed-bootstrap-and-conversation-effects is now root-lifecycle-effects, since app-shell-effects.ts holds only root lifecycle hooks. - app-shell-copy.ts, which is command-palette error copy used by app-shell-command-actions.ts, moves to commands-and-overlays. Refs #4582 Generated-by: Claude Opus 5.5 --- apps/desktop/renderer-architecture.json | 26 +++++-------------- apps/desktop/src/renderer/app-shell.tsx | 2 +- .../detail-panel.tsx} | 0 apps/desktop/stories/app-shell.stories.tsx | 2 +- apps/desktop/stories/module-hubs.stories.tsx | 2 +- docs/astryx-surface-file-inventory.md | 2 +- docs/astryx-surface-file-inventory.paths | 2 +- 7 files changed, 11 insertions(+), 25 deletions(-) rename apps/desktop/src/renderer/{app-shell-detail-panel.tsx => shell/detail-panel.tsx} (100%) diff --git a/apps/desktop/renderer-architecture.json b/apps/desktop/renderer-architecture.json index 45bc9d22c3..134e82d171 100644 --- a/apps/desktop/renderer-architecture.json +++ b/apps/desktop/renderer-architecture.json @@ -7,7 +7,6 @@ "src/renderer/app-shell-chrome-actions.tsx", "src/renderer/app-shell-command-actions.ts", "src/renderer/app-shell-copy.ts", - "src/renderer/app-shell-detail-panel.tsx", "src/renderer/app-shell-e2e-fixture.ts", "src/renderer/app-shell-effects.ts", "src/renderer/app-shell-overlays.tsx", @@ -570,18 +569,6 @@ "importSpecifiers": 1, "nonTriviaTokens": 249 }, - "src/renderer/app-shell-detail-panel.tsx": { - "importDeclarations": 0, - "bridgePaths": {}, - "environmentCapabilities": {}, - "hookCalls": {}, - "lifecycleMethods": {}, - "unresolvedDependencies": 0, - "actionFactories": [], - "dependencyPaths": {}, - "importSpecifiers": 0, - "nonTriviaTokens": 87 - }, "src/renderer/app-shell-e2e-fixture.ts": { "importDeclarations": 1, "bridgePaths": { @@ -659,7 +646,7 @@ "nonTriviaTokens": 858 }, "src/renderer/app-shell.tsx": { - "importDeclarations": 41, + "importDeclarations": 40, "bridgePaths": {}, "environmentCapabilities": { "window.requestAnimationFrame": 3 @@ -696,7 +683,6 @@ "dependencyPaths": { "./agent-graph-panel": 1, "./app-shell-chrome-actions": 1, - "./app-shell-detail-panel": 1, "./app-shell-e2e-fixture": 1, "./app-shell-effects": 1, "./app-shell-overlays": 1, @@ -737,6 +723,7 @@ "./scroll-motion-policy": 1, "./settings/provider-brand-marks": 1, "./settings/runtime-host-ssh-terminal-dialog.js": 1, + "./shell/detail-panel": 1, "./shell/frame-style": 1, "./use-app-shell-session-ui-reads": 1, "./use-app-shell-session-workspace": 1, @@ -758,7 +745,7 @@ "@maka/ui": 1, "react": 1 }, - "importSpecifiers": 58, + "importSpecifiers": 57, "nonTriviaTokens": 7860 }, "src/renderer/use-app-shell-session-list.ts": { @@ -3720,8 +3707,7 @@ "capability": "shell-root-and-frame", "targetZone": "shell", "legacyPaths": [ - "src/renderer/app-shell-chrome-actions.tsx", - "src/renderer/app-shell-detail-panel.tsx" + "src/renderer/app-shell-chrome-actions.tsx" ] }, { @@ -3751,14 +3737,14 @@ "targetZone": "features/overlays", "legacyPaths": [ "src/renderer/app-shell-command-actions.ts", + "src/renderer/app-shell-copy.ts", "src/renderer/app-shell-overlays.tsx" ] }, { - "capability": "mixed-bootstrap-and-conversation-effects", + "capability": "root-lifecycle-effects", "targetZone": "split-by-capability", "legacyPaths": [ - "src/renderer/app-shell-copy.ts", "src/renderer/app-shell-effects.ts" ] }, diff --git a/apps/desktop/src/renderer/app-shell.tsx b/apps/desktop/src/renderer/app-shell.tsx index 2a46ec240b..cb29418e24 100644 --- a/apps/desktop/src/renderer/app-shell.tsx +++ b/apps/desktop/src/renderer/app-shell.tsx @@ -110,7 +110,7 @@ import { deriveDesktopExecutionBoundarySurface } from './desktop-execution-bound import { modelSetupToastCopy } from './model-connection-errors'; import type { AppShellCommandListOptions } from './app-shell-command-actions'; import { AppShellTitlebar } from './app-shell-chrome-actions'; -import { AppShellDetailPanel } from './app-shell-detail-panel'; +import { AppShellDetailPanel } from './shell/detail-panel'; import { appShellFrameStyle } from './shell/frame-style'; import { AppShellOverlays } from './app-shell-overlays'; import type { ArchivedTasksBridge } from './settings/tasks-settings-page'; diff --git a/apps/desktop/src/renderer/app-shell-detail-panel.tsx b/apps/desktop/src/renderer/shell/detail-panel.tsx similarity index 100% rename from apps/desktop/src/renderer/app-shell-detail-panel.tsx rename to apps/desktop/src/renderer/shell/detail-panel.tsx diff --git a/apps/desktop/stories/app-shell.stories.tsx b/apps/desktop/stories/app-shell.stories.tsx index efa130c120..84b3f94719 100644 --- a/apps/desktop/stories/app-shell.stories.tsx +++ b/apps/desktop/stories/app-shell.stories.tsx @@ -61,7 +61,7 @@ import { SESSION_WORKBAR_DEFAULT_WIDTH, type WorkbarLayoutState, } from '../src/renderer/features/workbar/testing'; -import { AppShellDetailPanel } from '../src/renderer/app-shell-detail-panel'; +import { AppShellDetailPanel } from '../src/renderer/shell/detail-panel'; import { deriveChatTurnPresentation } from '../src/renderer/application/contracts/turn-presentation'; import { deriveSessionRail, diff --git a/apps/desktop/stories/module-hubs.stories.tsx b/apps/desktop/stories/module-hubs.stories.tsx index 413bd3d2d3..4916307410 100644 --- a/apps/desktop/stories/module-hubs.stories.tsx +++ b/apps/desktop/stories/module-hubs.stories.tsx @@ -45,7 +45,7 @@ import { createFakeModuleHubServices, McpPage, } from '../src/renderer/features/module-hub/testing'; -import { AppShellDetailPanel } from '../src/renderer/app-shell-detail-panel'; +import { AppShellDetailPanel } from '../src/renderer/shell/detail-panel'; import { withSkillLocationCounts } from '../src/shared/skill-location-counts'; // Fidelity convention (#1433): every story below names the real app path diff --git a/docs/astryx-surface-file-inventory.md b/docs/astryx-surface-file-inventory.md index 549bbd73d6..7a699d4889 100644 --- a/docs/astryx-surface-file-inventory.md +++ b/docs/astryx-surface-file-inventory.md @@ -30,7 +30,6 @@ Wiki bar: Design Conventions · API Use-the-System · Theming · Container Paddi |------|------|-------------|------------|----------| | `apps/desktop/src/renderer/agent-graph-panel.tsx` | shell-chrome-or-panel | Banner, Button, EmptyState, IconButton, Selector, Spinner | aligned — uses Astryx (Banner, Button, EmptyState, IconButton, Selector, Spinner) | aligned | | `apps/desktop/src/renderer/app-shell-chrome-actions.tsx` | shell-chrome-or-panel | none | aligned — no raw controls; no Astryx JSX usage | aligned | -| `apps/desktop/src/renderer/app-shell-detail-panel.tsx` | shell-chrome-or-panel | none | aligned — no raw controls; no Astryx JSX usage | aligned | | `apps/desktop/src/renderer/app-shell-overlays.tsx` | shell-chrome-or-panel | Spinner | aligned — uses Astryx (Spinner) | aligned | | `apps/desktop/src/renderer/app-shell.tsx` | shell-chrome-or-panel | AppShell | aligned — uses Astryx (AppShell) | aligned | | `apps/desktop/src/renderer/app.tsx` | other | Theme | aligned — uses Astryx (Theme) | aligned | @@ -212,6 +211,7 @@ Wiki bar: Design Conventions · API Use-the-System · Theming · Container Paddi | `apps/desktop/src/renderer/settings/tasks-settings-page.tsx` | settings-page | EmptyState, IconButton, List, ListItem | aligned — uses Astryx (EmptyState, IconButton, List, ListItem) | aligned | | `apps/desktop/src/renderer/settings/usage-settings-page.tsx` | settings-page | none | aligned — no raw controls; no Astryx JSX usage | aligned | | `apps/desktop/src/renderer/settings/web-search-settings-page.tsx` | settings-page | Banner, Button, EmptyState, Link, Selector, StatusDot, Switch, TextInput | aligned — uses Astryx (Banner, Button, EmptyState, Link, Selector, StatusDot, Switch, TextInput) | aligned | +| `apps/desktop/src/renderer/shell/detail-panel.tsx` | shell-chrome-or-panel | none | aligned — no raw controls; no Astryx JSX usage | aligned | | `apps/desktop/src/renderer/shell/window-titlebar.tsx` | shell-chrome-or-panel | Icon, IconButton, Tooltip | aligned — uses Astryx (Icon, IconButton, Tooltip) | aligned | | `apps/desktop/src/renderer/styles.css` | styles | n/a (css) | aligned — no off-rhythm control heights flagged | aligned | | `apps/desktop/src/renderer/styles/agent-graph.css` | styles | n/a (css) | aligned — no off-rhythm control heights flagged | aligned | diff --git a/docs/astryx-surface-file-inventory.paths b/docs/astryx-surface-file-inventory.paths index 5c15ec9d17..7baaba7b86 100644 --- a/docs/astryx-surface-file-inventory.paths +++ b/docs/astryx-surface-file-inventory.paths @@ -1,6 +1,5 @@ apps/desktop/src/renderer/agent-graph-panel.tsx apps/desktop/src/renderer/app-shell-chrome-actions.tsx -apps/desktop/src/renderer/app-shell-detail-panel.tsx apps/desktop/src/renderer/app-shell-overlays.tsx apps/desktop/src/renderer/app-shell.tsx apps/desktop/src/renderer/app.tsx @@ -182,6 +181,7 @@ apps/desktop/src/renderer/settings/subagent-settings-page.tsx apps/desktop/src/renderer/settings/tasks-settings-page.tsx apps/desktop/src/renderer/settings/usage-settings-page.tsx apps/desktop/src/renderer/settings/web-search-settings-page.tsx +apps/desktop/src/renderer/shell/detail-panel.tsx apps/desktop/src/renderer/shell/window-titlebar.tsx apps/desktop/src/renderer/styles.css apps/desktop/src/renderer/styles/agent-graph.css From d084b62d093d616d71c201073fc35a40eae5081c Mon Sep 17 00:00:00 2001 From: chihumyum Date: Sat, 3 Oct 2026 17:52:42 +0800 Subject: [PATCH 5/6] docs(desktop): record each retained root hook's actual consumers and owners The retained-root table is the residual inventory R2 closes on, so every row should match the code. An audit of the rows that stay at the root found wrong consumers (the memory indicator is the transcript's session-context chip, not the titlebar; the rail reads go to the transcript and titlebar, not the command palette), wrong owners (useSystemUiLocale, useAppShellNavRefSync, useStableActions and useToast live in legacy files or @maka/ui) and stale capabilities (useShellAppearance writes no settings; ConversationLifecycle, not the transcript, bumps the pet counter). Those rows now name what the code does. The M3 rows are left to the M3 owner, which retires them. Refs #4582 Generated-by: Claude Opus 5.5 --- apps/desktop/src/renderer/README.md | 32 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/desktop/src/renderer/README.md b/apps/desktop/src/renderer/README.md index 810d3b67cd..d302f6879e 100644 --- a/apps/desktop/src/renderer/README.md +++ b/apps/desktop/src/renderer/README.md @@ -277,32 +277,32 @@ or reason, which stays with review. | --- | --- | --- | --- | --- | --- | --- | --- | | `AppShell` | `useState` | `uiLocalePreference` | `LocaleProvider`; appearance settings | AppShell | the persisted locale preference and its setter | locale | — | | `AppShell` | `useState` | `uiLocaleOverride` | `LocaleProvider`; E2E locale override | AppShell | a runtime locale override above every region | locale | — | -| `AppShell` | `useSystemUiLocale` | `systemUiLocale` | `resolveUiLocale` for `LocaleProvider` | AppShell | read the OS locale and its changes | locale | — | +| `AppShell` | `useSystemUiLocale` | `systemUiLocale` | `resolveUiLocale`, whose result reaches `LocaleProvider`, `ErrorBoundary` and `AppShellContent` | legacy `use-system-ui-locale.ts` | read the OS locale and its changes | locale | — | | `AppShellContent` | `useActiveExecutionBoundary` | `activeExecutionBoundary` | Composer permission control; the Composer submission owner reloads it after a boundary answer | Conversation | read and reload the owner Session's execution boundary | — | M3 | -| `AppShellContent` | `useAppShellBootstrapSubscriptions` | Main change subscriptions | Session, connection, Host-profile and settings refreshers; app-window commands | legacy `app-shell-effects.ts` | startup refreshes, the global shortcuts and the handlers `ShellLifecycleSubscriptions` subscribes with the injected `ShellLifecycleSources`; no bridge access | application lifecycle | — | -| `AppShellContent` | `useAppShellHostEffects` | titlebar modal sync | titlebar | legacy `app-shell-effects.ts` | observe top-layer modals; the `data-os` platform tag is applied by `ShellLifecycleSources` | layout | — | -| `AppShellContent` | `useAppShellNavRefSync` | `navSelectionRef` | ownership checks of async results | AppShell | mirror the navigation selection into a ref | navigation | — | +| `AppShellContent` | `useAppShellBootstrapSubscriptions` | lifecycle handlers and startup | `ShellLifecycleSubscriptions` (Session, connection, Host-profile and settings refreshes, window commands); the startup Session and E2E-fixture refresh; ⌘, and ⌘N | legacy `app-shell-effects.ts` | startup refreshes, the global shortcuts, the mounted flag and pending-ref cleanup; returns the handlers `ShellLifecycleSubscriptions` subscribes with the injected `ShellLifecycleSources`; no bridge access | application lifecycle | — | +| `AppShellContent` | `useAppShellHostEffects` | titlebar modal sync | the native window controls, dimmed through `titlebar-modal-sync.ts` | legacy `app-shell-effects.ts` | observe top-layer modals; the `data-os` platform tag is applied by `ShellLifecycleSources` | layout | — | +| `AppShellContent` | `useAppShellNavRefSync` | `navSelectionRef` | ownership checks of async results (Composer claim, surface-owner checks) | legacy `app-shell-effects.ts` | mirror the navigation selection into a ref | navigation | — | | `AppShellContent` | `useAppShellPersistenceEffects` | theme and navigation persistence | `` theme class and palette; stored navigation | legacy `app-shell-effects.ts` | apply the theme preference and palette; persist the navigation state | layout | — | | `AppShellContent` | `useAppShellProjectContext` | project context | titlebar project name, path and open-folder gate; Workbar project id and aliases; Module Hub and command-palette client-path access; composer mentions project path; the default-Host refresh in the lifecycle handlers | legacy `use-project-context.ts` | read the owner Session's project (path, Git state, current project, capabilities) and, with no Session, the default Host's, and re-read it on change; no writes: project mutations and the open-folder commands belong to Task Entry | application lifecycle | — | | `AppShellContent` | `useAppShellSessionUiReads` | displayed Session chrome | interaction, queue, live-turn and execution chrome; Composer props | Conversation (transitional reader) | fixed-purpose reads of the displayed and owner Session | — | M3 | -| `AppShellContent` | `useAppShellSessionWorkspace` | Session workspace | every region's requested, published and owner Session | legacy `use-app-shell-session-workspace.ts` over the Session catalog and Conversation | Session selection and the catalog controller | navigation | — | +| `AppShellContent` | `useAppShellSessionWorkspace` | Session workspace | every region's requested, published and owner Session; transcript flags; the queue surface and Composer ref; the Session UI reads store | legacy `use-app-shell-session-workspace.ts` over the Session catalog and Conversation | Session selection, the catalog controller, list refresh and row patches, message reads and refresh | navigation | — | | `AppShellContent` | `useAppShellTurnPresentation` | `deriveTurnPresentation` | `ChatView` turn footer | application contract `turn-presentation` | derive turn presentation from the transcript projection and pending turn actions | — | M3 | -| `AppShellContent` | `useLayoutEffect` | `openSessionInChatRef` publication | turn footer, Module Hub, titlebar parent link | AppShell | publish the current open-Session command into a ref | cross-region command | — | -| `AppShellContent` | `useSessionNavigationReads` | rail reads | command palette sessions, titlebar parent, `--maka-sidenav-width` | Session Navigation | revision navigation, the active parent Session and the rail layout | navigation | — | +| `AppShellContent` | `useLayoutEffect` | `openSessionInChatRef` publication | every caller of `openSessionInChat`: turn footer, transcript lineage, linked-Session and revision links, Composer submission, Workbar, Module Hub, command palette, Settings overlays, turn-request approval, agent graph, titlebar parent link | AppShell | publish the current open-Session command into a ref | cross-region command | — | +| `AppShellContent` | `useSessionNavigationReads` | rail reads | transcript revision navigation, titlebar parent and `sidebarCollapsed`, frame `data-sidebar-state` and `--maka-sidenav-width` | Session Navigation | revision navigation, the active parent Session and the rail layout; writes the compact flag into `sessionRailLayoutStore` | navigation | — | | `AppShellContent` | `useSessionSettingIntent` | selected-Session setting overlay | Composer model and mode controls; new-task settings for creation | Session Settings | an equality-selected overlay read, the new-task settings and setting commands | — | M3 | -| `AppShellContent` | `useShellAppearance` | appearance settings | theme, palette, user label, Workbar toggle position, locale update gate | legacy `use-shell-appearance.ts` | read and write client appearance settings | layout | — | +| `AppShellContent` | `useShellAppearance` | appearance settings | theme, palette, user label, Workbar toggle position, locale update gate; `appearanceHydrated` for the previous-shutdown notice | legacy `use-shell-appearance.ts` | read client and Host appearance settings and E2E state, hydrate the locale, apply theme, palette and font sizes, and expose local setters | layout | — | | `AppShellContent` | `useShellChatModel` | Composer model selection | model picker, health notice, new-chat model | Conversation (transitional) | derive model, thinking and executor selection | — | M3 | -| `AppShellContent` | `useShellConnections` | `newTaskConnections` | new-task model choices | legacy `use-shell-connections.ts` | the new-task target's connection snapshot and refresh | application lifecycle | — | -| `AppShellContent` | `useShellConnections` | `defaultHostConnections` | Settings, global commands, model setup | legacy `use-shell-connections.ts` | the default Host's connection snapshot and refresh | application lifecycle | — | -| `AppShellContent` | `useShellConnections` | `sessionHostConnections` | owner Session model choices | legacy `use-shell-connections.ts` | the owner Session Host's connection snapshot and refresh | application lifecycle | — | +| `AppShellContent` | `useShellConnections` | `newTaskConnections` | new-task model choices; transcript and onboarding connections and refresh; executor connection count | legacy `use-shell-connections.ts` | the new-task target's connection snapshot and refresh, falling back to the default Host or onboarding snapshot | application lifecycle | — | +| `AppShellContent` | `useShellConnections` | `defaultHostConnections` | `OnboardingConnectionSeed`; the new-task fallback; the refresh when Settings closes; the connection-event fan-out | legacy `use-shell-connections.ts` | the default Host's connection snapshot and refresh | application lifecycle | — | +| `AppShellContent` | `useShellConnections` | `sessionHostConnections` | owner Session model choices and readiness; transcript connections and refresh; the WorkHub snapshot | legacy `use-shell-connections.ts` | the owner Session Host's connection snapshot and refresh | application lifecycle | — | | `AppShellContent` | `useShellLiveTurn` | live-turn flags | mode-change gating, model switch, pet activity | Conversation reads | derive streaming and settled flags from the owner Session snapshot | — | M3 | -| `AppShellContent` | `useShellMemoryPill` | memory pill | titlebar memory pill | legacy `use-shell-memory-pill.ts` | read and refresh the owner Session's memory state | layout | — | +| `AppShellContent` | `useShellMemoryPill` | memory indicator | the transcript's session-context memory indicator; refreshed by the lifecycle handlers and when Settings closes | legacy `use-shell-memory-pill.ts` | read and refresh the owner Session's memory state, or the default Host's without a Session | layout | — | | `AppShellContent` | `useShellResume` | resume offer | Composer send slot | Conversation | per-Session resume availability | — | M3 | -| `AppShellContent` | `useStableActions` | `createAppShellE2eFixtureActions` | E2E fixture command | AppShell | apply test fixtures across navigation, rail, Workbar and appearance | cross-region command | — | -| `AppShellContent` | `useState` | `petCompletionNonce` | custom pet companion | AppShell | a counter the transcript bumps when the active Turn completes | cross-region command | — | +| `AppShellContent` | `useStableActions` | `createAppShellE2eFixtureActions` | the startup E2E-fixture refresh | legacy `app-shell-e2e-fixture.ts` over `application/contracts/use-stable-actions.ts` | apply test fixtures across Session selection and refresh, navigation, rail, Workbar, Settings, search and appearance | cross-region command | — | +| `AppShellContent` | `useState` | `petCompletionNonce` | custom pet companion | AppShell | a counter `ConversationLifecycle`'s `onTurnCompleted` bumps when the active Turn completes | cross-region command | — | | `AppShellContent` | `useState` | `navigationState` | navigation sections; stored navigation | AppShell | the selected section and each hub's module | navigation | — | -| `AppShellContent` | `useState` | `workHubActive` | WorkHub or Session surface | AppShell | whether the WorkHub surface is shown | navigation | — | -| `AppShellContent` | `useToast` | `toastApi` | toasts of every legacy action | Astryx toast provider | show toasts | cross-region command | — | +| `AppShellContent` | `useState` | `workHubActive` | WorkHub or Session surface; Workbar workspace; the rail entry; the Session-Host connection snapshot; the titlebar identity | AppShell | whether the WorkHub surface is shown; enablement is the WorkHub enablement authority's | navigation | — | +| `AppShellContent` | `useToast` | `toastApi` | toasts and confirm dialogs of every legacy action | `@maka/ui` `ToastProvider` (Astryx) | show toasts and confirmations | cross-region command | — | | `AppShellContent` | `useTurnActionRegistry` | pending turn actions | turn footer disabled mask; the Composer submission owner's Turn branch; bootstrap clears | legacy `use-turn-action-registry.ts` | pending action keys per Session | — | M3 | From 03c30db74e757b434f1e78b960624c38b6d45b26 Mon Sep 17 00:00:00 2001 From: chihumyum Date: Sat, 3 Oct 2026 19:10:18 +0800 Subject: [PATCH 6/6] docs(desktop): record the overlay and manual-report consumers as retained The "Transitional feature exports outside Conversation" table scheduled OverlaysConsumer and ManualDiagnosticReportConsumer for M5, with the legacy command actions. Neither can leave the root within R2. app-shell-overlays.tsx composes the legacy Settings surface, which it imports lazily, and the palette command list that #4997 deliberately kept as a shell injection point; feature zones may not import legacy code. The manual report is the diagnostics owner's command handed to the shell-built palette options. Both rows now record why they stay. Refs #4582 Generated-by: Claude Opus 5.5 --- apps/desktop/src/renderer/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/renderer/README.md b/apps/desktop/src/renderer/README.md index d302f6879e..3c260f4c85 100644 --- a/apps/desktop/src/renderer/README.md +++ b/apps/desktop/src/renderer/README.md @@ -316,8 +316,8 @@ following. Exports only tests or Storybook read live in each feature's | Feature | Export | Root consumer | Kind | Stays because / Removal | | --- | --- | --- | --- | --- | -| overlays | `OverlaysConsumer` | `app-shell-overlays.tsx` (Settings modal, palette command list) | render-prop projection of overlay state | M5, with the legacy command actions | -| diagnostics | `ManualDiagnosticReportConsumer` | command palette options in `app-shell.tsx` | render-prop manual report command | M5, with the legacy command actions | +| overlays | `OverlaysConsumer` | `app-shell-overlays.tsx` (Settings modal, palette command list) | render-prop projection of overlay state | stays: root composition of the legacy Settings surface and the palette command list, which a feature cannot import; it moves when the Settings surface migrates, outside R2 | +| diagnostics | `ManualDiagnosticReportConsumer` | command palette options in `app-shell.tsx` | render-prop manual report command | stays: cross-region command (the diagnostics owner's manual report, handed to the shell-built palette options) | | task-entry | `TaskEntryWorkspacePickerConsumer` | Composer region in `app-shell.tsx` | render-prop workspace picker | M3 | | session-collaboration | `GuestTurnRequests` | Composer region in `app-shell.tsx` | render-prop guest composer projection over the Composer ref | M3 | | module-hub | `ModuleHubSkillCatalogRevisionBoundary` | Composer mentions provider | render-prop skill catalog revision | M3 |