sc-theme-builder/builder/README.md
2026-07-01 20:07:28 -05:00

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/state lists every theme with active and built-in badges (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 -> multipart POST /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 }, from lib/apiState.buildManifest).
  • Live preview -- a sandboxed <iframe> (sandbox="allow-same-origin", no scripts) loads the live theme.css plus 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-based selector{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 single POST /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 the phase2Enabled config 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.