fix(desktop): guard localStorage collections against quota-exceeded rollback loops

Merged
KI
3mo ago
+743-60

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-at index from #6036 (24h pressure TTL, unstamped included) instead of registry membership + an in-memory preservedSnapshotTerminalIds set. 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 deletes selectOrphanedTerminalSnapshots, terminalSnapshotStorage, and the two registry additions outright (−4 files, −36 registry lines).
  • Key prefixes stay canonical in terminal-buffer-gc.ts — the shared/constants.ts copies are dropped.
  • QUOTA_NOTICE_MODE ships as offer-reclaim instead of silent: genuine exhaustion surfaces a toast with a working "Free up space" action (clears all persisted scrollback via clearAllTerminalState) rather than a console-only warning the user never sees.

Testing

  • withQuotaGuard tests kept verbatim from #5965 (quota absorb, retry-once, non-quota rethrow, reclaim-0 skip).
  • notifyQuotaExhausted tests kept (warn dedupe, mode branches, toast id reuse).
  • New terminal-buffer-gc tests for reclaimTerminalStateForQuota (24h TTL, fresh kept, key counts, index hygiene — mutation-checked) and clearAllTerminalState.
  • 350 tests pass across the touched dirs; bun run typecheck --filter=desktop and bun run lint clean.
  • CDP verification against the merged result to follow on this PR.

Open in Stage

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 withQuotaGuard to all localStorage-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.
  • New Features

    • Quota reclaim is driven by the terminal GC’s durable terminal-buffer-persisted-at index with a 24h pressure TTL via reclaimTerminalStateForQuota (plus clearAllTerminalState).
    • User notice now uses offer-reclaim: deduped warning and a toast with “Free up space” to clear saved terminal scrollback.

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.