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.