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

77 lines
4.1 KiB
Markdown

# 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.