Skip to content

Guard the theme toggle's localStorage read so blocked storage can't crash it - #52

Merged
nrdptel merged 1 commit into
mainfrom
feat/theme-storage-guard
Jul 17, 2026
Merged

nrdptel merged 1 commit into
mainfrom
feat/theme-storage-guard

Conversation

@nrdptel

@nrdptel nrdptel commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Why

The theme toggle reads the saved choice from localStorage on mount. The write was already wrapped in try/catch (with a comment about disabled storage) — but the read wasn't. In a browser that blocks site storage (private mode, a sandboxed frame, "block all cookies"), even reading localStorage throws, not just writing.

That unguarded read sits in the mount effect with no error boundary above it, so the throw doesn't fail quietly — it tears down the whole client render, not just the theme. A storage-blocked visitor would get a broken app plus an uncaught error. It's the exact case the guards on the write and the pre-paint theme script were meant to cover, missed on the one path that reads first.

Found by auditing every localStorage touch for storage-blocked resilience — the kit planner (read + write) and the layout theme script are all guarded; this read was the only unguarded one.

What

Wrap the read in try/catch, falling back to System, so setMounted still runs and the toggle stays live for the session. Mirrors the guarded write, the layout script, and the kit planner's storage handling.

Verification

  • New e2e (smoke.spec.ts) makes getItem throw and proves the toggle mounts, cycles System → Light, applies the theme, and raises no page error.
  • Verified non-vacuous: the test fails without the guard — the button doesn't even render (the client render crashes), confirming the bug was app-level, not a dead toggle.
  • Full gate green — lint · npm test 197 · build · Playwright e2e 39 → 40.
  • No data or product-behaviour change beyond not crashing.

…rash it

The theme toggle reads the saved choice from localStorage on mount. The write
was already wrapped in try/catch (with a comment) — but the read wasn't, and
in a browser that blocks site storage (private mode, a sandboxed frame, "block
all cookies") even *reading* localStorage throws, not just writing.

That unguarded read sits in the mount effect with no error boundary above it,
so the throw doesn't fail quietly: it tears down the whole client render, not
just the theme. A storage-blocked visitor would get a broken app, and an
uncaught error — the exact case the guards on the write and the pre-paint
theme script were meant to cover, missed on the one path that reads first.

Wrap the read in try/catch, falling back to System, so setMounted still runs
and the toggle stays live for the session. A new e2e makes getItem throw and
proves the toggle mounts, cycles, and applies a theme with no page error;
verified it fails without the guard (the button doesn't even render — the
render crashes). Fix mirrors the guarded write, the layout script, and the
kit planner's storage handling. e2e 39 -> 40; no data or product-behaviour
change beyond not crashing.
@nrdptel
nrdptel merged commit f98dbca into main Jul 17, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant