Skip to content

Publishing and versions

In Menestrel, saving and publishing are two separate gestures. Everything typed in the admin is saved continuously, but nothing goes out until someone clicks “Publish”. This page follows one change end to end: the draft, the per-language statuses, the snapshot, the build at the host, and the rollback.

Every entry has a working draft, separate from the published versions. The admin saves it on its own: 2 seconds after the last keystroke, and at most every 15 seconds while typing continuously. Close the tab, reopen the entry on another device: the draft is there.

The draft is mutable, overwritten in place. History is made of immutable versions, consolidated at the moments that matter: at publish time, when the author changes, and at most once every ten minutes of continuous editing. An hour-long writing session does not produce hundreds of versions, and “who wrote what” stays attributed to the right author.

A multilingual entry is not published as one block. Each language carries its own status:

  • draft: never published in that language;
  • published: the live version is up to date;
  • needs update: published, but the default language has changed since. The site keeps serving the translation as is; the admin only flags that it is behind.

The Publish button follows the same split: “Publish fr”, “Publish en” or “Publish all languages”. So does validation: an empty required field in English blocks publishing English, not French, and the dialog lists what is missing, field by field.

Two gestures, one mechanism:

  • from the editor: “Publish” on the open entry, with a choice of languages;
  • from the Publishing screen: a contextual button gathers everything waiting (“Publish 3 changes”). Validation stays per entry and per language: failing pairs get unchecked with the offending field spelled out, and you resubmit without them.

Publishing never overwrites anything: entry versions are append-only. Publishing inserts a new published version; the full history stays browsable.

Every publish produces a snapshot: one JSON file per collection and per language (flattened values, language fallbacks applied, images resolved to CDN URLs), plus a manifest carrying the schema and the file list. Everything is pushed to the CDN and never changes again: in the database, a SQL trigger forbids any update to a snapshot. Only one thing is mutable, the latest.json pointer, which designates the live snapshot.

This choice carries the rest of the system:

  • the build never depends on the API: the loader reads the snapshot files from the CDN. With the application down, an astro build still passes;
  • rolling back is trivial: re-pointing latest.json is enough, no content to recompute;
  • history is faithful: each publish has its snapshot, so you know exactly what was live at what time.

A client fixing four pages in three minutes must not trigger four builds. Each project has a grouping window, 90 seconds by default, adjustable in the project settings. It absorbs bursts without ever delaying the isolated gesture:

  • quiet window: the first publish triggers the build immediately. Fixing a price during a phone call stays a round trip under three minutes;
  • burst: the following publishes inside the window are grouped into a single build, the one for the last snapshot. Queued builds for superseded snapshots become “Cancelled”: no build for content that is already obsolete.

During a burst, the admin shows “Grouped publish in 42s…” with a “Publish now” button that forces the send without waiting for the end of the window.

On top of that, a per-project hourly trigger quota applies, depending on the plan (4 on Libre, 12 on Site, 30 on Studio and Sur mesure). Beyond it, publishes are grouped by force, never rejected: the admin shows “Your changes are grouped automatically” with the go-live time.

Tracking all the way to “Live at 2:32 PM”

Section titled “Tracking all the way to “Live at 2:32 PM””

The build goes out to every host connected to the project: Vercel, Netlify, Cloudflare or GitHub Actions. Each deployment follows an honest state machine:

State Shown as Meaning
queued Queued the build is waiting to be triggered
triggered Triggered the host accepted the trigger
building Building the build is running at the host
live Live the site serves the new content
failed Failed the build failed, diagnostic attached
cancelled Cancelled superseded by a more recent publish

Triggering is attempted up to three times with backoff before declaring failure. Tracking then polls the host’s API, if an optional tracking token is configured on the target. Without a token, Menestrel stays honest: the deployment shows “Triggered at 2:31 PM” and stops there, never a fake “Live”.

Two display rules:

  • editors see editor vocabulary: “Publishing…”, then “Live at 2:32 PM”. Never build, deploy, hook or pipeline;
  • failures go to the agency, never to the end client: email plus an indicator in the admin, with a readable diagnostic. After three consecutive failures the host is paused (we stop hammering it); a “Resume” button reactivates it once the configuration is fixed.

Any snapshot in the history can be restored: pick the version (“Back to yesterday’s 6:04 PM version”), confirm. Menestrel re-points latest.json to that snapshot and re-triggers the hosts: the site rebuilds with the exact content of that moment, served as it was built.

Rollback is an emergency gesture: it goes out immediately, with no grouping window, and does not count against the hourly publish quota.

The snapshot history is kept according to the plan: 7 days on Libre, 90 days on Site, 1 year on Studio and Sur mesure. Two snapshots are never purged, whatever the plan: the live one and the previous one. Immediate rollback therefore always stays possible, even on the Libre plan.

To run through this cycle on a real project, follow Your first site in 10 minutes. On the code side, the loader is what consumes the published snapshot: see the loader reference.