cmd/librenotes/web/public/sync.js drives the offline-online
reconciliation flow against the notes REST API:
- start(): registers online/offline window listeners, runs an
initial syncOnce() if currently online.
- syncOnce(): push() then pull(); emits "librenotes:sync-state"
events with state in {online, offline, syncing, synced, error}.
- push(): walks notesCache.pending() (rows with dirty=1, including
tombstones). PUTs use ?base=<synced_at> for optimistic locking
and DELETEs use the same. The notes API returns 409 with the
current server body on conflict; sync.js stashes the pair in
conflictsById and dispatches "librenotes:sync-conflict" so the
app shell can render a resolution dialog.
- pull(): GETs the summary list, refetches any row whose server
updated_at exceeds the local synced_at (or that is missing
locally), and stamps it as cleanly synced. Skips locally-dirty
rows so push's conflict path stays authoritative.
- resolveConflict(id, "local"|"remote"|"merge", merged): replays
the user's choice. "local" and "merge" PUT with the latest
server base so the second attempt accepts; "remote" overwrites
the local cache with the server copy.
app.html now includes a sync-state badge in the header and a
<dialog> for conflict resolution wired to the events. app.js
calls notesSync.start() on load and routes dialog clicks back to
resolveConflict. The dialog uses native <dialog>.showModal(),
which all current target browsers support.
style.css adds badge colour states (syncing/synced/offline/error)
and a two-column conflict layout that collapses on narrow widths.
Closes#23.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>