refactor(desktop): retire AppShell's direct Desktop bridge (R2 M5) - #5936
Conversation
…rsation port AppShell passed window.maka.attachments.readBytes to the transcript as a prop. Declare readBytes on Conversation's attachment port (ComposerStagingServices), whose Desktop adapter already returns the bridge's attachments namespace, and have StagedQuoteChatView, the feature's transcript ChatView, take the reader from it. The reader contracts of ChatMessageSurface and StagedQuoteChatView drop the prop, so AppShell can no longer supply one; app-shell.tsx goes from five direct bridge paths to four. Refs apache#4582 Generated-by: Claude Opus 5.5
AppShell read the client WorkHub switch from the Desktop bridge into its own state and passed it to Workbar, the session rail and the WorkHub dock. Create a WorkHub enablement authority in application/contracts beside the WorkHub workspace contract, with its Desktop source injected at composition like the session catalog. Workbar, the rail and the dock read it directly; the rail and WorkHub's main-window navigation check it again before opening WorkHub. WorkHubEnablementWatch hands AppShell only the on/off edges for the navigation it still owns (workHubActive and the destination). AppShell loses settings.getClient, settings.subscribeClientChanged, the workHubEnabled state, its ref and its effect. Refs apache#4582 Generated-by: Claude Opus 5.5
AppShell owned the onboarding snapshot: the poller, its invalidation subscriptions, the refresh after Settings closes and the bridge write behind "skip setup". Move the poller into an onboarding authority in application/contracts, created at composition with a Desktop source (the former onboarding-snapshot-bridge, now also writing the skip milestone on the default Host). The session rail reads per-Session send outcomes from it directly. AppShell receives a read-only projection plus refresh and skip through OnboardingProjectionRoot, the same render-prop pattern as the other shell roots, and derives first-run gating, the default-Host connection seed and the activation candidate from it. A failed read is now a flag. The localized error text was never shown, so nothing that might carry paths or tokens is kept. The snapshot types move to src/shared so the application layer can name them; preload re-exports them unchanged. AppShell loses onboarding.setMilestone, useOnboardingSnapshot and the sessionSendOutcomes prop it threaded to the rail. Refs apache#4582 Generated-by: Claude Opus 5.5
The shell's catalog refresh called window.maka.sessions.list itself. The session catalog now takes a SessionCatalogSource (full lists and the change feed), injected at composition from the Desktop session catalog adapter, and the shell refreshes through catalog.source. A catalog built without a source, as in tests and stories, is detached. use-app-shell-session-list.ts loses its sessions.list bridge path. Refs apache#4582 Generated-by: Claude Opus 5.5
…jected sources app-shell-effects.ts subscribed to seven Desktop bridge paths directly: app.info for the document's platform tag, the window menu, connection events, Host profile changes, Session changes and both settings-change feeds. The reactions stay a root application lifecycle, since they refresh several regions at once, but the environment leaves the shell: - ShellLifecycleSources (application/contracts/shell-lifecycle.ts) is supplied at composition by a Desktop adapter that also writes the data-os tag. - ShellLifecycleSubscriptions is the one subscriber. It takes Session changes from the session catalog's own source. - useAppShellBootstrapSubscriptions keeps the startup refreshes, the hotkeys and the mounted flag, and returns the handlers. Every handler now reads the latest render. The two settings-change handlers used to capture the first render's connection refresh; they now refresh the current connection projections, as the Host-change handler already did. app-shell-effects.ts loses all seven bridge paths and one effect. Refs apache#4582 Generated-by: Claude Opus 5.5
AppShell handed ConversationLifecycle an inline window.maka.sessions.listTurnLandmarks reader. Declare listTurnLandmarks on ConversationServices.sessions; the Desktop adapter already spreads the bridge's sessions namespace, so it supplies the function unchanged. The lifecycle passes it to the reading-position controller, and its prop is gone, so AppShell can no longer supply a reader. The controller never listed the reader as an effect dependency, so its identity becoming stable changes nothing. app-shell.tsx loses its last direct bridge path. Refs apache#4582 Generated-by: Claude Opus 5.5
Astro-Han
left a comment
There was a problem hiding this comment.
Reviewed 479ec0ecfac965d11a3af7d205331a7478490f23 (46 files, +1568/−681, 6 commits). The dispatch asked for a per-file pass on the retired bridge call sites — every former window.maka.* call must correspond item by item in its new owner — plus whether the ledger only tightens.
No P0–P3 findings.
The retired call sites. app-shell.tsx gives up five bridge paths, one each: attachments.readBytes, onboarding.setMilestone, sessions.listTurnLandmarks, settings.getClient, settings.subscribeClientChanged. Two of them (settings.getClient, settings.subscribeClientChanged) still have renderer callers elsewhere in the tree, so those removals are plainly a change of owner rather than a loss. The other three now have no call site anywhere under apps/desktop/src outside tests — which is the case worth stating precisely: the bridge methods are still referenced elsewhere in the repository (attachments.readBytes in 3 files, onboarding.setMilestone in 1, listTurnLandmarks in 12), so nothing appears orphaned at the contract level, and the description's per-commit table carries a "who loses what" column that declares the intent for each removal. I found no contradiction between that table and the tree; I did not attempt to prove that the three app-shell call sites were dead rather than merely unreferenced in the renderer.
The ledger only tightens, which is the property worth pinning. All three of those paths go from 1 to 0 in app-shell.tsx's ledger entry, the manifest diff is +14/−50 (net −36), and check-app-shell-hooks.mjs moves +2/−3 (net −1). Nothing is loosened, and the description's "43 → 30" for the AppShell-family bridge count is consistent with those numbers.
One thing worth knowing before merging these siblings. All four AppShell PRs share the base 1a66e4d5, so they are parallel rather than stacked. #5934 does not touch app-shell.tsx at all, so it cannot conflict on that file — but #5936 and #5937 overlap on it in three regions (@@ -204, @@ -240, @@ -249 in the AppShell / AppShellContent bodies), so whichever lands second will need a real merge there rather than a clean apply.
Gate on this head: test and label are green.
What I did not judge
- Whether the three unreferenced app-shell call sites were dead or load-bearing before their removal; the description asserts the former and the tree does not contradict it, but I did not trace each capability end to end.
- The internal correctness of the 7-path move in
app-shell-effects.tsbeyond the ledger accounting. - No Electron run.
I did not approve, request changes, or merge.
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
Astro-Han
left a comment
There was a problem hiding this comment.
Independent review of 479ec0ecfac965d11a3af7d205331a7478490f23 (base 1a66e4d5e; it merges cleanly onto current main 887924e14).
Verdict: no P0, P1 or P2 found. Each of the 13 bridge paths reaches an owner, and none is dropped. I traced the three that no longer appear as a literal window.maka.* anywhere outside tests, end to end on this head:
attachments.readBytes(transcript image bytes, not sending).StagedQuoteChatViewnow readsuseComposerStagingServices().readBytes(staged-quote-chat-view.tsx:29-30).createDesktopComposerStagingServicesreturnsbridge.attachmentswhole, so it is the same preload function (preload.ts:3421,attachments:readBytes).ChatMessageSurfaceandStagedQuoteChatViewdrop the prop from their types, so the shell cannot reintroduce it.sessions.listTurnLandmarks.ConversationLifecyclepassesservices.sessions.listTurnLandmarks(conversation-lifecycle.tsx:148). The Desktop Conversation adapter spreads...bridge.sessions(create-conversation-services.ts:61), so it is the same preload function (preload.ts:2469). Both call sites (the index read withnull, andlookupTurn) passturnIdexplicitly, so the preload default does not matter. The reference is now stable where it used to be a fresh closure, and that does not affect the effect deps.onboarding.setMilestone. The shell'sonSkipcallsonboarding.skipInitialOnboarding(), which goes toOnboardingAuthority.skipInitialOnboarding, thensource.skipInitialOnboarding, thenrunOnDefaultRuntimeHost(host => bridge.onboarding.setMilestone('initial_onboarding','skipped',host))(create-onboarding-source.ts:48-50). It then re-pulls, asonboarding.refresh()did. Thetry/catchand its toast in the shell are unchanged.
Other fidelity checks:
- WorkHub enablement. The watch fires
onEnabledon the firsttrueread, which opens WorkHub and selectssessionsat startup asbecameEnableddid. It firesonDisabled(exitWorkHub) on afalseedge. The old guardif (!workHubEnabledRef.current) returninopenWorkHubmoved to both of its callers: the rail entry re-checksisEnabled()on select, andWorkHubMainNavigationchecks beforeonOpenWorkHub. No other caller ofopenWorkHubexists. A read that keeps reportingfalseno longer callssetWorkHubActive(false), butworkHubActivecannot be true while the switch is off, so nothing changes in practice. - Lifecycle subscriptions. All six subscriptions are still made, with the same handlers.
useEffectEventgives the latest handler, which fixes the stale first-render closure in the settings mirrors as declared. Session changes now come fromcatalog.source, which wraps the samebridge.sessions.subscribeChanges.data-ostagging keeps its cancel. - Onboarding authority. The poller is moved verbatim.
failedreplaces only the truthiness oferror, which was all the shell read. - Gate and ledger only tighten.
useOnboardingSnapshotis removed,useEffectgoes 2→1 anduseState8→7, andbridgePathsis now{}forapp-shell.tsx,app-shell-effects.tsanduse-app-shell-session-list.ts.
Local run on this head (build:test): the workhub-enablement, onboarding-authority, shell-lifecycle, session-catalog-source, session-navigation-controller, composer-staging-owner, conversation-owner, workbar-controller and onboarding-incremental-preload suites pass (116/116). CI is green.
P3: silent default contexts. OnboardingAuthority (IDLE), WorkHubEnablement (DISABLED), ShellLifecycleSources (DETACHED) and the catalog's DETACHED_SOURCE fall back to inert values when their provider is missing. Every other feature port uses createServicesContext, which throws. If a future composition drops OnboardingAuthorityProvider, snapshot stays null and failed stays false, so isOnboardingLoading stays true for a user with no Sessions and the launch overlay never clears. Nothing would fail loudly. Suggest throwing (at least in dev) when the provider is absent, and making tests and stories pass explicit fakes.
P3: undeclared lifetime change. The onboarding snapshot now outlives an AppShell remount because the authority is an app singleton. Before, a remount started from null. This is benign, and the code comment documents it, but the PR's "Behavior" list does not mention it.
P3: test gap, already acknowledged in the PR. The handler map returned by useAppShellBootstrapSubscriptions is untested. Moving that mapping into a pure factory next to ShellLifecycleHandlers would make it testable without fixing the extensionless imports.
P3: feature entry re-exports a contract. features/workhub/index.ts:28 re-exports WorkHubEnablementWatch from application/contracts. Under #5934's root-symbol rule, that attributes a contract component to the WorkHub feature entry. Importing it from the contract directly, as OnboardingProjectionRoot and ShellLifecycleSubscriptions already are, would be more honest.
Cross-PR (git merge-tree against 887924e14).
- #5937: conflicts in
renderer-architecture.jsonand in threeapp-shell.tsxhunks (@@ -204,-240,-249). Both PRs wrapAppShellContentin a render-prop root and add one prop, so the second to land nests both. I confirmed these are the only three conflict regions. - #5935: conflicts in
app-shell.tsx, the ledger,scripts/check-app-shell-hooks.mjsandcomposer-staging-owner.test.ts. - #5934: a textual conflict in
features/workhub/index.ts. #5934's checker will also need--writeand the row edits listed in this PR's body.
This is an automated review by Claude (Anthropic), run on behalf of the maintainer. It is not an approval. Please verify the findings before acting on them.
…he lifecycle handlers Review follow-up for apache#5936: - The onboarding, WorkHub-enablement and shell-lifecycle contexts throw when their provider is missing, as createServicesContext does, instead of falling back to inert values that would, for example, hold the first-run gate closed. A catalog built without a source still accepts commits but fails when read through that source. Tests pass explicit fakes. - The event-to-reaction mapping moves into the pure createShellLifecycleHandlers beside ShellLifecycleHandlers, so it is unit-tested without loading app-shell-effects.ts. - AppShell imports WorkHubEnablementWatch from its contract; the WorkHub feature entry no longer re-exports it. Refs apache#4582 Generated-by: Claude Opus 5.5
Astro-Han
left a comment
There was a problem hiding this comment.
Reviewed deeff8d6b9c578c2c9799188cc24dc5ebf4b2718 (46 files, +1692/−697, 7 commits).
Both P3s from my previous pass are fixed, each verified in the code rather than from the reply.
- A missing provider no longer fails silently. The failure mode I flagged was that the onboarding authority's inert default meant a composition that forgot
OnboardingAuthorityProviderwould seesnapshot: null, failed: falseforever, soisOnboardingLoadingwould hold the launch overlay with no way to tell why. The authority now throws (OnboardingAuthorityProvider is missing),shell-lifecycle.tsthrows for its own sources, and the feature-services helper throws${providerName} is missingwith a documented convention that the message stays<Feature>ServicesProvider is missingacross slices. So the inert default is gone from all three, not only the one I named. - The cross-PR symbol pollution is gone. My finding was that the WorkHub feature entry re-exported a component from
application/contracts, which #5934'srootSymbolUseswould then record as a WorkHub public-entry symbol even though WorkHub does not own it. At this headfeatures/workhub/index.tsis byte-identical tomain(empty diff), and AppShell importsWorkHubEnablementWatchdirectly fromapplication/contracts/workhub-workspace/workhub-enablement.js. That removes the conflict in that file between the two PRs, which is what the dispatch asked me to check for this set.
The deep pass from my previous review is unaffected. The five retired bridge call sites, the ledger tightening (entries 1→0, manifest net −36, check-app-shell-hooks net −1) and the three sites that lost their last renderer caller while the bridge methods remain referenced elsewhere all still read the same; this head's increment is the fixes above.
Gate on this head: test and label are green; mergeable is true against the base branch, so the PR can still land on its own. Every commit carries a Generated-by: trailer and there is no Grok involvement.
What I could not judge
- I did not execute the suites; the fixes are verified by reading the throwing paths, the empty diff against
main, and the import site. - Whether the two sibling PRs still conflict on
app-shell.tsxafter this head — I measured three overlapping regions last round, and this change does not touchapp-shell.tsx's structure, so I would expect the overlap to stand.
I did not approve, request changes, or merge.
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
…-attachment-bytes # Conflicts: # apps/desktop/renderer-architecture.json
Astro-Han
left a comment
There was a problem hiding this comment.
Reviewed 63888f9fd0c186555b758b562ef4c20eee571ccd.
The rebase is faithful, so my previous review's conclusions carry over unchanged. A git range-diff between the old and new ranges marks all eight commits of this PR as = — byte-identical — with the only differences being commits that came in from main. The tree difference I measured against my previously reviewed head was that base advancement alone. The gate is green on the new head.
Merge order and remaining conflicts (measured at the current heads, all four sharing one base): there is no overlap with #5934 beyond the generated ledger; against #5935 the overlap is twelve files, including app-shell.tsx, chat-message-surface.tsx, composition/desktop-feature-services.tsx, scripts/check-app-shell-hooks.mjs and features/conversation/testing.ts; against #5937 it is the ledger, app-shell-effects.ts and app-shell.tsx. So the #5934 → #5935 → #5936 → #5937 order is still required, and this PR should land after #5935 has been merged and its resolution reviewed — the two are ownership moves in the same file, not adjacent edits.
What I could not judge
- Verified by
range-diffand file sets; I did not run the suites or perform the merges. - The moved logic was not re-audited, since the rebase left it untouched.
I did not approve, request changes, or merge.
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
…-attachment-bytes # Conflicts: # apps/desktop/renderer-architecture.json # apps/desktop/src/main/__tests__/composer-staging-owner.test.ts # apps/desktop/src/renderer/app-shell.tsx # scripts/check-app-shell-hooks.mjs
Brings in apache#5936. - app-shell.tsx: apache#5936 wraps AppShellContent in OnboardingProjectionRoot where this branch wraps it in ManualDiagnosticReportConsumer. Both are kept, the onboarding root outside, and AppShellContent takes both the onboarding projection and copyManualDiagnosticReport. - renderer-architecture.json: taken from main's copy, the workspace-projection ownership entry removed again, and regenerated. - README retained-root table: the useAppShellProjectContext row now describes the projection it still provides (project mutations and the folder commands are Task Entry's); the transitional-exports table lists ManualDiagnosticReportConsumer. The hook gate matches the merged tree unchanged (25 hooks, 30 call sites). Generated-by: Claude Code
Summary
At
c7fa6bb6a, the AppShell family still reached the Desktop bridge in 13 places across three files:app-shell.tsx: 5 pathsapp-shell-effects.ts: 7 pathsuse-app-shell-session-list.ts: 1 pathThis PR gives each one an owner. There is one commit per owner. Afterwards
app-shell.tsxhas no directwindow.makaaccess, and the AppShell-family bridge count in the ledger goes from 43 to 30. The remaining 30 are the chat, command, project, revision, stop, turn and E2E-fixture actions, which other slices own.4f49aaf99attachments.readBytesComposerStagingServicesgainsreadBytes; its Desktop adapter already returnedbridge.attachments.StagedQuoteChatViewreads it.onReadAttachmentBytesprop.ChatMessageSurfaceandStagedQuoteChatViewdrop the prop from their contracts, and a forced value is overridden.5bd6132d3settings.getClient,settings.subscribeClientChangedapplication/contracts/workhub-workspace/workhub-enablement.ts) with a Desktop source injected at composition. Workbar, the rail and the dock read it directly.WorkHubEnablementWatchhands AppShell only the on/off edges.workHubEnabledstate, its ref and its effect. Workbar's input losesworkHub.enabled,WorkHubDocklosesenabled, and the rail decides entry visibility itself.602046260onboarding.setMilestoneapplication/contracts/onboarding/onboarding-authority.ts). It owns the poller, invalidations, refresh and the skip command. Desktop source:create-onboarding-source.ts, formerlyonboarding-snapshot-bridge.ts. The rail reads send outcomes from it. AppShell receives a read-only projection throughOnboardingProjectionRoot.useOnboardingSnapshot(and the poller with it), the bridge write, and thesessionSendOutcomesprop it threaded to the rail.6d5500928sessions.listSessionCatalogSourceinjected into the session catalog at composition.use-app-shell-session-list.tsloses its bridge path.093d29906app.info,appWindow.subscribeCommand,connections.subscribeEvents,runtimeHostProfiles.subscribeChanges,sessions.subscribeChanges,settings.subscribeClientChanged,settings.subscribeExternalChangedShellLifecycleSources(application/contracts/shell-lifecycle.ts) comes from a Desktop adapter that also writesdata-os.ShellLifecycleSubscriptionsis the single subscriber; Session changes come from the catalog's source.app-shell-effects.tsloses all seven paths and one effect.useAppShellBootstrapSubscriptionskeeps the startup refreshes, hotkeys and mounted flag, and returns its handlers.479ec0ecfsessions.listTurnLandmarksConversationServices.sessions.listTurnLandmarks. The Desktop adapter already spreadsbridge.sessions.ConversationLifecycleloses itslistTurnLandmarksprop, so AppShell can no longer supply a reader.deeff8d6b479ec0ecf), described below.Design decisions
OnboardingProjectionRootfollows the render-prop root pattern ofTaskEntryRootandOverlaysRoot.WorkHubEnablementWatchandShellLifecycleSubscriptionsfollowCatalogRowWatch.readBytesandlistTurnLandmarksuse ports that already exist, and both Desktop adapters already supply the functions. Neither needed adapter code.createServicesContext, instead of falling back to inert values. A catalog built without a source can still be committed to, but reading through its source rejects or throws. Tests pass explicit fakes; no story mounts these readers.createShellLifecycleHandlersbesideShellLifecycleHandlers. The hook returns it, so the mapping is unit-tested without loadingapp-shell-effects.ts.WorkHubEnablementWatchfrom its contract, as it doesOnboardingProjectionRootandShellLifecycleSubscriptions.features/workhub/index.tsis unchanged from main.Behavior
There is no product, visual, IPC, storage, copy or shortcut change. Three internal differences:
refreshConnectionsclosure. They now refresh the current connection projections, as the Host-change handler already did.snapshotErrorFallbackonboarding copy key now has no reader. It stays in place so this PR changes no copy and does not collide with feat(i18n-ko): native surfaces and E2E fixtures #4515'skocatalog; removing it can be a follow-up.null. The authority's code comment documents this.OnboardingSnapshotandDesktopOnboardingSessionUpdatetypes moved tosrc/shared/onboarding-snapshot.d.tsso the application layer can name them. Preload re-exports them unchanged.Inventory (
c7fa6bb6a→ this branch; the branch is rebased on1a66e4d5e, which adds only non-renderer files)app-shell.tsxlines / import statementswindow.makapaths inapp-shell.tsxlegacyAppShell.files)nonTriviaTokens:app-shell.tsx/app-shell-effects.tscontrollerOwnersNotes:
useOnboardingSnapshot,workHubEnabled(useState8→7) and itsuseEffect(2→1).controllerOwnersunchanged: no React controller moved. The new owners are authorities created at composition.use-onboarding-snapshot.ts.Retained root entries in this PR's scope
Proposed rows for the retained-root table in #5934:
useAppShellBootstrapSubscriptionsShellLifecycleSubscriptionsShellLifecycleSourcesand the catalog sourceuseAppShellHostEffectsuseAppShellPersistenceEffectsuseAppShellNavRefSyncuseState(workHubActive)useShellConnections×3ShellLifecycleSubscriptions; onboarding seeduseAppShellSessionWorkspaceuseAppShellProjectContextuseShellMemoryPillRefs #4582
Merge with #5934 and #5935 (
e542fbe28)Merged upstream/main
255ae23ae(not rebased). Conflicts:app-shell.tsx: imports only. Kept main's application-contractdesktopSlashCommandAvailabilityplus this PR's onboarding and lifecycle imports.useNewTaskChoice,useTaskSubmissionReadinessanduseOnboardingSnapshotare gone;useStableActionsis 1 anduseState3.composer-staging-owner.test.ts: imports only.--write.Follow-ups the merge required:
TaskReadinessProvidernow reads the onboarding snapshot from the onboarding authority (useCurrentOnboardingSnapshot), so AppShell no longer passesrefreshKey={onboarding.snapshot}. Its test harness delivers the key through a fake authority.useOnboardingSnapshot, theworkHubEnableduseStateand the WorkHub-enablementuseEffect.useAppShellHostEffectsis now the titlebar modal sync only.useAppShellBootstrapSubscriptionsis retained as an application lifecycle with no bridge access. The onboarding connection-seeduseEffectandworkHubActiverows stay.listTurnLandmarksprop toConversationLifecycle.On the merge:
--strict-baseagainst255ae23aepasses. The hook gate is 25 / 30, AppShell-family bridge references are 20, andapp-shell.tsxhas 0. Typecheck, lint, format, both knip runs, both inventories and ASF pass, and desktop tests are 3,187 / 3,187.Electron is 33 / 34.
session-workbar.spec.ts:179("Terminal survives navigation and reload…", the post-reloadtoBeVisibleat line 224) fails intermittently on main too, so it is not from this PR. Results of that spec alone,--repeat-each=8:8ad836ce1(main before #5934/#5935)255ae23ae(current main)63888f9fd(this PR before the merge)e542fbe28(this merge)Review focus
Overlap with the other R2 slices. All of them edit
app-shell.tsx, the ledger and the hook gate. Whichever lands second merges main and regenerates the ledger.refactor(desktop): own Composer readiness, new-task choices and submission below the shell #5935 (Composer readiness). Expect manual merges in:
app-shell.tsxscripts/check-app-shell-hooks.mjs(theuseStateline)composition/desktop-feature-services.tsx(both add imports and providers)features/workbar/controller/use-workbar-controller.ts, where both PRs edit the import block at lines 38–44chat-message-surface.tsx, the Conversation README,features/conversation/testing.tsand the slash-menu story are touched at non-adjacent lines. Its readiness can read the onboarding authority instead of a snapshot prop.chore(desktop): check R2 root symbol uses and retained root hooks #5934 (root symbol allowlist and retained-root rows). The new root symbols here, all from application contracts, need allowlist entries:
WorkHubEnablementWatch,OnboardingProjectionRoot,getOnboardingActivationCandidateandShellLifecycleSubscriptions.features/workhub/index.tsis no longer touched, so that textual conflict is gone.The rows above are proposed for its table. If chore(desktop): check R2 root symbol uses and retained root hooks #5934 lands first, the main merge here must also update its table:
useOnboardingSnapshot, theworkHubEnableduseStateand the WorkHub-enablementuseEffect;useEffect, which now reads theonboardingprop, andworkHubActive;useAppShellHostEffectsrow, since itsapp.inforead moved toShellLifecycleSourcesand only the titlebar modal sync remains;rootSymbolUses.The new
src/shared/onboarding-snapshot.d.ts. Declaration files are outside the checker's source index, so it adds nolegacyAppShellClosureentry.--strict-basepasses against bothc7fa6bb6aand1a66e4d5e.perf(desktop): bound transcript work on session switches #5712 (testikun).
conversation-lifecycle.tsx: it edits lines 109–117, this PR lines 46 and 149.conversation-owner.test.ts:111, where the harness now injectslistTurnLandmarksthrough stub services instead of a prop. Its rebase needs that one-line change.External PRs. Not designed around, as the issue now records:
features/conversation/ports.ts. ACP executor modes and scoped catalog lifecycle #5826's hunk at lines 89–95 is not adjacent to this PR's addition aftercompact.bridge-contract.d.tslines 327–350 move to a re-export; no open PR touches those lines.Naming.
ComposerStagingServicesnow also serves a transcript read. Renaming it (e.g.ConversationAttachmentServices) can be a follow-up.Verification
Run locally on Node 24.19.0 at
deeff8d6b(on1a66e4d5e), after a realnpm install,node scripts/apply-dependency-patches.mjs,node scripts/install-electron-with-retry.mjsandnpm --workspace @maka/desktop run build:workspace-deps:npm --workspace @maka/desktop run clean:main && … build:test && … test:dist: 3,165 / 3,165 pass (main has 3,143; 22 new). ThereadBytestests and the rail WorkHub-entry test were checked by reverting the behavior. The other new suites exercise modules this PR adds, and their source checks fail on the base files. The new or changed suites:composer-staging-owner.test.ts: transcript bytes through the port; a forced prop is overridden; the adapter; AppShell is free of the path.workhub-enablement.test.ts: the authority reads only while subscribed; a failed read keeps the value; StrictMode edges; the adapter.session-navigation-controller.test.ts:onboarding-authority.test.ts: the poller suite moved here; plus the authority lifecycle, the failure flag, skip then refresh, the projection root, the Desktop invalidations, and AppShell withoutsetMilestone.session-catalog-source.test.ts: refresh through the catalog source; the adapter.shell-lifecycle.test.ts:createShellLifecycleHandlersroutes each event to the same refreshes as before, including a Host that is not ready versus a ready default Host;data-oscancel;app-shell-effects.tsandapp-shell.tsxcontain nowindow.maka.workhub-enablement,onboarding-authorityandsession-catalog-source.conversation-owner.test.ts: landmarks injected through services only.npm --workspace @maka/desktop run build:with-deps && npx playwright test --config e2e/playwright.config.ts: 34 / 34 pass on479ec0ecf(3.7 min) and again on the review follow-updeeff8d6b(4.0 min). This includesworkhub-layout,workhub-reconstruction,workhub-pending-question,settings,session-local-recoveryandstreaming-remount. The run was on the same tree before the rebase onto1a66e4d5e, whose 5 files are outside the renderer and Electron app.onReadAttachmentBytes={window.maka.attachments.readBytes}toapp-shell.tsxfails the renderer typecheck with TS2322.npm run typecheck(preload, main, renderer, storybook): pass.node apps/desktop/scripts/check-renderer-architecture.mjs --write, thennpm run check:renderer-architecture -- --base 1a66e4d5e22067afd986d43fc634159e307a8e31 --strict-base: pass. It also passed againstc7fa6bb6abefore the rebase.npm run check:app-shell-hooks: ok, 27 / 39; inventory edited by hand.npm run lint,npm run format:check.npx knip --workspace apps/desktop,npx knip --workspace packages/ui.npm run windows:inventory(current, 119),npm run astryx:surface-inventory(ok),npm run check:asf-headers.Not covered directly:
smoke:storybook) was not run. The two stories changed only in their service fixtures, andtsconfig.storybook.jsontypechecks.AI use
Select exactly one:
Tool(s) and scope: Claude Opus 5.5 in Claude Code wrote the implementation, tests and PR text. The author chose the WorkHub enablement and onboarding placement and the landmark timing, reviewed the change, and decided to submit it. Every commit carries
Generated-by: Claude Opus 5.5.Checklist
Does this PR entail a change in behavior?