fix(publish): republish the site when an entry's public state changes - #3
Draft
asachs01 wants to merge 1 commit into
Draft
fix(publish): republish the site when an entry's public state changes#3asachs01 wants to merge 1 commit into
asachs01 wants to merge 1 commit into
Conversation
A listing page is a static artefact: its `base.loop` over a content table is expanded once, at full-publish time, and baked into the slot. Per-entry publishing rewrites that entry's own artefact and nothing else, so the moment an entry enters or leaves public visibility every index that links to it is wrong, and stays wrong until a human presses Publish. A deleted post keeps a live card pointing at a 404; a post scheduled for 09:00 is missing from the index until someone notices. Publish, scheduled publish, unpublish, and delete now ask `server/publish/autoSitePublish.ts` for a background full-site republish, which is the only thing that re-expands a loop. Four rules make that affordable and safe: - Coalesced: the first request opens a 5s batch window and every request inside it is absorbed, so a backlog of forty posts costs one publish. The window is half a `publishScheduler` tick, so one tick's due rows land in a single batch. - Never re-entrant: at most one run is in flight, because two would race the slot swap. Requests raised during a run collapse into exactly one follow-up window. - Never recursive: guaranteed structurally, with an architecture test that fails the build if a new caller appears. A runtime origin check would lie — plugin publish.* handlers run in the QuickJS worker and their RPCs return on their own event-loop task. - Never a surprise publish: `publishDraftSite` promotes the draft, so a run proceeds only while the draft already matches what is published. It then changes no page and re-expands the loops and nothing else. With unpublished site edits present the run is skipped and logged. The rebuild is background work, so an author's request returns as soon as their entry commits. A failed run is logged and dropped: the entry publish already committed and the bake reaches `swapSlot` only on success, so the live site is untouched. Attribution is the system actor, the convention the scheduled-publish tick already uses — nobody asked for this site publish, which is also why `publishDraftSite` now takes `string | null`. `AUTO_SITE_PUBLISH_ON_ENTRY_CHANGE=0` (also false/off/no) restores the old behaviour for operators who publish on their own cadence. Default is on: the stale listing is a correctness bug, not a preference.
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
Published listing pages drift out of step with their entries, and this fixes it.
A listing is a static artefact like any other page: its
base.loopover a content table is expanded once, at full-publish time, and the resulting cards are baked into the slot.publishDataRowrewrites the entry's own artefact and nothing else — it never re-expands anybody else's loop. So the moment an entry enters or leaves public visibility, every index that links to it is wrong and stays wrong until a human presses Publish.Both halves of that were hit in production on
blog-cms.wyretechnology.com:/blogwhile its own URL 404'd;/blogfor hours.publishScheduler.tscallspublishDataRowto flipstatusand does nothing else.Three more posts are scheduled (2026-09-07, 2026-09-14, 2026-10-12) and would each have hit the same thing.
This cannot be fixed from a plugin.
content.entry.*hooks exist, but the only publish-ish RPC target iscms.content.republishAll, andrepublishSinglePagere-renders an existing snapshot and discards the HTML — its documented purpose is firing hook side-effects. It cannot see new content and cannot rewrite a listing. There is nocms.site.publishtarget.What changed
Publish, scheduled publish, unpublish, and delete now call
requestAutoSitePublish(db, uploadsDir)(newserver/publish/autoSitePublish.ts), which runs one backgroundpublishDraftSite— the only thing that re-expands a loop.publishSchedulertick, so one tick's due rows land in one batch.publish.*handlers run in the QuickJS worker and their RPCs return on their own event-loop task.publishDraftSitepromotes the draft, so a run proceeds only while the draft already matches what is published — it then re-expands the loops and changes nothing else. With unpublished site edits present it is skipped and logged.Background, so the author's request returns as soon as their entry commits. A failed run is logged and dropped: the bake reaches
swapSlotonly on success, so the live site is untouched. Attribution is the system actor (null), the convention the scheduled-publish tick already uses — hencepublishDraftSitenow takesstring | null.AUTO_SITE_PUBLISH_ON_ENTRY_CHANGE=0(alsofalse/off/no) restores the old behaviour. Default on.Verification
bun run buildbun test— 6627 pass / 1 skip / 0 fail (baseline onwyre-release: 6616 / 1 / 0; +11 new)bun run lintcompose.prod.ymlpasses the new variable through; not run against a live stackChecklist
docs/features/publisher.md,docs/server.md,docs/features/content-storage.md,docs/reference/architecture-tests.md,docs/deployment/README.md,.env.example,.env.production.example,CHANGELOG.md.Notes for reviewers
site_snapshotsrow plus onedata_row_versionsrow per page, the same as a manual Publish.handleContentEntriesPublish/Delete/DeleteMany) have nouploadsDirand already skip artefact writes and prunes. That is a pre-existing gap and a separate change.An upstream PR against
corebunch/instaticcarries the same commit.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.