fix(desktop): gate session restore on profile initialization - #74388
fix(desktop): gate session restore on profile initialization#74388DavidMetcalfe wants to merge 2 commits into
Conversation
d729bc7 to
7128f77
Compare
|
Thanks for tracing the profile-scoped storage read through the renderer boot sequence. The race is present on current main: Problems
Suggested changes
Automated hermes-sweeper review. |
After c4212b9 scoped remembered routes to per-profile localStorage keys, the cold-start restore effect reads $activeGatewayProfile.get() inside the effect body without subscribing. On mount the atom is 'default' (initial value), while the real profile is set asynchronously by adoptPrimaryProfile(). The restore fires first, reads the global unsuffixed key hermes.desktop.lastRoute, and finds the stale value from before per-profile scoping was introduced. Add $profileInitialized — set true after adoptPrimaryProfile (finally) and adoptBoot resolve the real profile — and gate the restore effect on it. The restoredRef logic is adjusted so a not-yet-initialized profile does not consume the ref, allowing the effect to retry once the flag lifts. Single-profile (default) users are unaffected: the global key IS the correct key when the profile is default.
…tion Regression coverage for the async ordering the fix gates: the restore effect must not read $activeGatewayProfile until adoptPrimaryProfile() resolves the real profile, or it restores the stale global localStorage keys instead of the named profile's scoped route/session.
7128f77 to
578e5cd
Compare
|
@teknium1 Good catch — the PR shipped without regression coverage for the async ordering, and the existing
Mutation-verified: removing the |
Description
After
c4212b945scopedgetRememberedRoute/setRememberedRouteto per-profile localStorage keys, the cold-start restore effect inuse-desktop-integrations.tsstill reads$activeGatewayProfile.get()inside the effect body without subscribing to it. On mount,$activeGatewayProfileis'default'(its initial value). The real profile is set asynchronously byadoptPrimaryProfile(). The restore effect fires first, reads from the global unsuffixed keyhermes.desktop.lastRoute, and finds the stale value from before per-profile scoping was introduced. The write side correctly writes to the scoped key — but that key is never read at startup, so every restart restores the stale global key.The fix
A
$profileInitializedatom (initializedfalse, settrueinadoptPrimaryProfile/adoptBootafter the real profile is resolved) gates the restore effect. The restore waits until the profile is known, then reads from the correct scoped key.Simply adding
$activeGatewayProfileto the dependency array is insufficient — the first render still runs with'default', andrestoredRefblocks the second run with the correct profile. A separate readiness signal is needed.Changes
store/profile.ts: Add$profileInitializedatomapp/gateway/hooks/use-gateway-boot.ts: Set$profileInitializedafter profile resolution inadoptPrimaryProfile()(finally block) andadoptBoot()app/contrib/hooks/use-desktop-integrations.ts: Subscribe to$profileInitializedviauseStore, gate the restore effect onprofileInitialized, add to dependency arrayReviewer Notes
restoredReflogic is adjusted: when!profileInitialized, we return WITHOUT settingrestoredRef.current = true, so the effect re-runs whenprofileInitializedbecomes true. The original behavior (setrestoredRefwhen not atNEW_CHAT_ROUTE) is preserved.rememberedRouteKey('default')returns the global unsuffixed key — the same key used before scoping. No behavioral change for single-profile users.adoptBoot()HMR path also sets$profileInitializedto cover the case where the Desktop window persists across a hot-reload update.finallyblock inadoptPrimaryProfileguarantees$profileInitializedis set even when the IPC call throws or profile setup fails — the error path falls back to'default'in thecatch, and thefinallyunblocks the restore effect regardless.Closes #74387