fix(desktop): guard localStorage collections against quota-exceeded rollback loops
Reworked from #5965 by @snehendu098 — thank you for the excellent diagnosis and the withQuotaGuard design, both kept here. Refs #5496 (fixes the freeze mechanism; the growth contributors are tracked separately in SUPER-1686). Opened as a new PR because the original fork doesn't allow maintainer edits and the branch needed reworking on top of #6036.
Problem (from #5965)
When localStorage is full, @tanstack/db's localStorageCollectionOptions re-serializes the whole collection on every mutation and rethrows QuotaExceededError. The library rolls the optimistic write back → the live query re-emits → the effect that queued the write retries — a synchronous ~40x/s loop that pegs the renderer at >110% CPU and survives restarts because the full store is on disk.
Fix
withQuotaGuard (unchanged from #5965) wraps every localStorage-backed collection's storage: on quota error it reclaims space, retries once, then returns without throwing. No throw → no rollback → the loop cannot form at any of the write sites. Non-quota errors still rethrow.
Changes from #5965
- Reclaim victim selection: driven by the terminal GC's durable
terminal-buffer-persisted-atindex from #6036 (24h pressure TTL, unstamped included) instead of registry membership + an in-memorypreservedSnapshotTerminalIdsset. The in-memory set reset on relaunch, so a quota event shortly after boot could delete restorable snapshots of terminals in not-yet-opened workspaces. This also deletesselectOrphanedTerminalSnapshots,terminalSnapshotStorage, and the two registry additions outright (−4 files, −36 registry lines). - Key prefixes stay canonical in
terminal-buffer-gc.ts— theshared/constants.tscopies are dropped. QUOTA_NOTICE_MODEships asoffer-reclaiminstead ofsilent: genuine exhaustion surfaces a toast with a working "Free up space" action (clears all persisted scrollback viaclearAllTerminalState) rather than a console-only warning the user never sees.
Testing
withQuotaGuardtests kept verbatim from #5965 (quota absorb, retry-once, non-quota rethrow, reclaim-0 skip).notifyQuotaExhaustedtests kept (warn dedupe, mode branches, toast id reuse).- New
terminal-buffer-gctests forreclaimTerminalStateForQuota(24h TTL, fresh kept, key counts, index hygiene — mutation-checked) andclearAllTerminalState. - 350 tests pass across the touched dirs;
bun run typecheck --filter=desktopandbun run lintclean. - CDP verification against the merged result to follow on this PR.
Summary by cubic
Prevents renderer-freezing retry loops when localStorage is full by guarding all @tanstack/react-db collections: on quota errors we reclaim space, retry once, then drop the write instead of throwing. Refs SUPER-1686 (fixes the freeze mechanism; growth contributors tracked separately).
-
Bug Fixes
- Added
withQuotaGuardto alllocalStorage-backed collections; non-quota errors still throw, quota errors reclaim once and return without throwing. - Eliminates the rollback/retry loop and keeps changes in memory for the session instead of pegging CPU.
- Added
-
New Features
- Quota reclaim is driven by the terminal GC’s durable
terminal-buffer-persisted-atindex with a 24h pressure TTL viareclaimTerminalStateForQuota(plusclearAllTerminalState). - User notice now uses
offer-reclaim: deduped warning and a toast with “Free up space” to clear saved terminal scrollback.
- Quota reclaim is driven by the terminal GC’s durable
Summary by CodeRabbit
-
New Features
- Added automatic recovery when local storage reaches its limit, helping preserve app responsiveness.
- Added user notifications when storage is full, including an option to free space by clearing saved terminal history.
- Added cleanup of older saved terminal snapshots to reclaim storage space.
-
Bug Fixes
- Prevented storage-limit errors from interrupting local state updates.
- Preserved in-memory changes when persistence temporarily fails.