Skip to content

Show the app error page when saved state cannot load - #2479

Merged
kmcginnes merged 2 commits into
mainfrom
load-persisted-state-before-render
Oct 29, 2026
Merged

kmcginnes merged 2 commits into
mainfrom
load-persisted-state-before-render

Conversation

@kmcginnes

Copy link
Copy Markdown
Collaborator

Summary

When saved state can't load at startup, Graph Explorer showed a blank page with no way forward; it now shows the app error page.

User-facing: if the browser blocks every storage option Graph Explorer can use, users see "Something went wrong" with a reload button instead of a blank page.

 startApp(container)
   root = createRoot(container)
-  await loadPersistedState()          # a rejection escaped, nothing rendered
+  try
+    await loadPersistedState()
+  catch error
+    logger.error("Could not load the saved app state", error)
+    root.render(<AppErrorPage error={error} />)
+    return
   root.render(<App />)

AppErrorPage now takes only error, so it works outside an error boundary.

Evidence

Before: startApp.test.tsx "shows the app error page when persisted state fails to load" failed with UnknownError: The database connection was lost. and an empty container.
After: the same test passes.

Graph Explorer error page with a reload button

Verified live: production build in Chromium with IndexedDB open and localStorage both blocked: the error page renders and the console logs the load error. If only IndexedDB fails, localForage falls back to localStorage and the app boots normally, as it did before.

Merge Danger

Door: two-way

Impact: startup

Only reached when storage fails to load; the success path is unchanged.

Related


🤖 Written by an AI agent on my behalf.

@kmcginnes
kmcginnes force-pushed the load-persisted-state-before-render branch from 848a30e to 17b91cd Compare October 28, 2026 00:09
@kmcginnes
kmcginnes added this pull request to stack #2483 October 28, 2026 00:40
@kmcginnes
kmcginnes force-pushed the load-persisted-state-before-render branch from 17b91cd to 42c4c74 Compare October 28, 2026 01:14
@kmcginnes
kmcginnes force-pushed the load-persisted-state-before-render branch from 42c4c74 to fec9eea Compare October 28, 2026 14:18

@kmcginnes kmcginnes left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One change needed


/** This is the app wide error page that will be shown when the app essentially crashes */
export default function AppErrorPage(props: FallbackProps) {
export default function AppErrorPage(props: Pick<FallbackProps, "error">) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's be explicit about the props rather than picking from FallbackProps.

@kmcginnes
kmcginnes force-pushed the load-persisted-state-before-render branch 2 times, most recently from 500d90e to 78b1bcd Compare October 28, 2026 20:31

@kmcginnes kmcginnes left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved

@kmcginnes
kmcginnes force-pushed the load-persisted-state-before-render branch from 78b1bcd to 1d5a15d Compare October 29, 2026 00:02
@kmcginnes
kmcginnes force-pushed the load-persisted-state-before-render branch from 1d5a15d to 6201b4a Compare October 29, 2026 00:05
Base automatically changed from load-persisted-state-before-render-refactor-bootstrap to main October 29, 2026 18:05
@kmcginnes
kmcginnes force-pushed the load-persisted-state-before-render branch from 6201b4a to f4783dc Compare October 29, 2026 18:08
@kmcginnes
kmcginnes merged commit 3ce8bc0 into main Oct 29, 2026
3 checks passed
@kmcginnes
kmcginnes deleted the load-persisted-state-before-render branch October 29, 2026 18:09
kmcginnes added a commit that referenced this pull request Oct 29, 2026
## Summary

When saved state can't load at startup, the error page said only
"Something went wrong", which gives no hint that browser storage is the
cause or what to do about it.

**User-facing:** When Graph Explorer can't read its saved data at
startup, the error page now says so, names private browsing or blocked
site storage as likely causes, and suggests reloading, then clearing
site data as a last resort. Other app errors keep the generic copy.

```diff
 loadPersistedState
   run migrations
-  loadPersistedAtoms()
+  try loadPersistedAtoms()
+  catch → throw new PersistedStateLoadError(cause)

 createDisplayError(error)
+  PersistedStateLoadError → "Unable to load saved data" + storage guidance
   …existing mappings, generic fallback
```

`startApp` and `AppErrorPage` are unchanged: the boot path already
passes the caught error to `AppErrorPage`, which maps it through
`createDisplayError` like any other app error.

Also fixes two stale notes left by #2478 and #2474: the
`migrateUserStylingIfNeeded` TSDoc and the test-setup line in the
IndexedDB persistence ADR.

## Evidence

**Before:** A storage failure at boot showed "Something went wrong / An
error occurred. Please try again."
**After:**

![Boot error page reading "Unable to load saved
data"](https://github.com/user-attachments/assets/4467165d-5d0b-4f40-b99d-4f0fe4cf7975)

`startApp.test.tsx` asserts the saved-data heading and message on a
failed load. `storageAtoms.test.ts` asserts the load rejects with
`PersistedStateLoadError` carrying the storage error as `cause`, and
`createDisplayError.test.ts` pins the copy.

**Verified live:** Ran the dev server in Chromium with IndexedDB and
localStorage made to throw, and confirmed the page above.

## Merge Danger

**Door:** two-way

**Impact:** copy

## Related

- Related to #1833
- Follows #2479

---

<sub>🤖 Written by an AI agent on my behalf.</sub>
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