fix(desktop): stop Settings Workspace from sticking on skeleton under Strict Mode - #74829
fix(desktop): stop Settings Workspace from sticking on skeleton under Strict Mode#74829zihaochen68 wants to merge 2 commits into
Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the focused Strict Mode regression coverage. The reported premise is present on current main: the app runs under StrictMode at apps/desktop/src/main.tsx:46, and useOnProfileSwitch's one-shot guard calls its callback on the next effect pass at apps/desktop/src/app/hooks/use-on-profile-switch.ts:10-24.
Problems
resetHermesConfig()atapps/desktop/src/app/settings/config-settings.tsx:119only runs whileConfigSettingsis mounted. The Capabilities detail preservesVoiceProviderFieldsby toolset name (apps/desktop/src/app/skills/index.tsx:331-333,821), while that component retains a one-shot config draft and autosaves it (apps/desktop/src/app/settings/voice-provider-fields.tsx:47-74) without profile-switch handling. A profile-A draft can therefore survive a live swap and be saved against B.
Suggested changes
- Extend the profile-transition reset/cancellation to
VoiceProviderFields, or centralize it at the shared config-record boundary. Add a mounted A→B voice-panel regression proving it reseeds B and never saves A's draft.
Automated hermes-sweeper review.
| savedDiscoverySignatureRef.current = undefined | ||
| setConfig(null) | ||
| saveVersionRef.current = 0 | ||
| setSaveVersion(0) | ||
| void resetHermesConfig() |
There was a problem hiding this comment.
This reset only runs when ConfigSettings is mounted. VoiceProviderFields can remain mounted in the Capabilities detail across the same live profile swap (apps/desktop/src/app/skills/index.tsx:331-333,821), but it retains a one-shot local config draft and debounced saveHermesConfig write with no switch handler (apps/desktop/src/app/settings/voice-provider-fields.tsx:47-74). Please cover that sibling so profile A's draft cannot be saved through profile B's active route.
There was a problem hiding this comment.
Good catch — fixed, and you were right that the deeper issue was where the reset lived. I took the "centralize it at the shared config-record boundary" option: invalidateProfileScopedQueries() (already called from the store/profile.ts subscriber and store/gateway-switch.ts) now hard-resets hermes-config-record via resetQueries while everything else keeps stale-while-revalidate invalidation. The reset no longer depends on any particular panel being mounted, and ConfigSettings just drops its local draft like every other consumer.
While auditing the remaining saveHermesConfig / useHermesConfigRecord consumers for the same class of bug, I found and fixed two more sibling paths beyond VoiceProviderFields — details in the PR comment below.
cdd3488 to
fad3c1f
Compare
|
Updated per review — rebased onto current Centralized boundary (
Sibling paths audited (every
Tests
|
fad3c1f to
c564086
Compare
|
@teknium1 gentle ping — the sibling-path feedback (centralized config-record reset + VoiceProviderFields / AutoArchiveSetting / ModelSettings) is in |
c564086 to
b41c32f
Compare
|
@teknium1 gentle follow-up — still hoping for a re-review when you have a moment. The sibling-path feedback (centralized Also for triage context: this PR supersedes the narrower hook-only fix in #61826 (same Strict Mode |
… drafts Rebased onto current main (per-profile hermesConfigKey preserved). - Compare normalized profile keys in useOnProfileSwitch (Strict Mode safe) - State-derived draft reseeding in ConfigSettings / VoiceProviderFields - Central hermes-config-record hard reset in invalidateProfileScopedQueries - Cancel pending autosaves / drop AutoArchive copies on profile switch - Version-/epoch-guard in-flight cache writes (ConfigSettings, ModelSettings) Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
b41c32f to
21dfb5b
Compare
|
@teknium1 gentle follow-up — still hoping for a re-review when you have a moment. Just rebased onto current
Also noting #61826 was closed unmerged — this PR still covers that hook fix and the broader draft/cross-profile save class from review. Happy to adjust further if anything else stands out. |
What does this PR do?
Fixes Desktop Settings → Workspace (and other
ConfigSettingstabs) getting stuck on the skeleton forever under React Strict Mode, and closes the related class of bugs where a mounted settings draft from profile A can be saved through profile B's route after a live gateway/profile switch.Reported failure chain (
ConfigSettingsskeleton):useOnProfileSwitchused a one-shotfirstflag; Strict Mode's second effect pass looked like a profile switch and cleared the local config draft.loadedConfig, so React Query structural sharing prevented re-seeding when the draft was empty but the cached reference was unchanged.Sibling failure class (review feedback):
resetlived insideConfigSettings, so it only ran when that panel was mounted. Surfaces that stay mounted across a profile swap (notablyVoiceProviderFieldsin Capabilities) kept a one-shot draft + debouncedsaveHermesConfigwith no switch handler — profile A's draft could be PUT into profile B.Approach:
useOnProfileSwitch(not a first-mount flag).config === nulland data is available (state-derived, self-healing).hermes-config-recordinsideinvalidateProfileScopedQueries()(resetQueries—setQueryData(undefined)is a bail-out in RQ v5), so every consumer drops profile A's record on switch regardless of which panels are mounted.Related Issue
Fixes #74824
Type of Change
Changes Made
apps/desktop/src/app/hooks/use-on-profile-switch.ts(+ test) — normalized key comparison; Strict Mode safeapps/desktop/src/lib/query-client.ts(+ test) — centralhermes-config-recordhard reset on profile/gateway switch;HERMES_CONFIG_QUERY_KEYowned hereapps/desktop/src/app/hooks/use-config-record.ts— re-exports the shared key (no per-panelresetHermesConfig)apps/desktop/src/app/settings/config-settings.tsx(+ test) — state-derived seeding; drop draft on switch; version-guard post-save cache mirrorapps/desktop/src/app/settings/voice-provider-fields.tsx(+ test) — same draft/autosave/switch pattern (Capabilities TTS panel stays mounted)apps/desktop/src/app/settings/sessions-settings.tsx(+ test) —AutoArchiveSettingdrops whole-record copy and refetches on switchapps/desktop/src/app/settings/model-settings.tsx— optimistic rollback only within the same profile epochapps/desktop/src/app/settings/toolset-config-panel.test.tsx— hermes mock stubs for profile-switch importsHow to Test
cd apps/desktop npx vitest run \ src/app/hooks/use-on-profile-switch.test.tsx \ src/app/settings/config-settings.test.tsx \ src/app/settings/voice-provider-fields.test.tsx \ src/app/settings/sessions-settings.test.tsx \ src/lib/query-client.test.tsnpm run dev→ Settings → Workspace; fields render (not a permanent skeleton) under Strict Mode.Checklist
Code
fix(desktop): …)npx vitest runinapps/desktopDocumentation & Housekeeping
cli-config.yaml.example— N/ACONTRIBUTING.mdorAGENTS.md— N/A