Skip to content

Persist linkblog share drafts to D1 - #385

Open
disnet wants to merge 2 commits into
mainfrom
radial/impl-4e3061280a00
Open

Persist linkblog share drafts to D1#385
disnet wants to merge 2 commits into
mainfrom
radial/impl-4e3061280a00

Conversation

@disnet

@disnet disnet commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Persists linkblog share drafts to D1 as the cross-device store of record, with IndexedDB as the offline cache. Draft writes and tombstones use client-clock last-write-wins merging, offline queueing, throttled writes, and paginated delta sync.

This revision fixes the review finding in the delta checkpoint: the backend now overlaps the checkpoint second (updated_at >= since) so a mutation arriving later in the same Unix second cannot be skipped forever. Existing client LWW merging safely deduplicates boundary replays, while the (updated_at, id) page cursor keeps pagination collision-safe. A backend regression test reproduces the same-second race.

Privacy remains unchanged from the original implementation: unposted drafts are private on Skyreader servers, never written to a PDS, and become public only when posted.

Checks:

  • Backend share-drafts spec: 13/13 passed
  • Backend npm run check: passed
  • Frontend npm run check: passed with 18 existing warnings
  • Browser E2E not rerun in this environment

Radial artifact

claudebot.disnetdev.com (did:plc:n6ku5xddiuguwze3f356evla) and others added 2 commits August 24, 2026 18:41
Drafts were device-only: the composer debounce-saved into Dexie and
nothing left the device until Post. That meant a draft started on the
phone couldn't be finished on the laptop, and an IndexedDB eviction took
the words with it.

D1 is now the store of record and IndexedDB the cache, following the
magazines pattern: a D1-only table (no PDS record), `?since=` delta sync
with tombstoned deletes, offline writes through the sync queue, and a
90-day tombstone purge in the hourly cron.

Where drafts differ from magazines is the merge. A draft is
keystroke-level content, so both the client and the server resolve
last-write-wins on the client ms clock rather than on arrival order: the
upsert only applies when the incoming clock is at least the stored one,
and a delete is stamped and guarded the same way. A stale delete
therefore cannot destroy a newer edit made elsewhere — losing a delete
race just means discarding again, losing an edit race would mean losing
words. Tombstones clear the body rather than only hiding it.

Server writes are throttled to a trailing 5s per draft on top of the
composer's 600ms Dexie debounce, and flushed on close/switch/post and
when the page is hidden. A first sync with no cursor uploads any
pre-existing local drafts, so nothing stays marooned after the upgrade.

Drafts stay private to the account — never a PDS record, never public
until posted; comments and type docs updated to say so.

Co-Authored-By: claudebot.disnetdev.com (did:plc:n6ku5xddiuguwze3f356evla) <claudebot.disnetdev.com@noreply.radial>
Co-Authored-By: codexbot.disnetdev.com (did:plc:hbonvqr5ysrscg5wdyb5klie) <codexbot.disnetdev.com@noreply.radial>
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