Publishing stages
Publish Changes is an administrator action in the editor and Admin. Both controls use the same in-process publisher. It builds saved source into staged output beside the installation’s live dist/, checks that output, then attempts a local directory switch. It does not push Git, approve content, upload to a provider or give static readers a sign-in system.
Before publishing, agree which saved work may be released. The job includes other writers’ saved pages, not just the page open in the initiating browser. Unsaved browser drafts are excluded. Protect internal reader output independently at its host.
Prerequisites and scope
| Requirement | Why it matters |
|---|---|
| Admin session | Editors cannot start publication |
| Node and packages | Child build runs the current process executable and installed builder |
| Saved valid source | YAML, routes, templates and assets must build correctly |
| Writable output parent | Publisher creates temporary siblings and renames output directories |
| Reviewed audience | Every generated page and copied public asset belongs in the release |
| Recoverable old state | Generated previous output is not a source or account backup |
Service hardening must permit the output parent, not only writes inside current dist/. A service allowed to edit Markdown can still fail publication when it cannot create sibling folders or rename output. Correct ownership/service restrictions deliberately; do not grant broad filesystem access just to hide a failure.
The four stages
| Stage | Work and completion boundary |
|---|---|
| Taking a snapshot | Check source/parent, copy saved content and selected runtime files, count visible Markdown |
| Building the site | Run builder into temporary output; collect progress and bounded logs |
| Checking the new build | Require home/404/search files, valid search JSON and matching generated page count |
| Switching the live site | Rename old output aside, rename new output in, then rotate/record state |
A job/stage is pending, running, done or failed as applicable. Publisher status is idle before any current job, then running, done or failed. Detailed current job state is in memory; a restart loses it. Retained activity history is a separate, bounded summary, not a reconstructed detailed job.
Only one job can run in that publisher. A second start returns 409 with A publish is already running. This is one-process coordination, not a distributed lock across independently started editor servers. Do not run multiple uncoordinated publisher processes over the same output parent.
Exactly what is snapshotted
| Input | Consistency boundary |
|---|---|
| Content tree | Copied synchronously into a temporary saved-source tree |
| Runtime settings | Selected site-settings.json copied if present |
| Header links | Selected links.json copied if present |
| Link icons | Selected favicons/ tree copied if present |
| Static assets | Not snapshotted; builder reads installation assets |
| Templates | Not snapshotted; builder reads installation templates |
| File configuration | Not snapshotted; builder reads installation siteconfig.json |
Snapshotting prevents editor saves handled later in the same process from changing that copied content during child generation. It is not an all-input transaction against external filesystem writers. An operator/script or another process can still alter source during the copy, or non-snapshotted inputs during generation. Coordinate those operations, review staged output and do not promise a consistent atomic snapshot of every build input.
The child receives content/output/data selectors pointing at staged folders. Its environment is reduced to PATH, HOME and internal build-events=1, rather than inheriting arbitrary editor environment selectors. Assets/templates/config remain at the installation. It uses the current Node process executable, not a separately chosen shell Node on PATH.
Temporary and previous output
Names below describe implementation artifacts, not places to put manual source. The job identifier forms the suffix.
| Directory | Purpose |
|---|---|
| Publish output | Temporary generated build beside live output |
| Source snapshot | Temporary copied content beside staged output |
| Data snapshot | Temporary copied settings/links/icons |
| Aside output | Old live output temporarily moved out of the way |
| Previous output | Normally one prior successfully rotated reader |
With default output basename dist, patterns are dist.publish-<id>, that path plus .src/.data, dist.old-<id> and dist.previous. Read the actual reported paths before operating on them. Do not manually remove or move a folder while another publish may be using it.
The next job attempts to remove siblings starting with its output basename plus .publish- before creating fresh staging. Completion/failure cleanup is best effort. Storage permissions or interruption can leave artifacts. Retained .old-/.previous folders are recovery evidence, not generic junk to delete without inspection.
Build timeout, progress and diagnostics
The default child-build timeout is five minutes. At timeout it sends SIGKILL to the child and reports failure at the build stage. There is no documented environment variable for changing this internal option and no UI cancel/rollback action.
| Evidence | Bound |
|---|---|
| Internal log | Last 300 lines; each truncated to 600 characters |
| Status response | Last 80 log lines |
| Diagnostics | Last 60 lines plus stage, error, runtime, folders and page count |
| Page progress | Builder’s internal total/page events; malformed events ignored |
| Error detail | Last bounded stderr/error-like messages |
Progress describes generation, not approval, URL correctness, deployment or service availability. Hide progress closes the visible workflow but does not cancel the running job. The UI polls running status at roughly 400 ms and retries network polling after about 1500 ms; that is job feedback, not a guarantee of completion time or browser availability.
Logs/diagnostics redact recognized credential-shaped values. Redaction is pattern-based, not a guarantee that arbitrary private content is removed. Inspect diagnostic text before sending it outside the operator audience. Never put credentials into Markdown, filenames or build messages. Diagnostics include operational paths and the initiating username, which may be private even without a password.
What verification does and does not prove
Before switching, the publisher requires index.html, 404.html and search-index.json; it parses search JSON and checks that reported generated pages equal the counted total. Missing files, invalid JSON or an incomplete count fails the job before a switch.
That check does not crawl every link, validate every index entry’s schema/URL/anchor, render diagrams, inspect media, test accessibility, scan the release audience or check remote hosting. A build can pass and still publish a broken authored URL or an unintended public asset. Apply Review and release before committing the release and recheck the live reader afterward.
Switching is not atomic
The switch first renames old dist/ to an aside sibling, then renames staged output to dist/. A request between those operations can find no output. The implementation is not an indivisible atomic pointer change and does not promise zero downtime. A crash at the wrong point can require operator recovery.
After the new output is in place, the older dist.previous is removed and the aside version renamed into that backup slot. A rotation failure can leave the new reader live and report the aside folder as the previous version. Publication can succeed with that warning; do not infer that a warning means the old reader is still live.
The implementation confirms home exists after switching and attempts to write the publish marker. That is not a bytewise reread of the entire verified build. A marker-write failure is logged; the reader may be live while the unpublished indicator is unreliable.
Failure and retained state
| Failure point | State to inspect and safe response |
|---|---|
| Source/parent check | Old output normally untouched; fix path/ownership/source first |
| Build/timeout | Old output untouched; saved source remains; fix actual error and review before retry |
| Output verification | Old output untouched; staged result rejected, cleanup attempted |
| Move old output aside | Old output could not be moved; inspect its actual location/permissions |
| Move new output in | Publisher tries to restore aside; restoration itself can fail |
| Backup rotation | New output can remain live; previous version may be reported under aside name |
| Post-switch home missing | Job reports failure; inspect actual output and reported previous folder |
| Marker/history write | Underlying reader result not automatically reversed; indicator/history may be incomplete |
Read Live site, failed stage, error and actual filesystem together. A failed job label alone is not sufficient to choose what to restore, because a later failure can occur after switching. Do not repeat the publish action while uncertain whether another process/job is running or the new site is already live.
If restoration fails, diagnostics warn that the live site may be missing and identify the previous aside path. Pause publication, locate the actual old/new directories, preserve them and follow an operator-reviewed restoration plan. A blind deletion of .old- can destroy the needed recovery copy. Manual restoration must match your host/service ownership and avoid overwriting newer valuable output.
Unpublished indicator
The marker compares current source-target fingerprints with the last recorded editor publication. Targets include content, static assets, templates, site configuration, selected settings/links/icons. Fingerprinting recursively records file paths, sizes, rounded modification times and directory paths. It hashes that metadata, not file content bytes.
This detects normal edits/adds/deletes/renames and settings changes, but it is not a cryptographic content-integrity proof. Same-size edits with preserved rounded timestamps can evade it. It may include changes in files the builder later skips. A changed marker is not a Git diff, review approval or proof a remote reader is old.
No usable marker means the site is treated as not yet published from the editor. A manual command-line build does not record an editor publication. A settings/content change made after the captured fingerprint can correctly remain unpublished after a successful job. A failed marker write can make the indicator wrong; check actual source/output rather than trying to clear it by deleting data blindly.
A complete publication check
- Have writers save intended changes and stop unrelated operator edits for the release.
- Review saved source, selected data, public assets and recipient audience.
- Start Publish Changes once as administrator and wait for done/failed.
- If failed, preserve source and inspect the stage/live/filesystem state before recovery/retry.
- Open the local reader through its actual serving process. Verify new text, route, heading/search, media, diagrams and missing-page status.
- Check the reported prior output and retain it alongside source/private-runtime backups.
- Use your separate host deployment process to transfer the exact reviewed output.
- Verify remote home and deep routes, search, media, cache, 404 and redirects; record the deployed revision.
The previous generated reader cannot restore Markdown, accounts or browser drafts. No UI page history, rollback button, automatic retry or remote deployment is provided. See Backup and restore, Publish and deployment issues and Security and history for those distinct responsibilities.