fix(workspace): read the localStorage global defensively - #771
Conversation
|
ANIRUDDHA ADAK (@aniruddhaadak80) is attempting to deploy a commit to the InkVell Team on Vercel. A member of the Team first needs to authorize it. |
Ishaan Gangwani (ishaan1124)
left a comment
There was a problem hiding this comment.
Thanks. The three store fixes (artifacts/context.ts, sessionTabs.ts, ui.ts) are right, and the test covers them well.
One blocker in FdaBanner.tsx: the trailing () calls the [getter, setter] tuple that createSignal returns, not the arrow function:
const [dismissed, setDismissed] = createSignal(() => { ... })()bun run --cwd frontend/workspace typecheck rejects it (TS2349: This expression is not callable. Type 'Signal<() => boolean>' has no call signatures.). At runtime the FDA chip would throw as soon as it rendered, which takes down the sidebar. That's the blank page this PR sets out to prevent. The new test doesn't render FdaChip, so it passes anyway.
Read the flag first, then hand the boolean to createSignal:
const stored = (() => {
try {
return globalThis.localStorage.getItem(DISMISS_KEY) === "1"
} catch {
return false
}
})()
const [dismissed, setDismissed] = createSignal(stored)Fast CI on this PR should show the same typecheck failure. I'll merge once that's fixed and green.
|
Fixed in The blocker. const stored = (() => {
try {
return globalThis.localStorage.getItem(DISMISS_KEY) === 1
} catch {
return false
}
})()
const [dismissed, setDismissed] = createSignal(stored)
On the test gap. You are right that One thing outside the code. The branch was also conflicting with Also worth flagging for the maintainers: the |
94087eb to
f890a7e
Compare
`typeof localStorage` invokes the same getter as reading it, so it cannot guard an access that throws when storage is blocked. The workspace store and the artifact store are built at module scope, so the throw took the whole app down rather than only losing persistence. Use the try/catch form the rest of the workspace already uses, and guard the FdaBanner read its write already guarded.
Restores the fix from 94087eb, which lived in a merge commit that the rebase onto main dropped. Co-authored-by: Cursor <cursoragent@cursor.com>
116532c to
7b1778a
Compare
79b8f21
into
synthetic-sciences:main
* fix(workspace): finish guarding browser storage reads #771 makes the workspace, artifact and session-tab stores read `localStorage` defensively, but the blank page it sets out to prevent is still reachable. With storage blocked, three reads run before any of that code executes. The theme provider is the one that matters. `getStoredColorScheme` reads `localStorage` with no guard at all, `ThemeProvider` calls it from its synchronous `init`, and `app.tsx` mounts `ThemeProvider` as an ancestor of `ErrorBoundary`. Nothing above the boundary can catch the throw, so the app still dies on first render. This is why the changelog entry in #771 overstates what that PR delivers. The rest are the same mistake #771 already identified, in the form where a `typeof` guard sits outside the `try` that was meant to cover it. A `typeof` check invokes the very getter that throws, so it cannot protect the read: - `pages/session.tsx` read the sidebar collapse state and width, called at render. - `atlas/SkillsPage.tsx` read `sessionStorage` and `localStorage`. - `components/prompt-input.tsx` read `localStorage` in a component body. `atlas/right-pane-layout.ts` has a subtler version: `localStorage` is a default parameter, and default parameters are evaluated at the call site, before the function's own `try` can catch anything. The read path was already called inside a `try` by `RightPane`; only the write path was exposed. That file's sibling `artifact-view.ts` documents the correct pattern in a comment, so this follows it. Net effect: with storage blocked the workspace now boots and persists nothing, instead of rendering a blank page. Single parent, rebased onto current `main`, so a rebase carries it. This is a separate branch from #771 on purpose: that branch is being rebased, and these files do not overlap with it. Refs #771 * fix(workspace): keep the theme lock intact, and let Prettier reflow Two CI corrections to the storage sweep. `theme-lock.test.ts` pins its expectations against the *source text* of theme/context.tsx, including the exact line localStorage.removeItem(STORAGE_KEYS.LEGACY_THEME_CSS_LIGHT) Routing that block through `storageOrNothing` renamed the call and broke the assertion. The legacy-key cleanup is now guarded in place with a try/catch, which keeps the pinned text and matches the idiom the same file already uses for the CSS cache a few lines above. The reads still go through the helper, because no test pins those. Dropping the `= localStorage` default also shortened the readPaneWidth signature below the print width, so Prettier reflows it onto one line.
What does this PR do, and why?
Three copies of this read the
localStorageglobal behind atypeofguard:localStorageis an accessor property on the global object, sotypeof localStorageandlocalStorageboth perform a[[Get]]that invokes thegetter - a
typeofguard runs the very access it is trying to avoid. Withstorage blocked (sandboxed frame, denied origin,
dom.storage.enabled=false)that throws
SecurityError, and there is notry.The result is a blank app, not a degraded one, because two of these run at
module scope:
atlas/store/ui.ts-const state = createContextState(), exported asuiStoreand used by the global keys, panes and toolbarartifacts/context.ts-export const artifactContext = createArtifactState()atlas/store/sessionTabs.ts- reached during the session page's renderThe throw happens while evaluating the argument, before
restore(storage)- whichhandles
undefinedperfectly well - ever runs, so the import fails.All three now use the form
pages/session-trace.tsalready uses, andatlas/files/last-source.tsalready documents why:FdaBannerhad the mirror-image asymmetry - its write was already guarded, itsread was not - so the read is guarded the same way.
Linked issue
Fixes #770
How did you verify it?
frontend/workspace/src/atlas/store/blocked-storage.test.tsinstalls alocalStoragegetter that throws, using the pattern already established inatlas/files/artifact-boundaries.test.ts:context, session tabs and artifacts still start- the three factories mustnot throw
context state works in memory, with persistence quietly off- the pane isusable: it opens a scope and a file, with nowhere to save them
a working storage is still read, so persistence is not silently lost- with areal global, a seeded
openscience-context-state-v2payload is restored. Thisis the guard that the fix does not turn every read into
undefinedOn unmodified
3e94875cthe first two fail with the real error:The third passing on both is the point: it proves the failure is specific to the
blocked case.
Commands run:
bun test src/atlas/store/blocked-storage.test.ts-> 3 passbun test src/atlas/store src/artifacts-> 73 pass, 0 fail (10 files)bun run typecheckinfrontend/workspace-> the only two errors aresrc/custom-elements.d.ts(1,1)and(1,2)TS1128, which is the pre-existingWindows symlink artifact described below; no error mentions any file I
changed
One gap to flag: the
FdaBannerread is not covered by an automated test.FdaChipneedsuseGlobalSDK,useDialogand a resource, so mounting it in atest means standing up the SDK and dialog providers, which is a lot of harness
for a five-line guard. The fix there is the same shape as the three that are
tested, and it is required for internal consistency because that file's own
dismiss()write is already guarded. Happy to add the provider harness if youwould rather have it covered.
Pre-existing reds on this Windows checkout, not from this diff:
bun run format:checkcannot pass here: git materializes the LF blobs as CRLF,so Prettier flags hundreds of untouched files. I verified all six changed files
are formatted per the repo config with line endings normalized.
frontend/workspacetypecheck:src/custom-elements.d.tsis a symlink (mode120000) that Windows checked out as a text file containing../../ui/src/custom-elements.d.ts, which TypeScript then parses as source.Checklist
bun run checkis green (format, typecheck, backend + frontend/ui + SDK tests) - blocked on this Windows checkout by the CRLF and symlink artifacts described above; the workspace typecheck reports no error in any file I changed, and 76 workspace tests are greenbun run --cwd frontend/workspace buildsucceeds if I touchedfrontend/workspaceorfrontend/ui- I touchedfrontend/workspace; the build needsfrontend/workspace/distfrom a full workspace build, which I could not complete on this Windows checkout because of the symlink artifact above, so please treat that box as unverified./tooling/repo/generate.tswas run and thetooling/sdkoutput committed if I changedbackend/cli/src/server- not touchedfrontend/docs/src/content/openscience/is updated if behavior changed - no doc change needed: the workspace already persists these stores and already degrades gracefully elsewherepackage.jsonversions and tags are written by the release workflow)installandfrontend/landing/public/installare still byte-identical if I touched either - not touched