Skip to content

Pantry Host 0.5.0 — Share to Bluesky

Choose a tag to compare

@jpdevries jpdevries released this 12 Jul 19:00
· 36 commits to main since this release

Until now, Pantry Host's AT Protocol story was one-directional: you could browse, deep-link, and import any exchange.recipe.* record on the network, but your own recipes stayed home. 0.5.0 closes the round trip. Sign in with your Bluesky account and publish any recipe — or a whole menu — to your own PDS in one tap. Import a recipe, edit it, publish it back, import it again; nothing is lost that wasn't lossy on the way in. Your records live on your Personal Data Server and travel with your identity — Pantry Host infrastructure never stores them.

The write path is already proven in the wild: the first real records published from inside Pantry Host — a Wine Night menu with four recipes, photo blobs and all — are live on the network and browsable in the community feed.

✨ Highlights

🦋 One-tap publish (#33)

A Share to Bluesky button on every recipe and menu detail page, styled into the export row, on both tiers.

  • Recipes → exchange.recipe.recipe: ingredients, instructions, ISO-8601 durations, yield, keywords — with diet tags (vegan, gluten-free, …) promoted to the lexicon's suitableForDiet definitions.
  • Menus → exchange.recipe.collection: children are resolved cheapest-first — Reuse (already on your PDS), Cite (imported from Bluesky — your collection strongRefs the original author's record, never re-publishing their work as yours), or Publish inline.
  • Re-publish updates the same record in place (putRecord, stable AT URI); Unpublish deletes it. Publish receipts are remembered per browser.
  • Real createdAt — records carry your recipe's actual creation date, not publish time.

📷 Photos ride along

photoUrl is fetched, downscaled to the PDS blob cap (1600 px longest edge / ~950 KB, JPEG quality fallback), uploaded via uploadBlob, and attached as embed.images — the same field the import path reads, so photos survive the round trip. Small images pass through untouched (animated GIFs keep their animation). A photo failure never blocks a publish. The browser PWA's OPFS-stored photos resolve through a pluggable fetcher.

🪪 Attribution, preserved

Publishing an imported or adapted recipe emits the lexicon's adaptedFrom attribution — originalUri for Bluesky-imported recipes, sourceUrl for web imports — so provenance chains stay intact across the network. Auto-tags added on import (bluesky, mealdb, …) are stripped on publish so round trips don't re-ingest their own markers.

🔍 Preview before it leaves your kitchen

Every publish routes through a preview modal showing the exact JSON record that will be written, whose PDS it lands on, the per-child plan for collections (Reuse / Cite / Publish badges with a record-count summary), and any attribution. Confirming flips the modal to a confirmation step: the minted AT URI with click-to-copy, and — on real publishes — a QR code + share link to the record's own detail page.

🔐 OAuth with granular scopes

Sign-in via atproto OAuth + DPoP (@atproto/oauth-client-browser) requesting exactly what the feature needs and nothing more: repo:exchange.recipe.recipe, repo:exchange.recipe.collection, and blob:image/*. Hosted client metadata in production; the spec's loopback client on 127.0.0.1 for development. A Bluesky Account section in Settings shows who's signed in and offers sign-out.

🦺 Dry-run by default

A real publish takes two deliberate acts: set ATPROTO_PUBLISH_DRY_RUN=false and click through the preview. Dry runs mint visibly synthetic receipts (DRYRUN-… rkeys), and real publishes never trust them — no dry receipt is ever reused as a collection strongRef, re-published over, or "deleted" from a PDS it never reached. On the self-hosted tier the flag reaches the browser via a _document.tsx meta tag (Rex doesn't inline process.env into client bundles).

♿ Accessible confirmation

The publish outcome is announced through a persistent role="status" live region that mounts empty and is populated on success — the pattern screen readers reliably announce (WCAG 2.1 AA 4.1.3) — with focus moved to the confirmation heading as a second signal.

📚 Under the hood

  • New shared modules: atproto-publish.ts (record builders + publish/unpublish), pds-published.ts (receipts), contexts/BlueskyAuth.tsx (OAuth session), plus BlueskySignInModal, PublishPreviewModal, PublishToBlueskyButton, BlueskyAccountStatus, and OAuth callback routes — one implementation shared by both tiers.
  • Marketing: the AT Protocol section's publish milestone graduates from "Coming next" to a live tile linking a real published collection.

⚠️ Known limitation

Recipes whose photoUrl points at an external host without permissive CORS publish without their photo (gracefully — a console warning, never a failed publish). Recipes with locally-uploaded photos are unaffected. A proxy-assisted path is under consideration.

🧰 Prior groundwork in 0.4.1

The community feed indexer is now self-healing (watchdog reconnect, honest health checks, on-demand backfill) — so records you publish actually show up in the feed.

🤖 Generated with Claude Code