Cross-device publish state: deterministic rkeys + PDS reconcile#36
Merged
Conversation
…e publish state Publish state was a per-browser localStorage receipt: a second device showed "Share to Bluesky" for an already-published recipe and would mint a DUPLICATE record, and an external delete left a dead "View on Bluesky". Rather than copy state into a DB (helps only the shared-DB self-hosted tier, and drifts from PDS truth), read the source of truth — the user's PDS. - Deterministic rkey (rkeyForLocal, from the local id): publishes now always putRecord at that key, so first publish and every re-publish target ONE record. No duplicate on a second device or cleared cache. Records published before this change keep their original rkey via the localStorage receipt (opts.rkey wins). - Reconcile effect: on sign-in (non-dry, once per item) verify a stored receipt still resolves (clear if deleted elsewhere), or with no receipt adopt a record found at the deterministic rkey — so a fresh device shows "View on Bluesky". localStorage stays as an optimistic cache; the PDS decides. findPublishedRecord / recordExists are public getRecord reads (no auth). - Fixes a latent bug: publishCollection passed the menu's rkey to inline child publishes, so on menu re-publish every child would overwrite the menu's key. Children now get their own deterministic rkey. Verified with a stub agent + real PDS reads: first publish → putRecord at rkey=id (no createRecord); re-publish honors an explicit legacy rkey; collection children publish to their own keys while the collection uses the menu key; recordExists true on the live Wine Night record, false on a bogus rkey; findPublishedRecord null when absent. Both tiers typecheck at baseline; web build passes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
pantry-host-marketing | 7003f66 | Jul 12 2026, 08:30 PM |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
pantry-host-web | 7003f66 | Commit Preview URL Branch Preview URL |
Jul 12 2026, 08:32 PM |
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.
Follow-up to #33. Publish state was a per-browser
localStoragereceipt, which meant:Rather than persist state in the app DB (which only helps the shared-DB self-hosted tier, requires a schema migration touching Rust + Node + web, and still drifts from PDS reality), this reads the actual source of truth — the user's PDS.
Changes (
packages/shared)Deterministic rkeys (
rkeyForLocal,atproto-publish.ts) — publishes now alwaysputRecordat an rkey derived from the local recipe/menu id. First publish and every re-publish target one record, so a second device (or a cleared cache) can't create a duplicate. Records published before this change keep their original rkey via the localStorage receipt (opts.rkeystill wins).PDS reconcile (
PublishToBlueskyButton.tsx) — on sign-in, once per item, non-dry-run:recordExists) and cleared if the record was deleted elsewhere;findPublishedRecord) is adopted, so a fresh device shows "View on Bluesky".getRecordreads (no auth).Latent bug fixed —
publishCollectionpassed the menu's rkey down to inline child recipe publishes, so a menu re-publish would overwrite the menu's key with each child. Children now get their own deterministic rkey.Verified
Stub-agent + real-PDS runtime test:
putRecordatrkey = id,createRecordnever calledchild-A/child-B, collection to the menu key (bug fix confirmed)recordExiststrue on the live Wine Night collection, false on a bogus rkey;findPublishedRecordnull when absentvite buildpassesScope notes
Separately filed: the self-hosted OAuth gap (real publishing only works via
127.0.0.1loopback today) — see the linked issue.🤖 Generated with Claude Code