Skip to content

fix(web): replay offline highlights; reading-session queue race - #694

Merged
mrviduus merged 1 commit into
mainfrom
fix/web-highlights-offline
Oct 4, 2026
Merged

mrviduus merged 1 commit into
mainfrom
fix/web-highlights-offline

Conversation

@mrviduus

@mrviduus mrviduus commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Why

Architecture review 2026-10, #11 (+ web half of #12): silent data loss on web.

What

  • Highlights — a highlight saved while the server was unreachable stayed pending in IndexedDB, was never sent, and vanished from the screen on the next server load; offline edits were overwritten by server rows; failed deletes came back. New pure lib/highlightSync.ts (planHighlightSync) merges server + local and plans creates/updates/deletes; the hook replays on load and on online (existing useNetworkRecovery), serialised so overlapping triggers can't double-POST. Offline deletes leave a deleted tombstone until the DELETE lands.
  • No duplicates — POST has no idempotency key (server mints the id), so before replaying a create the plan adopts a server row with the same anchor (canonical JSON; jsonb reorders keys).
  • Reading-session queue — the flush cleared the queue up front and wrote back only failures, clobbering sessions queued mid-flush. Now it removes only what it settled, from a fresh read.

Verified

  • Web vitest 787/787 (12 new: plan/merge, hook replay/failure/tombstone, queue race — race test fails on old code); tsc clean; build ok.
  • Browser (Playwright, local stack): highlight made with setOffline(true) reached the server after going online; highlight made with /me/highlights blocked reached the server after reload, no duplicate, painted.

Known limits (follow-ups)

  • An edit to a lost-response create before the next sync is dropped (proper fix: client-supplied id on POST).
  • Removing a note never reaches the server (client sends noteText:null, server needs removeNote) — pre-existing.

Rollback

Revert.

🤖 Generated with Claude Code

@mrviduus
mrviduus enabled auto-merge (squash) October 4, 2026 19:58
@mrviduus
mrviduus disabled auto-merge October 4, 2026 20:07
mrviduus added a commit that referenced this pull request Oct 4, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mrviduus
mrviduus force-pushed the fix/web-highlights-offline branch from 6c88c0b to 28e1ae5 Compare October 4, 2026 20:54
mrviduus added a commit that referenced this pull request Oct 4, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mrviduus
mrviduus force-pushed the fix/web-highlights-offline branch from 28e1ae5 to 85e1c76 Compare October 4, 2026 21:17
mrviduus added a commit that referenced this pull request Oct 4, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mrviduus
mrviduus force-pushed the fix/web-highlights-offline branch from 85e1c76 to 11a86cf Compare October 4, 2026 21:42
mrviduus added a commit that referenced this pull request Oct 4, 2026
… failed reading sessions (#695)

* fix(sync): progress LWW compares client clock with itself; mobile retries failed reading sessions

- reading_progresses.client_updated_at: catalog progress stale-write guard compared client
  UpdatedAt with server-stamped UpdatedAt, so a device clock behind the server had newer writes
  refused with 200. Now compared only with the stored client stamp (ProgressClock), clamped to
  server now + 5 min so a future clock can't freeze the row.
- mobile stamps progress with the time it was recorded (snapshot), like web.
- mobile: failed session submits queued in AsyncStorage (cap 50), flushed after each session,
  on sign-in/app start and on reconnect; cleared on sign-out. Queue rules shared with web.
- server: session dedup pre-check covers edition sessions too (unique index already existed).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore: leave web session queue to #694 (avoids conflicting rewrites)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Pending highlights survived only in IndexedDB: server load replaced the list
and nothing replayed them. Merge server + local pending, replay
create/update/delete on load and online; dedupe lost-response creates by
anchor; tombstone offline deletes. Session flush removes only settled
sessions from a fresh read.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mrviduus
mrviduus force-pushed the fix/web-highlights-offline branch from 11a86cf to 24a7a4a Compare October 4, 2026 23:09
@mrviduus
mrviduus merged commit a34e3cc into main Oct 4, 2026
10 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