Preview the artifact you will publish

M31V stopped approving an editor approximation and began reviewing the exact immutable build proposed for activation.

M31V's first preview server rendered source files on demand. It skipped final asset fingerprinting, used development styles, included drafts automatically, and did not generate the real feed or redirect configuration.

An article could pass review in preview and fail after publication because preview and release were two implementations of the same idea.

I replaced that arrangement with artifact preview. The build produced one complete, immutable candidate. Review, verification, upload, and activation all referred to that candidate by identity.

Preview stopped meaning “approximately how the source may look.” It meant “the thing we are considering publishing, served without making it public.”

Development view and release preview had different jobs

Writers needed fast feedback while editing. Rebuilding a complete archive and every asset after each keystroke made the workflow sluggish.

M31V kept a development view with incremental rebuilds and helpful overlays. It was explicitly labeled working preview. It could show drafts, source positions, and incomplete content.

Approval required a release candidate built through the full pipeline:

validate complete content graph
resolve publication state at explicit clock
generate every page and protocol surface
fingerprint and rewrite assets
assemble redirect configuration
write build manifest
run artifact checks
seal candidate directory

The two modes reused parsers, renderers, and validation. They differed in latency, annotations, and completeness. The product did not pretend their outputs were interchangeable.

Fast iteration and faithful review became separate named milestones instead of one preview trying to optimize both.

The candidate received identity before review

Every complete build received an immutable ID tied to source revision, build configuration, publication clock, and generated manifest.

pressroom-2015-08-04-1427-b071

The human-readable portion helped discussion; the manifest and artifact digest distinguished actual contents. Rebuilding changed identity even from the same source if a relevant input differed.

Review URLs contained the build ID:

preview.example.invalid/builds/pressroom-.../notes/article/

A comment saying “the image is wrong on preview” could now name one candidate. New source produced a new URL rather than mutating the old preview underneath the comment.

The candidate did not become current merely because it was newest. It became eligible after validation and review. Identity separated chronology from approval.

This was the same lesson as operation IDs in X8B6 applied to releases: one identity belonged to one declared payload.

The preview host served the generated directory through production-like URL rewriting and cache rules. It did not run the source parser per request.

That exposed problems the old server hid:

  • a generated page used the wrong relative asset path,
  • a fingerprinted file was missing from the upload manifest,
  • directory-index routing differed from development routes,
  • a case-sensitive host rejected a path that worked locally,
  • a canonical URL pointed at the public origin incorrectly,
  • a feed used development-only data.

The host added access control and a small preview frame at the boundary rather than injecting markup into every artifact file. Downloading the candidate produced the same public bytes proposed for release.

Serving the artifact made preview less intelligent and more trustworthy.

Origin-dependent values were declared inputs

Canonical URLs, feed links, and sitemap locations needed the public origin. A preview hosted under another domain should not rewrite the artifact into a different semantic candidate.

M31V built public canonical URLs using the intended release origin. The preview host served them as-is and intercepted navigation only where the review shell needed to keep access context.

This meant clicking a canonical link could leave preview and reach the current public release. The frame clearly offered “open preview path” and “open public canonical URL” as separate actions.

For network requests that had to stay within the candidate, the artifact used path-relative or origin-relative URLs according to publication policy. Dynamic forms were disabled or pointed at a safe preview endpoint, never a production mutation service by accident.

Origin behavior belonged in the build contract. Preview did not silently patch strings and create an artifact no other stage saw.

Unpublished articles required protection. An unguessable build ID was not authorization.

The preview host sat behind authentication and prevented indexing with response headers and robots policy as defense in depth. Build listings required access. Logs avoided storing article bodies in query strings.

I did not place shared credentials inside the static artifact. Access happened at the hosting boundary, so the same candidate could later move to the public release directory without secret removal.

Review links expired according to project policy, while archived artifacts could remain in protected storage for provenance. A candidate rejected for sensitive content was removable without rewriting other candidates.

The preview system acknowledged a limitation: anyone authorized to view could capture the content. It protected access, not control after viewing.

Security remained separate from artifact identity and editorial status.

Draft inclusion was build state

The old preview always showed every draft. Reviewers sometimes approved a candidate without noticing a second unfinished article in the same build.

Release candidates used an explicit publication set. A draft could be included for focused review through a declared preview-only view, but that candidate was not eligible for public activation.

The manifest said:

candidate class: public-eligible | preview-only
publication clock: ...
published nodes: ...
included draft nodes: ...

The review frame displayed the class prominently. Promotion did not toggle a flag on the same artifact. Removing preview annotations or drafts required a new public-eligible build and therefore new identity.

This prevented an editor switch from turning a broad working build into a release without rerunning validation and generation.

Eligibility was a property of the sealed artifact, not a button state in the preview UI.

Review covered surfaces beyond the page

The old preview concentrated on article HTML. M31V's publication artifact also contained author archives, topic pages, feed, sitemap, search index, redirects, error pages, and assets.

The review index linked every changed or affected surface. For one new article it might show:

article page
home listing
author archive
two topic archives
feed entry
sitemap entry
search-index record
related-content changes on three older articles

The list came from the content graph dependency map, not a manual checklist. Review could focus on affected surfaces while complete verification still covered the artifact.

Protocol outputs had human-readable inspectors. The feed inspector parsed the generated XML and displayed entry identity and dates without replacing actual schema validation.

Preview became a view of the release's consequences, not merely the new page.

Development preview sometimes served source images directly. The release generated derivatives, fingerprinted them, and rewrote references. Approval had never seen the exact selected assets.

Candidate preview served final derivative names and cache headers. The asset manifest connected source file, output variants, dimensions, byte size, and references.

Reviewers could inspect which candidate an image element selected under a viewport, whether layout dimensions were reserved, and whether alternate text came from the normalized figure. Performance warnings linked to the final output, not the larger source file alone.

Unreferenced output assets failed or warned according to policy. Missing references failed before preview became available.

This caught a build bug that generated the right image but rewrote a URL with the previous fingerprint. Source preview looked correct; artifact preview returned a missing file.

Fidelity meant following asset transformation all the way to the served bytes.

Reviewers wanted source line links, viewport controls, build metadata, and comments. Injecting those features into candidate pages risked creating another rendering path.

I kept tools in a surrounding frame and communicated with the page through a narrow review bridge where necessary. The artifact remained intact inside its frame. A “view raw candidate” option removed the shell entirely.

The shell could read the manifest and map public path to source reference. It did not ask the page template to render editor-only markup.

Some behavior, such as frame-related sizing or same-origin restrictions, could differ from the final host. Raw view and automated artifact checks covered that gap. The review UI named when it was framing content.

Preview tooling should explain the candidate without modifying the evidence under review.

Comments belonged to candidate and location

A review comment stored candidate ID, public path, optional source reference, viewport context, and text. It did not edit article source directly.

candidate: b071
path: /notes/release-previews/
source: content/articles/release-previews.md:42
viewport: 390 × 844
comment: figure caption wraps under the image credit

When a new candidate arrived, comments remained attached to the old one. A reviewer could mark them resolved in the newer candidate after verifying the change. The system did not float comments onto similar DOM coordinates where they might refer to different content.

This was a simple review log, not a collaborative editor. Source changes still happened in files and version control.

Candidate identity made feedback historically coherent. It also prevented “already fixed” discussions where two people had unknowingly reviewed different mutable previews.

Before publishing a preview link, M31V ran checks over final files:

  • every generated internal URL resolved,
  • canonical paths were unique,
  • feed and sitemap parsed,
  • redirect graph had no loop or missing destination,
  • asset references existed and matched manifest,
  • public-eligible artifacts contained no draft nodes,
  • HTML had required title, language, and document structure,
  • byte budgets and warnings were recorded,
  • release manifest matched directory contents.

The preview frame showed pass, warning, or failed status. Failed candidates could still be inspected locally for diagnosis but could not enter approval or activation.

Automated checks did not replace visual review. They ensured visual review did not spend time discovering a missing sitemap file or broken archive link that the artifact could prove mechanically.

The report traveled with the candidate through upload and activation.

Upload verified identity again

The preview host and release host were separate. Copying the artifact introduced another chance for partial transfer or corruption.

After upload into a new release directory, the deployment script recomputed or checked the manifest against server files. It queried the candidate through the host's real routing before activation.

The system did not rebuild on the release host. Rebuilding could produce different bytes from the reviewed artifact due to tool or time differences. It moved the sealed candidate.

If verification failed, the directory remained inactive and the preview candidate retained its approved identity. A new upload attempt used the same artifact; a source or build change created a new candidate and required review.

This made “approved” refer to contents, not to a Git branch that might advance after approval.

Release directories were immutable after upload. Activation switched the active pointer to the approved build. The same files previously served under protected preview became the public release.

There were host-level differences—public origin, cache edge, protocol negotiation—that preview could not fully reproduce. Post-activation smoke checks verified those boundaries. The artifact itself did not change.

Rollback switched to the prior verified release. It did not run the generator backward or reconstruct overwritten files.

The build manifest allowed a public request to be connected to the candidate reviewed earlier. Diagnostics could say “active build b071” and compare it with approval records.

Artifact preview completed a chain of custody from source to public state.

Scheduled releases still needed a fresh candidate

A candidate built days before a scheduled date had a publication clock and dependency state from its build. If another article or redirect changed before activation, simply turning on the old candidate would roll those changes backward.

M31V rebuilt scheduled releases against the intended publication time and current source, producing a new candidate for automated validation and whatever review policy the change required.

For high-confidence unchanged content, the system could compare manifests and affected surfaces. It did not mutate the old candidate's status at midnight.

This made scheduling a build-and-activation workflow, not a hidden conditional embedded inside static files. The scheduler's failure could be detected because the expected candidate or activation event was absent.

Artifact immutability clarified that time-dependent publication creates a new public state and deserves a new identity.

Preview could reproduce generated files, URL routing, compression, and much of the host configuration. It could not perfectly reproduce public DNS, every edge cache, reader authentication state, or network geography.

The preview report separated artifact checks from environment checks. Activation gates covered public certificate, redirect behavior, negotiated protocol, cache headers, and representative routes.

This prevented “preview is identical to production” from becoming a comforting exaggeration. It was identical in the most important controlled unit: the sealed artifact. Host boundaries remained observable release work.

When a bug appeared only publicly, the manifest helped rule out content-byte drift and focus on environment. When bytes differed, the chain immediately failed.

Fidelity became specific enough to be useful.

Candidate retention had a policy

Keeping every preview forever exposed drafts and consumed storage. Deleting them immediately after activation weakened provenance and made review history hard to audit.

M31V retained active and recent rollback artifacts, approved build manifests, and selected rejected candidates tied to important decisions. Routine working previews expired sooner. Sensitive rejected candidates could be removed immediately with their review records preserving only safe metadata.

The retention job operated by candidate identity and status. It never deleted the active release or its rollback predecessor. A pinned incident candidate remained protected.

Comments and manifests followed compatible retention. A comment was not useful if the referenced candidate disappeared without an archival decision.

Preview infrastructure became a small release repository, not an endless pile of temporary directories.

An approved candidate could wait while host configuration, redirect policy, or a required dynamic endpoint changed. The bytes remained identical, but the environment assumptions recorded in its verification report might no longer hold.

Approval therefore carried a scope and a practical validity window. Before activation, the deployment gate checked that required configuration versions and dependent endpoints still matched the candidate manifest. A mismatch did not mutate the artifact or silently extend approval. It requested re-verification and, where the visible result changed, another review.

This avoided two extremes. Approval was not invalidated by every unrelated repository commit, and it was not treated as timeless permission to publish old assumptions.

The record said who approved which candidate, after which checks, for which publication clock and environment contract. M31V remained a personal project, so the workflow was lightweight; the precision still mattered.

Artifact immutability fixed the object under review. It did not freeze the world around that object.

The workflow exposed one bad assumption

I had assumed preview was primarily a visual convenience. Artifact preview revealed that it was a release-control boundary.

It answered:

which exact bytes are under discussion?
which public surfaces will change?
which checks passed on those bytes?
which source and clock produced them?
which candidate received approval?
which artifact was uploaded and activated?

An on-demand source renderer could answer how one file looked now. It could not answer those release questions because it kept changing and omitted much of the artifact.

The new workflow cost more build time and preview storage. It removed ambiguity from review, transfer, and rollback. Fast working preview preserved the writing loop so fidelity did not have to make every keystroke expensive.

Preview became evidence

M31V's final preview was not a second version of the site. It was a protected way to serve the exact candidate artifact proposed for release.

That choice connected editorial review to release engineering. Comments belonged to build identity. Asset selection matched final output. Archives, feed, redirects, and search were visible consequences. Upload verified the same manifest. Activation changed only the public pointer.

The approach did not make public hosting identical to preview or eliminate human review. It made the controlled part of the release stable enough that differences could be named.

I still value instant component and source previews. They are excellent tools for creation. I no longer ask them to prove what will be published when the real product is a complete, generated, deployed artifact.

Review the artifact you will activate. Anything else is a sketch of the release.