refactor(desktop): move App Update controller below AppShell - #4498
refactor(desktop): move App Update controller below AppShell#4498chihumyum wants to merge 2 commits into
Conversation
628a5ec to
d5cd6e9
Compare
|
@Astro-Han could you please review exact head I rebased it onto Exact-head local verification passes: rebuild; renderer architecture 71/71 plus the base ratchet; App Update 22/22; Desktop 1,982/1,982; UI 313/313; typecheck, lint, format, Desktop/UI Knip, ASF headers, AppShell hook scope, Astryx inventory; and the real Electron window smoke. The new hosted CI and Windows package checks are currently queued. |
d5cd6e9 to
076fb61
Compare
|
Rebased onto current How the two real conflicts were resolved:
Local verification on this head with Node 24: desktop Automated update from Claude Code on behalf of the PR author. |
076fb61 to
750ee05
Compare
|
Rebased again onto current Same local verification on this head with Node 24: desktop Automated update from Claude Code on behalf of the PR author. |
750ee05 to
8cc5de4
Compare
|
Rebased onto Posted by Claude Code on behalf of the PR author. |
8cc5de4 to
8a30f75
Compare
|
Rebased once more onto Posted by Claude Code on behalf of the PR author. |
… namespaces through (#4590) Nine renderer feature slices each restated the same twenty-line services context, and their Desktop adapters hand-wrote forwarders whose signatures equal the bridge methods they forward to. Every new slice paid that join cost again, and the copies could drift apart without any test noticing. `application/contracts/feature-services.tsx` now exports `createServicesContext<S>(providerName)`, which returns the Provider and hook pair. Each slice's `services-context.tsx` shrinks to a few lines while keeping every exported name, type, and error message. Desktop adapters hand a bridge namespace through where the port is a structural subset of it (`sessions`, `newTasks`, `scheduledTasks`, `shellRuns`, `todo`, `attachments`), removing 33 identity forwarders. Adapter blocks that rename, guard, filter, or translate stay hand-written. Preload namespaces are plain objects with no `this` usage, so passing one through is runtime-safe, and the port types stay narrow, so nothing new is reachable from feature code. `composition/desktop-feature-services.tsx` is untouched; the tenth slice from #4498 can adopt the factory after it lands. No behavior change. Refs #4582 Generated-by: Claude Code
… namespaces through (apache#4590) Nine renderer feature slices each restated the same twenty-line services context, and their Desktop adapters hand-wrote forwarders whose signatures equal the bridge methods they forward to. Every new slice paid that join cost again, and the copies could drift apart without any test noticing. `application/contracts/feature-services.tsx` now exports `createServicesContext<S>(providerName)`, which returns the Provider and hook pair. Each slice's `services-context.tsx` shrinks to a few lines while keeping every exported name, type, and error message. Desktop adapters hand a bridge namespace through where the port is a structural subset of it (`sessions`, `newTasks`, `scheduledTasks`, `shellRuns`, `todo`, `attachments`), removing 33 identity forwarders. Adapter blocks that rename, guard, filter, or translate stay hand-written. Preload namespaces are plain objects with no `this` usage, so passing one through is runtime-safe, and the port types stay narrow, so nothing new is reachable from feature code. `composition/desktop-feature-services.tsx` is untouched; the tenth slice from apache#4498 can adopt the factory after it lands. No behavior change. Refs apache#4582 Generated-by: Claude Code Generated-by: GLM-5.3-Flash (ZCode)
8a30f75 to
a54ef20
Compare
|
Rebased onto
Exact head: |
a54ef20 to
08c27ef
Compare
|
Rebased onto |
08c27ef to
96a84dc
Compare
|
The run on |
Astro-Han
left a comment
There was a problem hiding this comment.
Reviewed at 96a84dc. All four done-conditions from #4582 hold: the two effects, one state and two refs leave the shell render body (check-app-shell-hooks.mjs 14→12 / 15→14), the four window.maka.* update paths leave app-shell.tsx and the three in about-settings-page.tsx go with them, useAppUpdateController → AppUpdateProvider is in controllerOwners, and the same PR deletes app-shell-app-update.ts, the nine shell-copy keys, the updateReminder / onOpenUpdate threading through SessionRailChrome, and the second copy of AppUpdateStatus in preload and main. The hand-removed ownership entry is the one the checker would otherwise reject (check-renderer-architecture.mjs:2346), so the ledger edit is consistent with regeneration.
The case for the move is stronger than the earlier slices: main had two subscribers to the same status (AppShellContent and About), and the shell one fed download progress into a useState on the renderer root, which is what the 512 → 1 fibers number is measuring. It also fixes a real ordering bug on the About page: the old effect read before it subscribed with no fence, so a slow updateStatus() could overwrite a newer push. The revision counter and the subscribe-then-read test pin that. I compared the nine copy keys across zh-CN / zh-TW / en against the deleted shell-copy.ts entries: identical.
No P0–P2. Five small things, none blocking, take what you want in one pass:
app-update-projection-context.tsgives the About projection a silent default (inactiveAboutProjection), wheremodule-hub-provider.tsx:118throwsProvider is missing. A stray About mount outside the Provider then sits on "not checked yet" forever and the button toasts. Make the desktop context nullable and throw on read; thepackages/uione can keep its default since it explicitly serves other hosts.- Three cases in
app-update-boundary.test.tsadd no constraint: exclusive owner and index/testing-only entry are whatcheck-renderer-architecture.mjs:1865and:2087already enforce, and the "recognizes alias / namespace / re-export / dynamic import" case tests the helper against inline strings. Keep the cross-package sidebar binding, the raw-capability ban and the mount-point checks; drop those three (~70 lines). Same point we settled on #4315. isDevBuildis dead inabout-status-detail.ts(its own comment says there is no dev-build branch) and this PR threads it through a newAboutUpdateRowprop. Delete the option, the prop and the call site.localizedAppUpdateErrorMessageinapp-update-copy.tsonly forwards togeneralizedErrorMessageForLocale, the same shell it copies fromshell-copy.ts. Call the original.create-app-update-services.tshands the feature the wholebridge.appnamespace (icons,sessionProjectInfo,infoincluded) and the adapter test asserts identity on it.create-goal-services.tscloses over the methods it needs; do the same here and drop the===assertion.
Two things before merge. The branch went CONFLICTING a few minutes ago because #4640 and #4823 both landed on renderer-architecture.json; please regenerate rather than hand-merge, and replay the ownership deletion. And test / package were still pending on this head when I read it; the approval stands across the rebase per CONTRIBUTING, but the exact head that merges needs green CI, including the session-workbar.spec.ts line that was red on main.
#4491 shares the ledger, app-shell.tsx, the hook gate and the Astryx inventory with this PR, so whichever lands second regenerates once; I would land this one first since it deletes more.
Manual acceptance still owed on a packaged build, both themes: About page across the eight update states with the percentage moving during downloading; the check button disabling and recovering, including closing and reopening Settings mid-check (the one semantic shift, since checking moved from the page to the Provider); the dev checkout still rendering no status row; the sidebar footer button appearing only for downloaded and versioned error; the install-failure toast and the running-tasks confirm, three locales.
Evidence boundary: static read at 96a84dc against main; no tests, build, Storybook or app run, so the fiber counts and suite results are the body's, not mine.
AI-assisted review: drafted with Maka; I verified the four done-conditions, the copy parity, the ownership edit and the checker overlap myself.
Generated-by: OpenAI Codex
Make the About projection context nullable and throw on a read outside AppUpdateProvider, drop the three boundary cases the architecture checker already proves, remove the dead isDevBuild option and prop, call generalizedErrorMessageForLocale directly instead of a forwarding wrapper, and hand the feature only the five update capabilities its port names instead of the whole app bridge namespace.
96a84dc to
17ed533
Compare
|
Rebased onto
Exact head: The packaged-build manual acceptance you listed is still owed and is on the author. |
Summary
Move the renderer-owned App Update lifecycle out of AppShell into a feature slice, with
AppUpdateProvideras the sole production owner ofuseAppUpdateController.No user-visible copy, update protocol, or install behavior is intended to change.
Refs #4582 (supersedes #3439)
Performance evidence
Measured in Electron 43.4.1 with a production React bundle, the same renderer PID, and alternating legacy/scoped runs:
The structural run used 8 rounds x 40 pushes, the pure renderer run 9 alternating rounds x 1,000 pushes, and the full propagation run 7 alternating rounds x 200 pushes. The full path used a real
BrowserWindow, Electron IPC, the production preload bundle, Desktop adapter, and App Update Provider; only the main status producer was a fixture.These measurements cover controlled renderer update propagation. They do not claim faster updater downloads or installs, lower main-process/network/disk cost, total wall/FPS improvement, or an exact production AppShell fiber count.
Verification
npm run rebuild,npm run typecheck,npm run lint, andnpm run format:checkapps/desktopandpackages/ui, ASF headers, stale-dist, andgit diff --checkNODE_NO_WARNINGS=1, Storage passed 1,077 tests with 8 skippednode scripts/desktop-real-window-smoke.mjs --programmatic-only --diagnostic-wait-ms 20000passed against the built renderer: visible 1280x840 movable/resizable window, renderer ready,appFramemounted, Search focused, dock visible, and no ErrorBoundaryThe real-window smoke used a source-built Electron app, not a packaged or signed installer; packaged updater download/install remains outside this PR's evidence.
Review focus
Please review the ownership guard and the two independent projections together: the feature should retain the only update subscription and bridge capability, while About and Sidebar Footer receive only the state needed at their render boundaries.
AI use
Select exactly one:
Tool(s) and scope: OpenAI Codex contributed substantive architecture design, implementation, tests, performance measurement, and verification. The human contributor reviewed the work and chose to submit it.
Checklist
Does this PR entail a change in behavior?