What you get
The property panel is built from the block manifest and never looks at the rendered page. The overlay does the opposite: it finds every job it has by walking[data-editable-target] in the DOM. So these four are the ones that
turn on here, and only here:
Everything else works either way. That is why this is a second step rather than
a requirement — and why nothing warns you about skipping it.
Mark the element that draws each prop
features[0].title) is described under
How a block is rendered.
Every attribute is inert when the editor is absent. editableProps takes no
editorMode argument for that reason: most sites render blocks through
components shared with their public pages, and threading a flag down to each
field is the step that does not get done.
Pass kind for any image field. Without it the overlay infers an image from
the prop’s name, against the convention Avocado’s own blocks use (imageUrl,
*.src) — so a field named photoUrl gets an image picker in the property
panel and no button in the preview, and nothing reports the disagreement. kind
takes the same word your block manifest uses.
For an image the attribute goes on the wrapper around the <img>, never on
the image. The overlay appends its Change button into the marked element, and
nothing can be appended into a void element. Marking the image itself gives you
a field that is listed in the property panel, highlights on hover, and has no
button — with no error on either side.
When a list row is its own component
The path is scoped from the block down —items[3].question — which a
component can only write if it knows where it sits. That holds while one
component draws the whole block, and stops holding the moment a list row is
drawn by a component of its own: it knows it has a question and cannot know
it is items[3].
Mark the wrapper with the scope, and the children stay ignorant:
left[1] scope inside a
sections[0] scope makes a child’s text into sections[0].left[1].text. A
block boundary ends the composition — a scope outside a block never reaches
into it — and an element’s own editableProps is not inside the scope it sets
for its children.
When the row has no wrapper to mark
A scope is an attribute, so it needs an element — and a list whose rows map straight into a flex or grid container has none to give it. The wrapper you add to hold the scope becomes the flex item, and the layout the rows had is now the layout of a column of wrappers: gaps land in the wrong places,align-items
applies to the wrong box, and a grid’s rows stop being the grid’s children.
display: contents is the whole answer — the element stays in the tree the
overlay walks and lays out as if it were not there, so the rows go on being
their parent’s children. Pass { display: "contents" } and the helper writes
it:
<li> or a card <div> around it should carry the scope on
that element rather than gain a second one to hold it.
Do it in one pass, and keep it
This is per-component work spread over as many files as you have renderers, and it is the step that gets done on a branch, not merged, and re-lost on the next one. The symptom of a half-done pass is always the same: one missing button, reported as a bug in the button.site-sdk/coverage answers exactly which manifest
fields your rendered page offers, so a branch that drops the markers goes red
in your own test suite instead of going quiet. Run it once when you finish this
pass, and assert on it after that.