cartwright
Features

Visual Builder

A governed, three-panel no-code page builder. Compose pages from a whitelisted section registry; output is stored as audited data in Page.layoutJson, never code written to disk.

The Visual Builder is a no-code page editor that stays true to Cartwright's data-not-code doctrine: you arrange sections in a UI, and the result is saved as a validated section tree — auditable, revertible data — not generated files. It's default-off behind the visualBuilderEnabled flag and changes nothing about a shop until you build a page.

Ships in engine v0.23.0. Default-off and canary-safe — a null layout renders exactly as before.

The three panels

/admin/visual-builder is a three-panel surface:

  • Left — sections. Add, reorder, and hide sections. The catalog is a fixed registry (see below), so the builder can never produce an unknown layout.
  • Center — live preview. An iframe (/[locale]/builder-preview) renders the working tree against your real design tokens, so what you see is what ships.
  • Right — inspector. Edit the selected section's fields; an AI "generate section" action can fill them for you.

A shared PageSections component renders both the preview and the published page, so the preview is the production output — not an approximation.

The section registry

Sections are a whitelist, each with a typed (Zod) props schema:

SectionPurpose
heroHeadline, sub-line, call-to-action.
featureGridA grid of features / value props.
ctaFooterClosing call-to-action band.
richTextFree prose block.
vibeSanitized HTML block — the bridge for Vercel v0 generation.

Because every section validates against its own schema, an AI generation can fill a section's props but can never emit arbitrary markup. The structured sections are filled by Anthropic; the vibe section is filled by Vercel v0 when v0Generator is on — v0's HTML is sanitized into the section's { html } props and sanitized again on render, so even generated markup stays inside the same governance and render path.

Stored as audited data

Published layouts live in Page.layoutJson — a validated section tree. When it's null (the default), the page renders from your existing body / vibeHtml, so nothing changes until you build.

Every write goes through the pages.set_layout tool, which means it inherits Cartwright's governance spine:

  1. Plan-first confirmation token — the change is described, then confirmed.
  2. Audit-log entry — who changed what, when.
  3. One-click revert — restore the previous layout from the audit trail.

Enabling it

visualBuilderEnabled is a compile-time flag (default-off). Before enabling, run a schema push to add the additive Page.layoutJson column:

pnpm db:push

With the flag off, /admin/visual-builder is not mounted and the storefront is byte-identical.

On this page