Skip to content

Keep the frontend up when storage, a dropped payload or an uploaded archive misbehaves - #63

Open
lgnap wants to merge 1 commit into
antoinevalentinHA:masterfrom
lgnap:fix/issue-13-frontend-defensive-guards
Open

lgnap wants to merge 1 commit into
antoinevalentinHA:masterfrom
lgnap:fix/issue-13-frontend-defensive-guards

Conversation

@lgnap

@lgnap lgnap commented Sep 11, 2026

Copy link
Copy Markdown

What

Three ways the UI could go blank or throw out of a handler, on input it does not control. All defensive, no behaviour change on valid input.

localStorage at startup. The settings reducer read it while building its initial state. Safari in private browsing and a few privacy settings make every access throw — the accessor itself included — so the store threw during creation and the application never rendered. safeGetItem / safeSetItem (utils/storage.js) answer "no stored value" instead; every localStorage use (reducer, App.js, PackLibrary.js) goes through them.

JSON.parse on untrusted input. parseJson (utils/json.js) answers undefined for anything that is not JSON. Applied to the three drag-and-drop handlers (ignore the payload), readFromArchive (rejects with a message naming story.json, also when the file is missing — that used to throw on null), and the upload XHR (rejects instead of resolving with a SyntaxError; the stray console.log that stringified the parsed object is gone).

No error boundary. React 16 unmounts the whole tree on an uncaught render error, which the user sees as a blank page. ErrorBoundary at the root shows the error and a reload button, in both languages, with no dependency on i18n or the store since either may be what failed.

Tests

  • storage.test.js (4): pass-through, throwing getItem, throwing accessor, failing setItem
  • json.test.js (2)
  • ErrorBoundary.test.js (2): rendered into jsdom with react-dom directly — no component testing library added
  • reader.test.js (2): malformed and missing story.json, real zips built with JSZip

Locally: yarn test → 67 passed (57 + 10); yarn build compiles under CI=true (warnings are errors there). Java untouched.

Tracked in lgnap#13.

🤖 Generated with Claude Code

…ded archive misbehaves

Three ways the UI could go blank or throw out of a handler, on input it
does not control:

- localStorage was read while the settings reducer built its initial
  state. Safari in private browsing and a few privacy settings make
  every access throw, the store threw during creation, and the
  application never rendered. safeGetItem/safeSetItem answer "no stored
  value" instead; every localStorage use goes through them.
- JSON.parse on a drag-and-drop payload (three sites), on story.json
  from an uploaded archive, and on the upload response body. parseJson
  answers undefined for anything that is not JSON; the drop handlers
  ignore it, the archive reader rejects with a message that names
  story.json (also when it is missing, which used to throw on null),
  and the upload rejects instead of resolving with a SyntaxError.
- No error boundary: React 16 unmounts the whole tree on an uncaught
  render error. ErrorBoundary at the root shows the error and a reload
  button, in both languages and with no dependency on i18n or the
  store, since either may be what failed.

Tests: storage (4, including a throwing accessor), parseJson (2),
ErrorBoundary rendered into jsdom with react-dom (2), readFromArchive on
a malformed and on a missing story.json (2).

Closes #13

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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