.astro or JSX templates — most starter kits — there is nothing
for it to edit until that copy lives somewhere a publish can write back to. This
page is the recipe for moving it there without touching the markup or the
styles.
It was worked out on the CodeStitch Beginner Astro Starter Kit: five pages of
inline copy, LESS, and two data files the template already reads. Afterwards
every <style> block was byte-identical, the visible text of every built page
matched the original build, and publishing two edits changed exactly two lines.
The examples are Astro; on Next the steps are the same, with JSX,
@avocadostudio-ai/site-sdk/markers and next/image.
The files are the published content. getPages and onPublish in your
content module
translate between them and PageDocs; the templates read the same files, or the
draft on an editor render.
1. One block per rendered section, prefixed
Walk each page top to bottom and make every section it renders one block type.npx -p @avocadostudio-ai/migration-sdk avocado-scope <url> --allow-localhost
against the dev server gives a second opinion on where the sections are.
Prefix every type with the site’s own short name — cs_hero, cs_cta,
cs_footer. Avocado ships Hero, CTA, Banner, Footer, Gallery and
fifteen more, and a site type with one of those names replaces the built-in’s
definition rather than erroring.
Declare the table with registerFieldTable, passing the image naming — there is
no CMS pack here to supply it:
src/avocado/content.ts
fixed: true so the editor does not offer one —
see the field table.
2. Move the copy into one JSON file per page
src/data/pages/<page>.json— onePageDocper page:id,slug,titleand the page’s own blocks, in order, each with its literals asprops.src/data/global.json— the blocks every page renders: header, footer, a shared call to action. Give them fixed ids (global-footer) and havegetPagesadd them to everyPageDoc, so the editor can select them on any page. On publish, write them back toglobal.jsononce; if two pages changed the same one differently, return{ ok: false, error }and write nothing. If the version you installed has its own model for site-wide blocks, the changelog says so — prefer it to this.
client.json and navData.json, map them to blocks in getPages and back
in onPublish rather than copying them into global.json:
- The field table has no object kind, so flatten nested objects —
address.citybecomes the propaddress_city— and nest them again on write. - Rename to the field’s meaning where the file’s key is opaque, both ways:
a nav row’s
keyis itslabel. - Write back into the original object,
{ ...client, phoneFormatted: props.phoneFormatted }, so the file keeps its key order and the diff stays one line.
3. Keep the markup; read props instead of literals
Each template keeps its elements, classes and<style> block. Only the literals
change, to reads from the page’s blocks, with the markers from
editorMarkers(Astro) beside them:
src/pages/index.astro
.map() over a list, marked with full paths
(items[2].title) or with a scope on the row element the template already
has — no extra wrappers.
Mark each block on the element that encloses its section, and put
data-avocado-root on the element that encloses every block — see preview
refresh.
Some things have no string form and stay out of the table: an SVG imported
as a component (import Logo from '../assets/logo.svg', rendered <Logo />)
and decorative icons. Making one editable means changing it to an <img>, which
is a markup and CSS change — ask first.
4. Declare what renders nowhere as panelOnly
A value used only in an attribute has no element to mark. Declare it
panelOnly: true so it is editable in the panel and
coverage does not expect a marker for it:
kind: 'link' — coverage never
expects a marker for one — and give the anchor’s label its own text field,
which carries the marker.
5. Images: project paths through astro:assets
An image field is a string. Store a file under src/assets/ as its project
path — /src/assets/images/landing.jpg — and resolve it at render time with
import.meta.glob, whose keys are exactly those paths. <Picture> and
<Image> keep their optimisation, srcset and AVIF/WebP output; any other URL
falls back to a plain <img>:
src/avocado/Img.astro
<picture> element — through pictureAttributes on
<Picture> — because the overlay appends its Change button into the marked
element, and nothing can be appended into an <img>.
Localise uploaded images on publish. An image chosen in the Studio arrives
as an http(s) URL, which a static host cannot rely on. In onPublish, download
any such value into src/assets/images/avocado/<sha1>.<ext> and store that
project path instead, so it goes through astro:assets and lands in the diff.
A gallery whose rows are only an image and its alt text can be a list of
{ image, image_alt } rows or an imageList.
Use the list when the rows need a caption or a link, or should share the
table’s image naming.
6. Write each file only if it changed
onPublish receives every page, edited or not. Write a file only when its
parsed content differs, and in the indentation it already has — client.json
in the starter kit is indented with tabs:
src/avocado/store.ts
updatedAt is not content. The orchestrator needs one on every PageDoc,
and a file-backed store has no use for it: derive it from the file’s mtime in
getPages ((await stat(file)).mtime.toISOString()) and strip it before comparing and
writing. A stored timestamp is a line that changes on every publish.
7. Verify: the diff is the test
Report each of these as a number or a diff:- Round trip. Call
getPages()and pass the result straight toonPublish. It must write nothing:git status --porcelainis empty. - One synthetic edit per field kind — a text field, an
htmlfield, an image, a list row removed, a string-list item added, a nav label, a field in each data file. Each lands in exactly one file and one field, andgit diffshows only those lines, indentation intact. - Through the orchestrator. Make two edits in the editor, one on a page
block and one on a site-wide block, publish, and read
git diff: two changed lines. Revert. - The public site did not move. The visible text of every built page
matches the pre-integration build, and
git diffon the templates shows no change inside a<style>block. - The editing surface.
editableCoverageat 100% on every page, measured againstastro devwith each block’spropspassed in, and thennpx avocado qa.
Related
Astro integration
The content module,
getDraftPage(), the markers, and publishing on a static template.The field table
Field kinds,
fixed, readOnly and panelOnly.Coverage checks
Whether every field that renders carries a marker, and whether the panel is usable.
QA gate
The last step: the integration checked in a browser, inside the editor frame.