Visual Editors

A floating inline click-to-edit button and a full drag-and-drop builder canvas — both schema-driven from the same block system.

Two visual editors, both operating on the exact same Section[] data described in The Content Model. Neither is a separate content system — a schema in src/content/blocks/*.json is all either editor needs to know about a block type.

Inline click-to-edit

A floating ✏️ button appears in the bottom-right corner of any page rendering block-driven content (home, about, features, once real content is published for them). Click it to edit that page’s sections directly, with a draft/publish flow. Client logic lives in public/js/inline.js; the routing that decides which button appears where is src/lib/editbar.ts.

Rich-text fields use a proper RTE in this editor; the builder canvas edits the same richtext field as a plain textarea of raw HTML instead — both write the identical HTML string back to the same field, just with different input UI.

The builder canvas

/admin/editor/build/<page> — a full drag-and-drop canvas built on GrapesJS: reorder blocks, add/remove them, edit every field type through a generated trait (settings) panel.

This is schema-driven, not hand-registered. src/builder/componentTypes.ts fetches every block schema from /api/admin/editor/block-schemas at runtime and generates the GrapesJS block, component type, and trait panel from it automatically. Adding a new block type never requires writing a new editor.Components.addType(...) call — if you find yourself doing that, the schema-driven factory is missing something, and that’s the bug to fix, not a reason to special-case a block.

Key files:

  • src/builder/main.ts — GrapesJS init, load/save/publish wiring.
  • src/builder/componentTypes.ts — schema → block/component-type/trait generation, JSON (de)serialization.
  • src/builder/traits.ts — the custom trait types (gv-textarea, gv-image, gv-list) that back the non-trivial field types. Each manages its own DOM instead of relying on GrapesJS’s default input-value wiring — read the file’s own docstring for why, before adding a seventh field type.
  • src/builder/render.ts — the canvas’s live-preview HTML renderer. This is a deliberate small duplication of each block’s real Astro component, not a shared abstraction — GrapesJS renders HTML strings, not Astro components, so there’s no way around having both. Keeping them in sync is exactly what Modifying an existing block type describes.

Deliberately out of scope

The builder canvas has no style manager — block markup is the only thing that determines how a block looks, precisely so a site owner can never drag a block off-theme by fiddling with inline styles. A “Theme Tweaks” UI (the Python original’s in-browser CSS-token override panel) isn’t built in this port either — see FAQ & Roadmap.