Connect linkblogs to existing publications - #343
Open
disnet wants to merge 12 commits into
Open
Conversation
Co-Authored-By: codexbot.disnetdev.com (did:plc:hbonvqr5ysrscg5wdyb5klie) <codexbot.disnetdev.com@noreply.radial>
Co-Authored-By: codexbot.disnetdev.com (did:plc:hbonvqr5ysrscg5wdyb5klie) <codexbot.disnetdev.com@noreply.radial>
Co-Authored-By: codexbot.disnetdev.com (did:plc:hbonvqr5ysrscg5wdyb5klie) <codexbot.disnetdev.com@noreply.radial>
Co-Authored-By: codexbot.disnetdev.com (did:plc:hbonvqr5ysrscg5wdyb5klie) <codexbot.disnetdev.com@noreply.radial>
Sharing to a connected standard.site publication worked, but every reader still assumed the fixed `skyreader-links` publication. - The in-app pull (myLinkblogStore) now resolves the target and reads it alongside the default, reconciling entries by echoed siteUri. A pull that can't see the connected publication is no longer reported complete, so linkblogStore.reconcile() can't prune live shares out of IndexedDB. - Discovery resolves each listed author's current publication in one batched settings lookup, so new subscribers aren't scoped to a dead publication. - Notes are read back from all four content formats (Leaflet, pckt, Offprint, Markdown) in-app and on the public site, and note edits rewrite the note region in the record's own format instead of failing. The public site's Leaflet reader now reconstructs blockquote blocks too, with mention facets rebased. - PublicationMeta.url stays the Skyreader linkblog page (the shareable one); a connected publication's own site rides along as `externalUrl`. - Own-post affordances and the "Linkblog" source pill match the resolved publication rather than the default rkey. - The Skyreader linkblog is always offered in the settings picker, so there's a way back even before its record exists; only entries that link out are listed from a connected publication, and the copy says so. Co-Authored-By: claudebot.disnetdev.com (did:plc:n6ku5xddiuguwze3f356evla) <claudebot.disnetdev.com@noreply.radial>
… at them Connecting an existing publication means sharing a repo with the app that owns it, and the last iteration still assumed the whole publication was ours. - Skyreader's link posts now carry the same constant marker its publications do. Un-share, in-place note editing, and the "already shared" overlay require it (or the default publication, where everything is ours), so a Leaflet/pckt post that happens to link out can't be rewritten or — the destructive half — deleted. The write paths re-read the record and refuse with 409, so a stale client can't get past the client-side gate either. - The document batch endpoint re-points a request whose scope names a linkblog publication its author has moved off, echoing the requested scope back so the client's digests and reconciliation keys don't shift. Follower migration rewrote D1, but nothing could reach a follower's device; their feed just stopped updating. Clients also take feedUrl from the server on the next subscription sync, so they heal for themselves. - Discovery unions the local list of connected linkblogs over the Constellation marker registry: connecting stamps nothing (the record isn't ours to touch), so a user who connects before their first share was invisible to it. - subscriptions_cache persists site_url. The author's linkblog page is what says "linkblog" once the publication rkey is arbitrary, and it only existed on the device that created the subscription; the migration backfills it as followers move. The page is now recognized by its DID-keyed path, not a hostname prefix that both missed dev origins and matched unrelated `linkblogs.*` hosts. - A scope that errored during a linkblog pull no longer counts as complete, so reconcile() can't prune live shares against a snapshot it couldn't confirm. Co-Authored-By: claudebot.disnetdev.com (did:plc:n6ku5xddiuguwze3f356evla) <claudebot.disnetdev.com@noreply.radial>
…ying Closes the four findings left open on the previous iteration. The optimistic just-shared document now carries the `skyreaderLinkblog` marker, so it reads as ours in a connected publication and stays in the cross-device overlay while the proxy indexes the write. reconcile() also stops using the marker-gated overlay as its prune signal: it now keeps a local row whenever ANY pulled document points at that article. Deleting a live share is the destructive direction, and an unmarked document is ambiguous (a pre-marker share, or a proxy that doesn't forward the field yet), not proof of deletion. `site_url` now survives a PDS round-trip. The Jetstream mirror was an `INSERT OR REPLACE`, which re-creates the row and so silently cleared every column the record doesn't carry — `site_url`, the pending `atmosphere_previous_feed_url` migration, and `active` (a parked feed came back reactivated). It's an UPDATE of the fields the record actually carries, falling back to an INSERT for a genuinely new row. The push side was dropping `siteUrl`/`category` from the record it writes, so a changed subscription stripped them from the PDS; both are pushed and compared in the no-op check now. The linkblog page test anchors the whole path shape instead of looking for a DID anywhere in it — siteUrl is client-supplied, and plenty of Atmosphere apps key pages by DID. Discovery: a partial covering index on `user_settings.linkblog_publication` (the connected-author list ran a full table scan on every /discover), and the registry now unions the same marker query over `site.standard.document` so authors who share to a publication we didn't create are visible network-wide, not just to the deployment holding their settings row. Co-Authored-By: claudebot.disnetdev.com (did:plc:n6ku5xddiuguwze3f356evla) <claudebot.disnetdev.com@noreply.radial>
Playwright's global setup shells out to `wrangler d1 migrations apply` and buffers everything it prints. Wrangler reprints the whole migration table after each migration it applies, so a fresh CI database emits roughly the square of the migration count in bytes — at 64 migrations that is 1.1 MB, just past execFileSync's 1 MB default maxBuffer, and the whole E2E job died with `spawnSync npx ENOBUFS` before a single test ran. Nothing reads that stdout, so discard it instead of buffering it; the only output this step inspects (migration errors) goes to stderr, which now gets room to spare. Co-Authored-By: claudebot.disnetdev.com (did:plc:n6ku5xddiuguwze3f356evla) <claudebot.disnetdev.com@noreply.radial>
The connect flow shipped as two unstyled <select>s: one listing publication
names ("Untitled publication", twice), one asking for a "Content format" the
user had no way to answer. Nothing distinguished Skyreader's own linkblog from
a Leaflet or pckt publication except a "(Skyreader)" suffix.
Publications now arrive described. `GET /api/linkblog/publications` reads the
user's documents alongside their publications and infers each one's app from
the content lexicon its posts already use, falling back to the publication's
host — NSIDs are reverse-DNS, so `pub.leaflet.*` gives leaflet.pub without
guessing. Each choice carries its app, description, http-checked url, post
count, and the format Skyreader should write there. The document read is
best-effort: if it fails the list still returns, just plainer.
The picker is now radio rows in the save-backing vocabulary — Skyreader's
linkblog first and named as ours, then "Or a publication you already have",
each row showing name, app badge, host, post count and an open-in-new-tab
link. Selecting one pre-fills the format its own posts use and says so; the
format select is styled to match the inputs beside it instead of rendering as
a raw platform widget. With no other publications there's one quiet line
saying where they'd appear, and no dead controls.
Connecting also makes the split explicit: name and description are now shown
under "Your Skyreader linkblog" and hidden (not disabled) while a connected
publication is live, replaced by a line saying its home app owns those fields.
Co-Authored-By: claudebot.disnetdev.com (did:plc:n6ku5xddiuguwze3f356evla) <claudebot.disnetdev.com@noreply.radial>
The note under the button spoke as if a publication were already connected even on the common path where none ever has been. Split it three ways: what connecting does, what switching back leaves behind, and what Skyreader will and won't touch in someone else's publication. Co-Authored-By: claudebot.disnetdev.com (did:plc:n6ku5xddiuguwze3f356evla) <claudebot.disnetdev.com@noreply.radial>
The "this is public" dialog always said the share was going to the Skyreader linkblog page, even after the user connected an existing standard.site publication — which is where the post actually lands. It now names that publication and links its own address, and mentions the linkblog page separately as somewhere it also shows up. Co-Authored-By: claudebot.disnetdev.com (did:plc:n6ku5xddiuguwze3f356evla) <claudebot.disnetdev.com@noreply.radial>
Co-Authored-By: claudebot.disnetdev.com (did:plc:n6ku5xddiuguwze3f356evla) <claudebot.disnetdev.com@noreply.radial>
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.
Summary
Connect linkblogs to existing user-owned standard.site publications with format-aware document publishing.
Keep externally managed publication records read-only and migrate follower scopes durably across rapid publication switches.
Merge old and connected publication scopes on public linkblogs, with page, permalink, and RSS metadata loaded from the resolved connected publication.
Resolve the connected publication on every read path: the in-app linkblog pull, discovery's subscribe targets, own-post affordances, and the source pill.
Read notes back from all four content formats and rewrite them in place on edit; on the public site, reconstruct Leaflet blockquote blocks with rebased mention facets.
Keep
PublicationMeta.urlon the Skyreader linkblog page (a connected publication's own site rides along asexternalUrl), always offer the Skyreader linkblog in the settings picker, and list only entries that link out from a connected publication.Mark Skyreader's own link posts and gate every mutating affordance on that marker, so a post the connected publication's home app wrote can't be note-edited or deleted from Skyreader (server-side too, with a 409).
Correct a follower's document scope server-side when its author has moved publications, echoing the requested scope back; clients also take
feedUrlfrom the server on their next subscription sync.Union locally-known connected linkblogs into discovery's registry, and persist
site_urlso a followed linkblog still reads as "Linkblog" on a device that didn't create the follow.Stamp the marker on the optimistic just-shared document, and prune local shares only when NO pulled document points at the article — an unmarked document is ambiguous, not proof of deletion.
Keep
site_url,atmosphere_previous_feed_urlandactiveacross a Jetstream mirror (it was anINSERT OR REPLACE), and pushsiteUrl/categoryto the PDS record instead of stripping them.Anchor the linkblog-page path test to the whole path, index
user_settings.linkblog_publication, and union the marker query oversite.standard.documentso connected-publication authors are discoverable network-wide.Fix the E2E job crashing in Playwright global setup:
wrangler d1 migrations applyoutput grew pastexecFileSync's 1 MB defaultmaxBufferonce this branch pushed the repo to 64 migrations (spawnSync npx ENOBUFS).Describe each publication in the settings picker before asking the user to choose one: the list endpoint infers each publication's app from its own posts (falling back to its host), and the picker became radio rows showing app, address and post count, with the content format pre-filled from what that publication already publishes and the Skyreader linkblog's own name/description separated out.
Name the connected publication in the first-share "this is public" warning: it linked the Skyreader linkblog address even when links were going to a connected publication, and now names that publication, links its own address, and mentions the linkblog page separately as somewhere the post also shows up.
Checks
backend:npm run check(tsc + prettier) clean;npm test380/380 passing (4 new this round).frontend:npm run check0 errors (25 pre-existing warnings);npm test296/296 passing (7 new this round); Playwright E2E 27/27 (1 new).feed-proxy:npm run check:typesclean; prettier clean at the repo-pinned 3.8.1.feed-proxy'sbun test(no bun binary available in this environment; the change there is one Constellation query added to the registry), Playwright e2e/PWA suites (browsers not installed; no linkblog coverage there).Deploy order
feed-proxyforwards theskyreaderLinkblogmarker; deploy it with or before the frontend, otherwise pulled documents in a connected publication read as foreign (no Remove, no note edit) until it ships.Radial artifact
Radial artifact
Radial artifact
Radial artifact