4.1 KiB
Theme Builder -- editor UI
This directory documents the two-stage plan for the in-browser theme editor.
The editor is mounted (admin-only) at GET /theme-builder/editor, whose shell
page (lib/page.js, ARCHITECTURE.md 8.2) loads a single static bundle served by
Saltcorn's built-in plugin static route at
/plugins/public/theme-builder/builderApp.js (no extra route -- plugins.js
1246-1276).
Phase 1 -- the shipping editor (public/builderApp.js)
public/builderApp.js is the buildless, dependency-free Phase-1 editor. It
is hand-written vanilla ES2020 -- no React, no Craft.js, no build step, no
imports. It IS the committed, shipped artifact: an installed plugin works
without ever running a bundler.
What it does (talking only to the REST API under window.__TB__.apiBase):
- Manager panel --
GET /api/statelists every theme withactiveandbuilt-inbadges (ARCHITECTURE.md 8.5/8.8). Per-row buttons: Activate, Edit, Duplicate, Rename, Delete, Export (plain<a download>to/api/themes/:id/export). Top toolbar: New, Import (file input -> multipartPOST /api/import). - Token panel -- color / font / spacing inputs rendered from the manifest
returned by
/api/state(manifest.tokens[<kebabKey>] = { kind, cssVar, selector, prop, derive, default }, fromlib/apiState.buildManifest). - Live preview -- a sandboxed
<iframe>(sandbox="allow-same-origin", no scripts) loads the livetheme.cssplus a representative sample page with an empty<style id="tb-overlay">. On every edit the editor rewrites that style with--bs-*custom properties (and the rule-basedselector{prop:value}tokens) -- the exact same overlay mechanism production uses on activate (lib/compile.emitOverlayCss). WYSIWYG with zero compile and zero network.
The cardinal rule: load != activate
Loading and editing a theme never touches the live site. Save (POST /api/themes/:id/save with { tokens, layoutTree, baseVersion }) and
Activate (POST /api/themes/:id/activate) are wired as separate
actions; only Activate publishes (ARCHITECTURE.md 1.4 / 8.3). Save uses
optimistic concurrency on baseVersion; a 409 version_conflict offers
reload-or-overwrite. Editing a built-in is allowed: the first Save transparently
duplicates it to an editable row, leaving the original untouched.
CSRF
The shell (lib/page.js) bootstraps
window.__TB__ = { apiBase, cssRoute, csrfToken, base, openThemeId }, reading
the token via req.csrfToken() (core's pattern, e.g. server/wrapper.js:259).
Every POST from the editor sends it back as the CSRF-Token header
(plus X-Requested-With: XMLHttpRequest and Accept: application/json so the
server treats every request -- including multipart import -- as JSON).
Phase 2 -- the upgrade path (builder/src, NOT yet implemented)
The Phase-2 editor is a React + Craft.js application whose source tree will
live under builder/src (ARCHITECTURE.md 8.3, 8.7, 10). It adds a visual
canvas panel (panel:"canvas", enabled only when caps.phase === 2) that
reuses saltcorn-builder's <Editor>/<Frame> patterns and the
craftToSaltcorn / layoutToNodes round-trip, with @craftjs/core and the
vendored saltcorn-builder element/storage files bundled directly into the same
output path (public/builderApp.js) by webpack (build:ui).
Phase 2 is purely additive and does not change the REST surface or the "load != activate" rule:
- The token panel, manager panel, and
--bs-*live-preview overlay carry over unchanged; the canvas is a third panel feeding the same singlePOST /save({ tokens, layoutTree }). - Activate is still just
POST /api/themes/:id/activate; the server-side engine swaps the overlay compiler for a full dart-sass recompile behind thephase2Enabledconfig flag (ARCHITECTURE.md 5.7 / 7.5).
Until that React tree is built, public/builderApp.js (the buildless vanilla
file) is the live editor. When Phase 2 lands, the webpack build overwrites
public/builderApp.js with the compiled React bundle (exposing the same
window.themeBuilder.mount entry point), and this builder/ directory holds its
source.