fix(web): reset scroll restoration when sessionStorage quota hits#707
Merged
tiann merged 3 commits intoMay 26, 2026
Merged
Conversation
TanStack keeps scroll state in RAM; pruning only the JSON blob did not stop repeat quota throws. On persist failure for the scroll key, clear the library cache when guarding real sessionStorage. Treat any write error on that key (not only QuotaExceededError-shaped) as recoverable. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Findings
- [Major] Successful prune still leaves TanStack cache oversized — when the trimmed retry succeeds, the persisted JSON shrinks but
scrollRestorationCachekeeps the full in-memory map, so the next TanStack persist serializes the huge state again and re-enters the quota path. Evidenceweb/src/lib/scrollStorageGuard.ts:102. - [Major] Hard reset can recurse through the guarded setter —
scrollRestorationCache.setpersists throughsessionStorage.setItem, which is already replaced bywrappedSetItem; if even the{}write fails, it re-enters the same hard-reset path until stack overflow. Evidenceweb/src/lib/scrollStorageGuard.ts:36.
Summary
- Review mode: initial
- The patch does not fully clear TanStack Router memory on the common successful-prune path, and the failure reset path can recursively call itself.
- Coverage gap: tests use a mock
Storage, so they skip the real-sessionStorage cache reset branch.
Testing
- Not run (automation):
bunis not installed in this runner.
HAPI Bot
Exercise TanStack scrollRestorationCache reset on real sessionStorage, mock-storage isolation, and generic storage write failures for tiann#708. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Findings
- [Major] Successful prune still leaves TanStack cache oversized — the new hard reset only runs when parsing or the trimmed retry fails, but when
originalSetItemaccepts the trimmed JSON the closed-overscrollRestorationCache.stateremains the full oversized object. TanStack’s next scroll update mutates and serializes that same large state again, so the UI can hit the same storage exception immediately after a “successful” recovery. Evidenceweb/src/lib/scrollStorageGuard.ts:102. - [Major] Hard reset can recurse through the guarded setter —
scrollRestorationCache.set(() => ({}))writes via the capturedsessionStorage.setItem, which is already replaced bywrappedSetItem; if the{}write throws, the wrapper re-entershardResetScrollRestorationPersistedStatebefore the fallback can run. Evidenceweb/src/lib/scrollStorageGuard.ts:36.
Summary
- Review mode: follow-up after new commits
- The follow-up added tests, but the production guard still has the two failure modes from the previous bot review. The current tests mock
scrollRestorationCache.setas a pure function, so they do not exercise TanStack’s real setter writing back throughsessionStorage.setItem.
Testing
- Not run (automation):
bunis not installed in this runner.
HAPI Bot
After a successful storage prune, align TanStack's in-memory scrollRestorationCache with the trimmed payload. Temporarily unwrap sessionStorage.setItem when writing through the library cache so hard reset cannot recurse through the guard (Codex PR review tiann#707). Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Findings
- None.
Summary
- Review mode: follow-up after new commits
- No issues found in the latest PR diff. The previous two bot findings are addressed in
web/src/lib/scrollStorageGuard.ts: the successful prune path now syncs TanStack's in-memory cache, and cache writes temporarily restore the originalsetItemto avoid recursive guarded writes. Residual risk: this still depends on TanStack Router's privatetsr-scroll-restoration-v1_3storage key andscrollRestorationCacheinternals.
Testing
- Not run (automation):
bunis not installed in this runner. - Reviewed contributor-reported coverage in
web/src/lib/scrollStorageGuard.test.tsfor successful prune cache sync and unwrapped hard reset writes.
HAPI Bot
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
sessionStorage.setItemfailed we could prune the JSON on disk but the in-RAM map stayed huge, so the very next scroll persist threw again and the UI still hit the error boundary (fix(web): TanStack scroll restoration fills sessionStorage and can crash the SPA #708).tsr-scroll-restoration-v1_3, after prune+retry fails (or JSON is invalid), clear storage and callscrollRestorationCache.set(() => ({}))when the guarded target is realwindow.sessionStorage(skipped in unit tests that use a mockStorage).QuotaExceededError/ DOMException-shaped quota errors).Test plan
cd web && bun run test src/lib/scrollStorageGuard.test.ts(11 tests)cd web && bun run typechecksetItem/ no white-screen error boundaryIssues
Fixes #708