From e43cb2f026418a33dc1aa53e4312fcead2399e72 Mon Sep 17 00:00:00 2001 From: swear01 Date: Thu, 28 May 2026 06:49:40 +0000 Subject: [PATCH 1/2] chore(web): upgrade @tanstack/react-router to ^1.170.8 Fixes QuotaExceededError in scroll restoration: upstream @tanstack/react-router >=1.145.6 wraps sessionStorage.setItem with try-catch, preventing the crash when scroll restoration cache exceeds quota. Refs: #683, #716, #721 --- bun.lock | 23 +++++++++++------------ web/package.json | 3 ++- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/bun.lock b/bun.lock index e3d5919415..e6f60e4cd2 100644 --- a/bun.lock +++ b/bun.lock @@ -103,7 +103,8 @@ "@shikijs/themes": "^3.20.0", "@tanstack/react-query": "^5.90.12", "@tanstack/react-query-devtools": "^5.91.1", - "@tanstack/react-router": "^1.143.6", + "@tanstack/react-router": "^1.170.8", + "@tanstack/router-core": "^1.171.6", "@xterm/addon-canvas": "^0.7.0", "@xterm/addon-fit": "^0.11.0", "@xterm/addon-web-links": "^0.12.0", @@ -1031,7 +1032,7 @@ "@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="], - "@tanstack/history": ["@tanstack/history@1.141.0", "", {}, "sha512-LS54XNyxyTs5m/pl1lkwlg7uZM3lvsv2FIIV1rsJgnfwVCnI+n4ZGZ2CcjNT13BPu/3hPP+iHmliBSscJxW5FQ=="], + "@tanstack/history": ["@tanstack/history@1.162.0", "", {}, "sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA=="], "@tanstack/query-core": ["@tanstack/query-core@5.90.12", "", {}, "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg=="], @@ -1041,13 +1042,13 @@ "@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.91.1", "", { "dependencies": { "@tanstack/query-devtools": "5.91.1" }, "peerDependencies": { "@tanstack/react-query": "^5.90.10", "react": "^18 || ^19" } }, "sha512-tRnJYwEbH0kAOuToy8Ew7bJw1lX3AjkkgSlf/vzb+NpnqmHPdWM+lA2DSdGQSLi1SU0PDRrrCI1vnZnci96CsQ=="], - "@tanstack/react-router": ["@tanstack/react-router@1.143.6", "", { "dependencies": { "@tanstack/history": "1.141.0", "@tanstack/react-store": "^0.8.0", "@tanstack/router-core": "1.143.6", "isbot": "^5.1.22", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-UqMX0xasL7ZTWPrDkXDnjRe/1zQt3zGZ/kybvdhwDoi+pfJcJFm1jXCwyWtIxWLkJ+oWaAWlVGbxyt353EE8HQ=="], + "@tanstack/react-router": ["@tanstack/react-router@1.170.8", "", { "dependencies": { "@tanstack/history": "1.162.0", "@tanstack/react-store": "^0.9.3", "@tanstack/router-core": "1.171.6", "isbot": "^5.1.22" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-Qw2ju6jjnIsMpuW+VrnHZWHuugqs592PWsnI56sG28qNhg14CgRLahOcNajfuJR9P4MxKGP94WVzmFKSYUz/ig=="], - "@tanstack/react-store": ["@tanstack/react-store@0.8.0", "", { "dependencies": { "@tanstack/store": "0.8.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-1vG9beLIuB7q69skxK9r5xiLN3ztzIPfSQSs0GfeqWGO2tGIyInZx0x1COhpx97RKaONSoAb8C3dxacWksm1ow=="], + "@tanstack/react-store": ["@tanstack/react-store@0.9.3", "", { "dependencies": { "@tanstack/store": "0.9.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg=="], - "@tanstack/router-core": ["@tanstack/router-core@1.143.6", "", { "dependencies": { "@tanstack/history": "1.141.0", "@tanstack/store": "^0.8.0", "cookie-es": "^2.0.0", "seroval": "^1.4.1", "seroval-plugins": "^1.4.0", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" } }, "sha512-NOdkn0lQNNjtyqns8lzZrq05mK3UdIOLR6WyH/reCwTqQ5w7lflquqM0AqbDuBA4iT2hOdNf1lS9NdW9esE2ig=="], + "@tanstack/router-core": ["@tanstack/router-core@1.171.6", "", { "dependencies": { "@tanstack/history": "1.162.0", "cookie-es": "^3.0.0", "seroval": "^1.5.4", "seroval-plugins": "^1.5.4" } }, "sha512-Ol6DQ+j6rf/rPVELIzo8LHwOQV2KL+zry3b+39kL/GKrt7YId52WJRAFMzuseY4XceSW+PU7sG/Cc1QkwJr0hg=="], - "@tanstack/store": ["@tanstack/store@0.8.0", "", {}, "sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ=="], + "@tanstack/store": ["@tanstack/store@0.9.3", "", {}, "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw=="], "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], @@ -1065,7 +1066,7 @@ "@twsxtd/hapi-linux-x64": ["@twsxtd/hapi-linux-x64@0.18.4", "", { "os": "linux", "cpu": "x64", "bin": { "hapi": "bin/hapi" } }, "sha512-VHdCKlJ3G1JOqVG7lDpeY7RJflfRp1qHb6WMuXf/a5g7g/UljPmLH8QfF/H/DbOXjuLVcBeqihEqBngYu+CIlw=="], - "@twsxtd/hapi-win32-x64": ["@twsxtd/hapi-win32-x64@0.18.3", "", { "os": "win32", "cpu": "x64", "bin": { "hapi": "bin/hapi.exe" } }, "sha512-kEG8tH5OcY/dRBpqbLmb3q2CS0UiM9L3OQX0LWvCn1aI3JQKaaZvzXElCXuwiIml0AI4fBAfofzUB/E31lxffw=="], + "@twsxtd/hapi-win32-x64": ["@twsxtd/hapi-win32-x64@0.18.4", "", { "os": "win32", "cpu": "x64", "bin": { "hapi": "bin/hapi.exe" } }, "sha512-MsIWaxFeRIbt4oqZnIMoJeLLCE4ZnU6qUin+RjrrmYKxW4p8ciaMrbUNXD5bGY/K50JlJwI6BZMX98UQOUr7uA=="], "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], @@ -1481,7 +1482,7 @@ "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - "cookie-es": ["cookie-es@2.0.0", "", {}, "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg=="], + "cookie-es": ["cookie-es@3.1.1", "", {}, "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg=="], "cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="], @@ -2715,9 +2716,9 @@ "serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="], - "seroval": ["seroval@1.4.1", "", {}, "sha512-9GOc+8T6LN4aByLN75uRvMbrwY5RDBW6lSlknsY4LEa9ZmWcxKcRe1G/Q3HZXjltxMHTrStnvrwAICxZrhldtg=="], + "seroval": ["seroval@1.5.4", "", {}, "sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw=="], - "seroval-plugins": ["seroval-plugins@1.4.0", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-zir1aWzoiax6pbBVjoYVd0O1QQXgIL3eVGBMsBsNmM8Ukq90yGaWlfx0AB9dTS8GPqrOrbXn79vmItCUP9U3BQ=="], + "seroval-plugins": ["seroval-plugins@1.5.4", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw=="], "serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="], @@ -2871,8 +2872,6 @@ "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], - "tiny-warning": ["tiny-warning@1.0.3", "", {}, "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="], - "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], diff --git a/web/package.json b/web/package.json index 1d7b989267..735f38a0aa 100644 --- a/web/package.json +++ b/web/package.json @@ -23,7 +23,8 @@ "@shikijs/themes": "^3.20.0", "@tanstack/react-query": "^5.90.12", "@tanstack/react-query-devtools": "^5.91.1", - "@tanstack/react-router": "^1.143.6", + "@tanstack/react-router": "^1.170.8", + "@tanstack/router-core": "^1.171.6", "@xterm/addon-canvas": "^0.7.0", "@xterm/addon-fit": "^0.11.0", "@xterm/addon-web-links": "^0.12.0", From cb42ffed080d0cdbb36f523c4a4fd52b03e22bea Mon Sep 17 00:00:00 2001 From: swear01 Date: Thu, 28 May 2026 08:42:19 +0000 Subject: [PATCH 2/2] fix(web): adapt scrollStorageGuard to @tanstack/router-core >=1.145.6 API `scrollRestorationCache` was removed from the public exports; replace with `storageKey` import and simplify `hardResetScrollRestorationPersistedState` to a plain `removeItem`. Remove the now-stale in-memory cache sync path and its associated tests. Upstream try-catch (>=1.145.6) covers crash prevention; this guard continues to proactively prune sessionStorage. via [HAPI](https://hapi.run) Co-Authored-By: HAPI --- web/src/lib/scrollStorageGuard.test.ts | 98 +------------------------- web/src/lib/scrollStorageGuard.ts | 77 ++++---------------- 2 files changed, 14 insertions(+), 161 deletions(-) diff --git a/web/src/lib/scrollStorageGuard.test.ts b/web/src/lib/scrollStorageGuard.test.ts index 14d0871b0e..f9e86259a3 100644 --- a/web/src/lib/scrollStorageGuard.test.ts +++ b/web/src/lib/scrollStorageGuard.test.ts @@ -1,17 +1,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -const mockScrollCacheSet = vi.hoisted(() => vi.fn()) - -vi.mock('@tanstack/router-core', () => ({ - scrollRestorationCache: { - state: {}, - set: mockScrollCacheSet, - }, -})) +import { storageKey as STORAGE_KEY } from '@tanstack/router-core' import { installScrollRestorationGuard } from './scrollStorageGuard' -const STORAGE_KEY = 'tsr-scroll-restoration-v1_3' const RETAIN_COUNT = 50 class QuotaExceededError extends Error { @@ -42,15 +33,12 @@ describe('installScrollRestorationGuard', () => { let uninstall: () => void beforeEach(() => { - mockScrollCacheSet.mockClear() storage = makeMockStorage() uninstall = installScrollRestorationGuard(storage) }) afterEach(() => { uninstall() - vi.unstubAllGlobals() - vi.restoreAllMocks() }) it('passes through writes to keys other than the scroll restoration key unchanged on quota error', () => { @@ -144,63 +132,6 @@ describe('installScrollRestorationGuard', () => { expect(storedKeys).toContain('/route/50') // boundary kept expect(storedKeys).not.toContain('/route/49') // boundary dropped expect(storedKeys).not.toContain('/route/0') // oldest dropped - expect(mockScrollCacheSet).not.toHaveBeenCalled() - }) - - it('syncs TanStack in-memory scroll cache after a successful prune on real sessionStorage', () => { - const realSessionStorage = makeMockStorage() - vi.stubGlobal('window', { sessionStorage: realSessionStorage }) - - const off = installScrollRestorationGuard(realSessionStorage) - - const fullState: Record = {} - for (let i = 0; i < 100; i++) { - fullState[`/route/${i}`] = { window: { scrollX: 0, scrollY: i } } - } - const fullValue = JSON.stringify(fullState) - - let call = 0 - realSessionStorage._setItem.mockImplementation((key: string, value: string) => { - call += 1 - if (call === 1) { - throw new QuotaExceededError() - } - realSessionStorage._store[key] = value - }) - - realSessionStorage.setItem(STORAGE_KEY, fullValue) - - expect(mockScrollCacheSet).toHaveBeenCalledTimes(1) - const updater = mockScrollCacheSet.mock.calls[0]![0] as ( - state: Record - ) => Record - const synced = updater(fullState) - expect(Object.keys(synced).length).toBe(RETAIN_COUNT) - expect(Object.keys(synced)).toContain('/route/99') - expect(Object.keys(synced)).not.toContain('/route/0') - - off() - }) - - it('hard reset calls scrollRestorationCache through unwrapped setItem', () => { - const realSessionStorage = makeMockStorage() - vi.stubGlobal('window', { sessionStorage: realSessionStorage }) - - const off = installScrollRestorationGuard(realSessionStorage) - const wrappedSetItem = realSessionStorage.setItem - - let cacheWriteSetItem: Storage['setItem'] | undefined - mockScrollCacheSet.mockImplementationOnce(() => { - cacheWriteSetItem = realSessionStorage.setItem - }) - - realSessionStorage._setItem.mockImplementationOnce(() => { throw new QuotaExceededError() }) - realSessionStorage.setItem(STORAGE_KEY, 'not json {') - - expect(cacheWriteSetItem).toBeDefined() - expect(cacheWriteSetItem).not.toBe(wrappedSetItem) - - off() }) it('removes the key entirely if the value is not valid JSON', () => { @@ -221,33 +152,6 @@ describe('installScrollRestorationGuard', () => { expect(storage.removeItem).toHaveBeenCalledWith(STORAGE_KEY) }) - it('does not reset TanStack scroll cache when guarding mock storage', () => { - storage._setItem.mockImplementationOnce(() => { throw new QuotaExceededError() }) - storage.setItem(STORAGE_KEY, 'not json {') - - expect(storage.removeItem).toHaveBeenCalledWith(STORAGE_KEY) - expect(mockScrollCacheSet).not.toHaveBeenCalled() - }) - - it('resets TanStack in-memory scroll cache when hard reset uses real sessionStorage', () => { - const realSessionStorage = makeMockStorage() - vi.stubGlobal('window', { sessionStorage: realSessionStorage }) - - const off = installScrollRestorationGuard(realSessionStorage) - realSessionStorage._setItem.mockImplementation(() => { throw new QuotaExceededError() }) - - realSessionStorage.setItem(STORAGE_KEY, JSON.stringify({ stale: { window: { scrollX: 0, scrollY: 1 } } })) - - expect(realSessionStorage.removeItem).toHaveBeenCalledWith(STORAGE_KEY) - expect(mockScrollCacheSet).toHaveBeenCalledTimes(1) - const updater = mockScrollCacheSet.mock.calls[0]![0] as ( - state: Record - ) => Record - expect(updater({ stale: { window: { scrollX: 0, scrollY: 1 } } })).toEqual({}) - - off() - }) - it('is idempotent — installing twice does not double-wrap', () => { const wrapped1 = storage.setItem const noop = installScrollRestorationGuard(storage) diff --git a/web/src/lib/scrollStorageGuard.ts b/web/src/lib/scrollStorageGuard.ts index 80679fb86f..f01d6d9ebe 100644 --- a/web/src/lib/scrollStorageGuard.ts +++ b/web/src/lib/scrollStorageGuard.ts @@ -1,14 +1,6 @@ -/** - * Key TanStack Router uses for its scroll restoration cache in sessionStorage. - * Defined in `@tanstack/router-core/src/scroll-restoration.ts` (not part of - * the package's public API — update this constant if the library bumps the - * suffix on `tsr-scroll-restoration-v1_*`). - */ -import { scrollRestorationCache } from '@tanstack/router-core' +import { storageKey } from '@tanstack/router-core' -type ScrollCacheUpdater = NonNullable['set']>[0]> - -const STORAGE_KEY = 'tsr-scroll-restoration-v1_3' +const STORAGE_KEY = storageKey const TARGET_ENTRIES_AFTER_PRUNE = 50 @@ -18,60 +10,22 @@ interface GuardedStorage extends Storage { [GUARD_MARKER]?: true } -function writeScrollRestorationCache( - storage: Storage, - originalSetItem: Storage['setItem'], - updater: ScrollCacheUpdater, -): void { - const guardedSetItem = storage.setItem - storage.setItem = originalSetItem - try { - scrollRestorationCache?.set(updater) - } finally { - storage.setItem = guardedSetItem - } -} - -function hardResetScrollRestorationPersistedState( - storage: Storage, - originalSetItem: Storage['setItem'], - isRealSessionStorage: boolean -): void { +function hardResetScrollRestorationPersistedState(storage: Storage): void { try { storage.removeItem(STORAGE_KEY) } catch { // ignore } - if (!isRealSessionStorage) { - return - } - // TanStack keeps the full scroll map in memory even when setItem fails. - // Pruning only the JSON string leaves RAM oversized — the next scroll - // write throws again. Clear the library cache so persisted size matches. - try { - writeScrollRestorationCache(storage, originalSetItem, () => ({})) - } catch { - try { - originalSetItem.call(storage, STORAGE_KEY, '{}') - } catch { - // last resort: session may be full or private-mode broken - } - } } /** * Wrap `sessionStorage.setItem` so writes to the scroll restoration cache - * survive quota exhaustion. The default behavior throws synchronously during - * a React commit, blocking the UI (see tiann/hapi#611). We prune the oldest - * entries (by JSON property insertion order — i.e. visited-first dropped, - * recently-visited kept) and retry once; if the value is not valid JSON or - * the retry still fails, we drop the key and reset TanStack's in-memory cache - * so navigation can continue. + * survive quota exhaustion. The default throws synchronously during a React + * commit, blocking the UI (see tiann/hapi#611). We prune oldest entries and + * retry once; if still failing, we drop the key so navigation can continue. * - * Idempotent — calling more than once on the same storage is a no-op. - * - * Returns an `uninstall` thunk that restores the original `setItem`. Intended - * for tests; production code calls this once at boot and never uninstalls. + * Upstream >=1.145.6 also wraps setItem with try-catch, so this guard is an + * additional safety net that proactively keeps the cache small. */ export function installScrollRestorationGuard( storage: Storage = typeof window !== 'undefined' ? window.sessionStorage : undefined as unknown as Storage, @@ -84,7 +38,6 @@ export function installScrollRestorationGuard( return () => {} } const originalSetItem = storage.setItem - const isRealSessionStorage = typeof window !== 'undefined' && storage === window.sessionStorage const wrappedSetItem = (key: string, value: string): void => { try { @@ -97,29 +50,25 @@ export function installScrollRestorationGuard( } let trimmed: string - let prunedState: Record try { const parsed = JSON.parse(value) as Record const keys = Object.keys(parsed) const keepKeys = keys.length > TARGET_ENTRIES_AFTER_PRUNE ? keys.slice(-TARGET_ENTRIES_AFTER_PRUNE) : keys - prunedState = {} + const next: Record = {} for (const k of keepKeys) { - prunedState[k] = parsed[k] + next[k] = parsed[k] } - trimmed = JSON.stringify(prunedState) + trimmed = JSON.stringify(next) } catch { - hardResetScrollRestorationPersistedState(storage, originalSetItem, isRealSessionStorage) + hardResetScrollRestorationPersistedState(storage) return } try { originalSetItem.call(storage, key, trimmed) - if (isRealSessionStorage) { - writeScrollRestorationCache(storage, originalSetItem, (() => prunedState) as ScrollCacheUpdater) - } } catch { - hardResetScrollRestorationPersistedState(storage, originalSetItem, isRealSessionStorage) + hardResetScrollRestorationPersistedState(storage) } } storage.setItem = wrappedSetItem