Add a client-side persistence-status layer with save-failure recovery#1859
Open
kmcginnes wants to merge 25 commits into
Open
Add a client-side persistence-status layer with save-failure recovery#1859kmcginnes wants to merge 25 commits into
kmcginnes wants to merge 25 commits into
Conversation
- waitForIdle now gates on in-flight count, not status, so a terminal failure on one key no longer signals settled while another key drains - skip no-op status recomputes to avoid redundant indicator re-renders - reclassify InvalidStateError as retryable (often a transient IDB state) - give terminal-failure toasts stable ids so they replace, not stack - remove the DEV-only window.__persistence hook (broke node-env tests)
The failure Alert now opens a detail dialog listing each failed collection (humanized from its storage key) and why it failed, with a Download backup button only when storage is full. Toasts are removed: the standing Alert plus dialog is the single, non-stacking recovery surface. Failure records now carry attemptCount and lastAttemptAt. Syncs CONTEXT.md and the ADR to the shipped detail view, fixes the ADR's waitForIdle seam name and the non-existent superseded-ADR reference, and adds a troubleshooting entry for save failures.
The indicator is now a rounded danger "Changes not saved" button that opens the detail dialog, rather than an Alert with an action slot. Revert the AlertAction addition to the shared Alert component since nothing uses it anymore, and sync the docs.
- waitForIdle drives off a new snapshot isSettling flag via the single subscribe path; drop the parallel idleWaiters mechanism - add store.reset(); use it for test cleanup and a Reset Save Status debug action instead of enumerating keys - detail dialog is now generic: always offers Save Configuration backup regardless of failure reason, matching the settings screen wording - write queue running set is a Set, not a Map with an unread Promise value - debug failure helper uses synthetic keys, decoupled from real atoms - revert the global leading-none button base change - sync ADR, CONTEXT.md, and troubleshooting docs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Graph Explorer stores all user data client-side in IndexedDB. Previously, when a durable write failed, in-memory state silently diverged from disk and was lost on reload — with no signal to the user.
This adds a persistence-status layer that owns write failure centrally. Every IndexedDB write now routes through a shared retry/coalescing queue that classifies failures, retries transient ones with backoff, and reports terminal failures into a single global status store. A "Changes not saved" indicator surfaces in the nav bar only when a write has terminally failed; clicking it opens a dialog with the failure details and — when storage is full (and therefore still readable) — a button to back up the configuration to a file.
The persisted setter now returns
void: callers just set state, and the storage layer is solely responsible for getting it to disk. This composes with the cross-tab merge work (ADRper-key-diff-merge-cross-tab-reconciliation) at the sameflushseam without conflicting.How to read
idle | saving | failed) outside React/JotaiflushthunkpersistThroughQueuevoidchange (shared by the localForage and active-connection paths)Core vs supporting: the four
persistence/modules + the indicator are the feature. The route files (Connections,DataExplorer,GraphExplorer,SchemaExplorer,SettingsRoot),NavBarspacing, andConnectionDetaildebug actions are wiring. The test-helper and*.test.tschanges are the mechanical fallout of the setter returningvoid(they synchronize onwaitForIdle()instead of awaiting a per-write promise).Validation
pnpm checkspasses (lint, format, types).pnpm testpasses (1854 passed, 1 expected-fail — the pre-existing Concurrent edits in multiple browser tabs silently clobber shared persisted collections (styling, schema, connections, sessions) #1820 cross-tab clobber regression marker, unchanged).waitForIdle,reset), and write queue (coalescing, retry-then-succeed, terminal no-retry, escalation after cap, per-key independence), plus a component test for the indicator.Related Issues
Check List
pnpm checkspasses with no errors.pnpm testpasses with no failures.