Add IndexedDB sync queue with FIFO drain and OPFS blob store - #17
Merged
Conversation
Mutations issued while offline are persisted to an IndexedDB `pending` store and drained in seq order when the vault is reachable. Six mutation kinds: create/update/delete-note, upload/link/delete- attachment. Local-UUID note IDs and blob refs are mapped to server IDs via dedicated stores so subsequent rows in the queue can resolve their target ids before issuing the real call. Blob payloads live in OPFS when available (with sidecar mimeType files — OPFS has no metadata channel) and fall back to an IndexedDB object store. The interface operates on ArrayBuffer + mimeType so the test harness (jsdom + fake-indexeddb) doesn't mangle Blob objects via structured clone. Drain classifies errors: auth halts the whole drain and stamps a meta marker; conflicts stash the row as `needs-human` and continue past it; 404s drop the row; anything transient hits an exponential backoff capped at 10m. The engine ticks on `online` events plus a 30s interval and no-ops when navigator.onLine is false or no vault is active. The existing mutation hooks (useCreate/Update/Delete-Note, useDeleteAttachment) keep the same signature. When offline, the create hook returns an optimistic Note with a `local-<uuid>` id; update/delete enqueue and resolve to a no-op Note / void. TanStack query caches are invalidated in onDrain when rows flush. Storage persistence is requested on SyncProvider mount. iOS' ~50MB PWA cap and eviction caveats remain documented in the install section. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.
Summary
Foundation for offline-capable CRUD in v0.2. Mutations issued offline are persisted in IndexedDB and drained in FIFO order when the vault is reachable.
src/lib/sync/—db(stores:pendingautoIncrement seq,id_map,blob_path_map,blobs,meta),queue(enqueue + drain),engine(online/interval tick),blob-store(OPFS → IDB fallback),id-map(local-id + blob-ref resolution),storage-quota.src/providers/SyncProvider.tsx— opens the DB, builds the blob store, listens for online/offline, runs the engine, invalidates TanStack caches on drain.src/lib/vault/queries.ts— existing hooks now fall through toenqueue(...)whennavigator.onLine === false. Same signatures; create returns an optimisticNotewith alocal-<uuid>id.Design decisions worth calling out
{localId → serverId}inid_map. Subsequent update/delete rows in the queue callresolveNoteId(localId)before dispatching. Same pattern for blob uploads (blob:<blobId>→ storage path). This keeps the mutation rows themselves small and independent, and means UI code never has to be told "your note id changed."seq. Queue depth is small (one user, thinking-pace mutations), so the cursor filters vault + status in-app rather than maintaining a compound index.Blobon structured clone. Callers convert via the exportedblobToArrayBuffer()helper.<blobId>.metaalongside each payload.VaultAuthError→ halt drain +meta["auth-halted"]marker;VaultConflictError→ stash asneeds-human, continue past it;VaultNotFoundError→ drop the row; anything else → exponential backoff (2^attempts seconds, capped at 10 min).Six mutation kinds
create-note,update-note,delete-note,upload-attachment,link-attachment,delete-attachment. All resolve local ids + blob refs before hitting the network.Out of scope (per PR #2 spec)
UI for queue status, dedicated conflict-resolution UI, voice capture wiring, scribe. This is plumbing only — the existing UI simply stops failing when offline.
Gates
bun run lint✅bun run typecheck✅bun run test --run— 231/231 passing (39 new sync tests)bun run build✅Test plan
local-*id; reload → note list empty (expected, since the queue lives in IDB and drains later).onlineevent) → note appears with server id.meta["auth-halted"]set.🤖 Generated with Claude Code