Skip to content

Commit 6248a30

Browse files
authored
🤖 fix: persist localStorage writes synchronously to prevent data loss (#1036)
## Problem Reviews attached to chat were lost on Electron restart because localStorage writes were deferred via `queueMicrotask`. If the app closed before the microtask ran (e.g., force quit, crash, or immediate close), the data was never persisted. ## Root Cause `usePersistedState` hook was using `queueMicrotask` to batch localStorage writes for "performance optimization" (added in #330). However, this introduced a race condition: 1. User creates a review → React state updates immediately 2. Microtask queued to write to localStorage 3. User closes app (or it crashes) → Microtask never runs 4. On restart → localStorage has stale/missing data ## Solution Remove `queueMicrotask` and write to localStorage synchronously. This ensures data persists immediately after state change, at the cost of a small blocking operation (~1-2ms for typical JSON.stringify + setItem). The synchronous write is acceptable because: - localStorage writes are fast (~1-2ms for typical data) - Data integrity is more important than micro-optimization - The deferred write was a premature optimization that caused real bugs ## Testing - [x] `make typecheck` passes - [x] `make lint` passes - [x] `make static-check` passes - [x] Existing tests pass _Generated with `mux`_
1 parent 6488f22 commit 6248a30

File tree

1 file changed

+19
-19
lines changed

1 file changed

+19
-19
lines changed

‎src/browser/hooks/usePersistedState.ts‎

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -146,27 +146,27 @@ export function usePersistedState<T>(
146146
setState((prevState) => {
147147
const newValue = value instanceof Function ? value(prevState) : value;
148148

149-
// Batch localStorage writes in microtask to avoid blocking UI
150-
queueMicrotask(() => {
151-
if (typeof window !== "undefined" && window.localStorage) {
152-
try {
153-
if (newValue === undefined || newValue === null) {
154-
window.localStorage.removeItem(key);
155-
} else {
156-
window.localStorage.setItem(key, JSON.stringify(newValue));
157-
}
158-
159-
// Dispatch custom event for same-tab synchronization
160-
// Include origin marker to prevent echo
161-
const customEvent = new CustomEvent(getStorageChangeEvent(key), {
162-
detail: { key, newValue, origin: componentIdRef.current },
163-
});
164-
window.dispatchEvent(customEvent);
165-
} catch (error) {
166-
console.warn(`Error writing to localStorage key "${key}":`, error);
149+
// Write to localStorage synchronously to ensure data persists
150+
// even if app closes immediately after (e.g., Electron quit, crash).
151+
// This fixes race condition where queueMicrotask deferred writes could be lost.
152+
if (typeof window !== "undefined" && window.localStorage) {
153+
try {
154+
if (newValue === undefined || newValue === null) {
155+
window.localStorage.removeItem(key);
156+
} else {
157+
window.localStorage.setItem(key, JSON.stringify(newValue));
167158
}
159+
160+
// Dispatch custom event for same-tab synchronization
161+
// Include origin marker to prevent echo
162+
const customEvent = new CustomEvent(getStorageChangeEvent(key), {
163+
detail: { key, newValue, origin: componentIdRef.current },
164+
});
165+
window.dispatchEvent(customEvent);
166+
} catch (error) {
167+
console.warn(`Error writing to localStorage key "${key}":`, error);
168168
}
169-
});
169+
}
170170

171171
return newValue;
172172
});

0 commit comments

Comments
 (0)