| ... | ... | @@ -63,6 +63,8 @@ function readPersistedState(): AppState { |
| 63 | 63 | } |
| 64 | 64 | } |
| 65 | 65 | |
| 66 | let legacyKeysCleanedUp = false; |
| 67 | |
| 66 | 68 | function persistState(state: AppState): void { |
| 67 | 69 | if (typeof window === "undefined") return; |
| 68 | 70 | try { |
| ... | ... | @@ -74,14 +76,29 @@ function persistState(state: AppState): void { |
| 74 | 76 | .map((project) => project.cwd), |
| 75 | 77 | }), |
| 76 | 78 | ); |
| 77 | | for (const legacyKey of LEGACY_PERSISTED_STATE_KEYS) { |
| 78 | | window.localStorage.removeItem(legacyKey); |
| 79 | if (!legacyKeysCleanedUp) { |
| 80 | legacyKeysCleanedUp = true; |
| 81 | for (const legacyKey of LEGACY_PERSISTED_STATE_KEYS) { |
| 82 | window.localStorage.removeItem(legacyKey); |
| 83 | } |
| 79 | 84 | } |
| 80 | 85 | } catch { |
| 81 | 86 | // Ignore quota/storage errors to avoid breaking chat UX. |
| 82 | 87 | } |
| 83 | 88 | } |
| 84 | 89 | |
| 90 | let persistTimer: ReturnType<typeof setTimeout> | null = null; |
| 91 | |
| 92 | function debouncedPersistState(state: AppState): void { |
| 93 | if (persistTimer !== null) { |
| 94 | clearTimeout(persistTimer); |
| 95 | } |
| 96 | persistTimer = setTimeout(() => { |
| 97 | persistTimer = null; |
| 98 | persistState(state); |
| 99 | }, 500); |
| 100 | } |
| 101 | |
| 85 | 102 | // ── Pure helpers ────────────────────────────────────────────────────── |
| 86 | 103 | |
| 87 | 104 | function updateThread( |
| ... | ... | @@ -395,8 +412,19 @@ export const useStore = create<AppStore>((set) => ({ |
| 395 | 412 | set((state) => setThreadBranch(state, threadId, branch, worktreePath)), |
| 396 | 413 | })); |
| 397 | 414 | |
| 398 | | // Persist on every state change |
| 399 | | useStore.subscribe((state) => persistState(state)); |
| 415 | // Persist state changes with debouncing to avoid localStorage thrashing |
| 416 | useStore.subscribe((state) => debouncedPersistState(state)); |
| 417 | |
| 418 | // Flush pending writes synchronously before page unload to prevent data loss. |
| 419 | if (typeof window !== "undefined") { |
| 420 | window.addEventListener("beforeunload", () => { |
| 421 | if (persistTimer !== null) { |
| 422 | clearTimeout(persistTimer); |
| 423 | persistTimer = null; |
| 424 | persistState(useStore.getState()); |
| 425 | } |
| 426 | }); |
| 427 | } |
| 400 | 428 | |
| 401 | 429 | export function StoreProvider({ children }: { children: ReactNode }) { |
| 402 | 430 | useEffect(() => { |