Visual Editor
The visual editor activates on the public site for logged-in admins via a floating "Edit page" button. Click any block to open a resizable drawer with full field editing, live preview, drag-reorder, insert, delete, and duplicate — without leaving the front end.
Overview
The visual editor is a Sanity-style overlay that activates on the public site for logged-in admins. While anyone else sees the rendered page exactly as it ships, an authenticated admin gets a floating Edit page button in the corner; clicking it switches the page into edit mode. Each rendered block gains a hover outline and an edit handle, and clicking any block opens a resizable side drawer with full field forms for that block's schema.
Edits are previewed live — typing into a field re-renders just that block via POST /admin/api/block-types/preview and swaps its DOM range in place — and committed with a single Save button that fires PATCH /admin/api/nodes/{id} with the full updated blocks_data. No new write endpoints were added for the editor. It speaks the same admin API as the regular node editor; the value is in the UX, not the contract.
Capabilities
- Block outlines — hover borders positioned with
requestAnimationFrame; zero scroll lag. - Drawer panel — resizable from the left edge; the chosen width is persisted in
localStorage. - Field support — all 21 canonical kernel field types:
string,textarea,richtext(Tiptap),number,range,email,url,date,color,toggle,select,radio,checkbox,image,gallery,file,link,reference,term,object(collapsible), andarray(drag-reorderable rows). Extension-contributed field types are also rendered — the same registry the regular admin uses. - Media picker — image, gallery, and file fields open the full media library inline; uploads, search, and crop selection all work without leaving the page.
- Live preview — typing into any field re-renders the block server-side and swaps the rendered HTML into the page DOM. No reload, no flicker.
- Insert blocks — an + Add block button above the outline plus inline gap buttons between blocks. New blocks render immediately on the page; the overlay assigns synthetic marker indices >= 100,000 to placeholders so they cannot collide with real kernel block indices until the save round-trip renumbers them.
- Delete & duplicate — actions materialise on the live page; no save needed to see the result.
- Drag-reorder — powered by dnd-kit; reordering blocks updates the rendered layout in real time.
- Save — a single
PATCHcommits all pending changes at once, atomically. Cancelling closes the drawer and resets the page to the last saved state.
How it works
The extension ships a compiled Go plugin (bin/visual-editor) plus a plain JS bundle (editor-ui/dist/editor.js). It declares two responsibilities in its manifest:
- Capabilities:
events:subscribe,events:emit,log:write— minimal. The editor writes nothing to the database; saves go through the existing admin node API and inherit its capability checks. - Subscription: the plugin subscribes to the kernel's
render.body_endevent. When a public page renders for a logged-in admin, the kernel fires that event and the plugin returns a<script>tag pointing ateditor.js. For anonymous visitors, the kernel skips the event payload — anonymous traffic sees no editor code at all.
On the client, editor.js:
- Reads the HTML-comment markers the kernel wraps each rendered block in. The exact format (in
internal/cms/public_handler_editor_markers.go) is<!--squilla:block:start:<index>:<type-slug>-->...<!--squilla:block:end:<index>-->. Those markers map DOM ranges back toblocks_dataindices so the overlay knows which block lives where. - Overlays the edit UI on top of the live page using
position: fixedelements positioned withrequestAnimationFrame. The overlay does not touch the rendered markup; it sits above it. - Talks to the plugin's HTTP handler at
/admin/api/ext/visual-editor/*for block-preview rendering and field-type metadata, and to the kernel's/admin/api/nodes/{id}for saves.
render.body_end only delivers the editor's response when the session check passes. There is no "hidden if not editing" client-side branch to bypass.Enabling the visual editor
Activate the extension from Admin → Extensions or via MCP. auto_activate is true in the manifest, so a fresh install activates it on first boot.
core.extension.activate({ slug: "visual-editor" })
Theme requirements
None. The kernel wraps block output in HTML-comment markers automatically when the visual-editor extension is active, regardless of which theme is loaded — themes do not need to opt in or expose a hook. Deactivating the extension removes both the markers and the injected script in the same render pass.
Limits and caveats
- The editor is for blocks, not chrome. Layouts, partials, and the page header/footer are not editable inline — use the regular admin Layout Editor for those.
- Drafts vs published. Saves write to the same
blocks_dataas regular admin edits; if the node is published, your save is live immediately. Switch the node to draft first if you want to iterate without exposing intermediate states. - Concurrency. The kernel does not lock during edits. Two admins editing the same node simultaneously will overwrite each other's last-write-wins. Coordinate out of band, or use draft mode.