Engineering Notes · Architecture
Reading time · 10 min
One Website Across Editing, Versions, and Export
How a single authoritative representation — base HTML plus ordered patches — produces consistent results across the editor, version restore, preview, and export without drift.

A website built in a visual editor exists in more contexts than most users realize.
It is viewed in the editing canvas while changes are being made. It is previewed in a separate environment for review. It is saved as a version that can be restored later. It is exported as a downloadable archive. It may be published to production hosting.
Each of those contexts has different runtime constraints, different available resources, and different expectations about what scripts are present and what URLs are valid.
If the system treats each context as an independent rendering problem, they drift. A section hidden in the editor reappears after export. An image visible in preview resolves to a broken URL in the download. A restored version does not match the state the user remembers saving.
StackLiberate is designed so that one authoritative representation produces all of those views. The same source of truth is materialized differently depending on context, but the authored content, structure, and design remain consistent throughout.
One website. Many materializations. Zero drift between them.
The source of truth is not a rendered page
The authoritative state of a StackLiberate project is the combination of two things:
- The base HTML — the original crawled or AI-generated website, stored immutably in R2.
- The ordered patch list — every user modification represented as a typed operation targeting a stable element identifier.
Neither the editor canvas, nor the preview iframe, nor a previously exported ZIP is the source of truth. They are all projections of that underlying pair.
This means there is exactly one path to "what the website currently looks like": apply the patches to the base HTML in order. Any system that does this — the editor, the preview renderer, the export pipeline, the version restore system — will produce the same result.
The canonical representation is never a DOM tree in a running browser. It is data that can be applied to produce a DOM tree, a static file, a screenshot, or any other derivative.
Editing operates on the representation, not the view
When a user changes a headline in the StackLiberate editor, the system does not record "the innerHTML of element X is now Y."
It records a text patch: "apply text content Y to the element identified by data-sl-id X."
The editor then projects that patch onto the iframe so the user sees the change immediately. But the projection is ephemeral — it exists only in the browser's memory. The durable change is the patch added to the ordered list.
This distinction is critical for multi-context consistency:
- If the editor crashes, unsaved projections are lost but all persisted patches remain valid.
- If the user reloads, the page is rebuilt from base HTML + patches — arriving at the same visual state without depending on browser memory.
- If a version is restored, the system replaces the current patch list with the version's snapshot. No DOM diffing is required.
- If the site is exported, patches are applied server-side with Cheerio. No browser is involved.
Because every context applies the same patches to the same base, they converge to the same result by construction.
Versions are snapshots of patch state
A StackLiberate version is not a copy of rendered HTML. It is a snapshot of the patch list at a point in time.
When a user saves a version, the system stores the complete ordered patch array as JSON. When they restore a version, the system replaces the current patch list with the stored one.
This has important properties:
Versions are lightweight. They store only the delta information (patches), not a complete copy of every page. A project with 50 versions does not require 50 full copies of the website.
Versions are deterministic. Restoring version N always produces the same result, because it means "apply patch list N to the immutable base HTML." The base never changes, so the version's meaning never changes.
Versions compose with the existing pipeline. A restored version passes through the same rendering path as a newly edited project. Preview, export, and publish all operate on the restored patch list identically to how they operate on any other patch list.
There is no special "version mode" in the system. A restored version is simply the project with a different patch list. Every downstream system handles it through its normal path.
Preview and export share a rendering contract
The preview system and the export pipeline both need to answer the same question: "What does this website look like with these patches applied?"
They answer it in different environments — preview runs in a browser iframe, export runs on the server — but they share the same patch application logic.
The rendering contract is:
- Start with base HTML.
- Apply patches in order, targeting elements by
data-sl-id. - Resolve asset references to their appropriate URLs for the target environment.
- Strip editing instrumentation (bridge script, selection markers, development attributes).
The preview renderer serves the result inside an iframe with the bridge script injected for continued editing. The export renderer writes the result to a ZIP file with assets downloaded locally and URLs rewritten to relative paths.
Both start from the same source. Both apply the same transformations. The difference is only in the final delivery format and the URL resolution strategy.
This shared contract is what makes it possible to promise that "what you see in the editor is what you get in the export." The promise is not enforced by pixel comparison. It is enforced by structural identity — the same operations produce the same document, differing only in how external references are resolved.
Asset identity prevents URL drift
A common source of inconsistency in website builders is asset handling. An image might display correctly in the editor (because it resolves against a CDN), fail in local preview (because the URL is absolute and the server is different), and break in export (because the file was never downloaded).
StackLiberate addresses this by giving every asset a content-addressable identity in R2 storage. When a website is crawled or an image is uploaded, the asset is stored with a key derived from its content hash. The HTML references this asset by its R2 path.
Each rendering context then resolves that R2 reference according to its own rules:
- Editor preview: resolves to the R2 public URL, so images display in the iframe.
- Export: downloads the asset from R2, places it in the ZIP's asset directory, and rewrites the URL to a relative path like
./assets/abc123.webp. - Publish: resolves to the production CDN path where the asset has been deployed.
The asset's identity is stable across all contexts. Only its URL resolution changes. This eliminates the class of bug where an image "works in preview but breaks in export" — both contexts reference the same asset by the same identity, and the resolution is a mechanical transformation that either succeeds completely or fails detectably.
The autosave contract
Patches are the source of truth, and autosave is how they move from browser memory to persistent storage.
The autosave system in StackLiberate must preserve a critical invariant:
The system must never report patches as saved if newer edits have occurred since the save began.
This is a concurrency problem. A save request takes time. During that time, the user may continue editing. If the system marks state as "clean" when the save response arrives, it may be lying — the user has made changes that were not included in the saved payload.
StackLiberate tracks dirty state independently of save requests. A successful save marks the specific patch list that was sent as persisted. If the current patch list has diverged from what was sent, dirty state remains true regardless of the save response.
This prevents a subtle but dangerous failure mode: the user believes their work is saved, closes the browser, and loses recent edits because the "saved" indicator was reflecting a stale save rather than the current state.
Export is not a separate system
Some website builders treat export as a conversion step — a process that takes the platform's internal representation and translates it into web files.
That translation is where inconsistencies hide. Every conversion involves interpretation, and interpretation can diverge from what the editor showed the user.
In StackLiberate, export is not a translation. It is a materialization of the same representation that the editor and preview system already use:
Base HTML (immutable, stored in R2)
+
Patch list (ordered, typed operations)
+
Asset resolution (R2 references → local relative paths)
+
Sanitization (scripts stripped, safety validation)
=
Standalone static website (ZIP)No step in this pipeline involves the editor. No step requires a browser. No step depends on the user's session state, network conditions, or UI preferences.
The export pipeline can run at any time, against any version, and produce the same result. It is a pure function of the stored data.
This is what makes the "export a working website" guarantee credible. The system is not promising to faithfully translate its internal format into something portable. The internal format is already the portable representation — patches applied to standard HTML. Export merely resolves the final details of where assets live and writes the result to disk.
Consistency by construction
The traditional approach to keeping multiple views of a website consistent is testing: render the page in the editor, render it in preview, render it in export, and compare the results.
That approach catches drift after it occurs. It does not prevent it.
StackLiberate's approach is consistency by construction. The views do not need to be compared because they are produced by the same transformation applied to the same data. Drift requires either the data or the transformation to differ between contexts.
The data cannot differ because there is only one stored copy — base HTML in R2, patches in Postgres.
The transformation is shared — Cheerio-based patch application with the same targeting logic in every context where server-side rendering occurs, and equivalent bridge-based application in the browser.
When a new patch type is introduced, it must be implemented in both the bridge (for live preview) and the Cheerio applicator (for export and server-side rendering). This is a deliberate coupling. It ensures that any editing capability that works in the browser also works at export time, and vice versa.
If a patch type cannot be implemented in both environments, it cannot ship.
That constraint prevents the most common failure mode in visual editors: features that work beautifully in the editing experience but produce unexpected results when the website leaves the editor's control.
The lifecycle as a single path
A StackLiberate website follows a single continuous path from creation through editing, versioning, preview, and export:
- Creation: Base HTML is produced (by crawl or AI generation) and stored immutably.
- Editing: User changes produce patches appended to an ordered list. The editor projects them onto the iframe.
- Autosave: Patches are persisted to Postgres. Dirty state is tracked independently.
- Versioning: Patch list snapshots are stored. Restore replaces the current list.
- Preview: Base HTML + patches rendered in sandboxed iframe via bridge.
- Export: Base HTML + patches applied server-side, assets localized, written to ZIP.
- Publish: Same pipeline as export, with deployment-appropriate asset resolution.
At every stage, the question "what does this website look like?" has exactly one answer: apply the current patches to the base. The only thing that changes between stages is how and where that answer is delivered.
That simplicity is the point.
A website that can be represented as one authoritative state materialized into many contexts is a website that can survive any of those contexts disappearing. The editor can be removed. The preview system can change. The hosting provider can be replaced. The export format can evolve.
The website remains the same, because its identity is not bound to any single way of viewing it.