tree payload: server-owned child role, slot wrapper and summary verdicts (drift audit 1, 2, 5) #243
Labels
No labels
bug
enhancement
needs-triage
ready-for-agent
ready-for-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/deckle#243
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent
#234 (drift audit), items 1, 2 and 5.
Summary
The server decides, once per node, what role a child plays in the element tree (item row, slot wrapper, nested element, body text) and what its summary line is, and sends those verdicts in the tree payload. Every server path and the whole web client read the verdict instead of re-deriving it from the child's own schema flags. Today the three rules exist in nine places that already disagree.
Current behavior
Confirmed at
dcc1067.Child role.
elemops.IsItem(ownerSchema, child)(introduced by #239) decides "is this child an item of its parent" from the parent'sitems.element. Every other place decides from the child's schema,hidden || group == inline:server/pending.goisFoldedElement,server/linktargets.go(row listing for the link picker);api/content.tsisInlineGroupNode,isHiddenNode,flattenChildren;elementcard.tsisCardNode,hasSubList; and the move-sibling visibility check inelementcrud.ts, which does not exclude hidden nodes and so disagrees even withisCardNode.The loader (
element/load.govalidateItems) only requires an item element to exist and not be inline. It does not requirehidden: true, and nothing requires a hidden element to be some owner's item. The two rules coincide only because every shipped item element (img,card) happens to sethidden: true. A non-hidden item element is an item to the server but a card, link target and pending element to the client. A hidden element placed outside its owner is invisible to the client but is not an item toIsItem.Slot wrapper. Four behaviours:
elemops.SlotWrapperName(all of l10n)slot, non-emptynameparamserver/linktargets.goslot, nonamecheckserver/pending.goslot, any depth, folded into the parent's fingerprintelementcrud.tsisSlotWrapper,api/content.tsflattenChildrenslot, nonamecheck, depth 1A nameless top-level
{{< slot >}}is transparent to the client and to pending/linktargets but an ordinary element to l10n, which will give it a uid. A self-closing{{< slot />}}is dropped by linktargets and pending and is a normal node everywhere else.Summary.
elemops.ElementSummary(schemasummarykey, then the firstSummaryTextparam, then the basename of the firstSummaryBasenameparam) is what the link picker and link-resolve label show.ElementForm.tsxrowSummaryfollows the same order from the form's summary roles.elementcard.tselementSummaryis different: body text first, then everytext/textareaparam joined with·, never thesummarykey, never a basename. A card and the link picker describe the same element differently.server/linktargets_test.gosays it "mirrorsrowSummary" in a comment; nothing runs the two against each other.Payload.
GET /api/pagereturnscontent.Document;Shortcode.MarshalJSON(internal/content/json.go) emits{kind, name, markup, params, children}and nothing about role or summary. The client derives everything from/api/elements.Desired behavior
elemopsowns the child-role rule and returns a verdict for a (parent schema, child) pair:item,slot,elementortext.IsItemandSlotWrapperNameare its building blocks or are folded into it.holdsElementin l10n,isFoldedElement, the link-picker row listing and the translationitemAtall call it; no server path readshiddenorgroup == inlineto decide a role.slot; what a nameless one or a nested one is must be decided once and documented). Pending, linktargets, the client and l10n give the same answer for the nameless, nested and self-closing cases.ElementSummary. The client reads them.isInlineGroupNode,isHiddenNode,isCardNode,hasSubList,isSlotWrapper,SLOT_WRAPPER_NAME, theelementcrud.tsvisibility check andelementcard.tselementSummaryeither disappear or become readers of the payload field. The client no longer needs a schema lookup to render the tree, the cards, the translation grid, the link picker or the pending count.items.elementmust declarehidden: true, and an element declaringhidden: truemust be named by some owner'sitems.element. Either violation is a schema error reported the way other sidecar errors are (harness checkand the editor's schema-error list).scaffold/site/AGENTS.md's "Insertability" paragraph (thehidden: truesentence) and "Item collections" state the enforced rule, and the sidecar schema section documentshiddenas "only for items children, enforced". Theadd-elementskill's items/hidden guidance points at that section.Decisions
items.elementis the only thing that makes a child an item; the child's flags are presentation. The server already ownsIsItemsince #239, and the CLAUDE.md one-owner rule says the client does not re-derive: a verdict in the payload is the chosen form, as the form's summary roles already are.hidden⇔ item in the loader rather than let the two drift: the maintainer's call. The alternative, letting the client render non-hidden items as cards, would make the editor show a row the form also edits inline.rowSummarydoes with the item label today.slot. Thenameparam is required by the sidecar, so a nameless one is a schema violation the loader orharness checkreports, not a case each reader resolves differently. A nestedslotis an ordinary element (what the client does today) and l10n keeps treating a below-top-level wrapper as opaque body only if that is what this single rule says; the implementer picks one answer and the tests pin it for every reader.Key interfaces
elemops.IsItem,elemops.SlotWrapperName,elemops.ElementSummary(internal/elemops/items.go,slots.go,summary.go) — existing owners to build on.content.ShortcodeJSON shape (internal/content/json.go) and the clientBlockNode/InlineNodetypes (web/src/api/content.ts) — gain the role and summary fields. Ifcontentmust stay free of schema knowledge, the server attaches the verdicts in its page answer (server/translation.gopageAnswer) rather than incontent.element.Schema.Hidden,validateItems(internal/element/load.go) — the enforcement.web/src/api/content.ts,elementcard.ts,elementcrud.ts,translationgrid.ts,components/ElementCard.tsx,LanguagesView.tsx,TranslationPlaceholder.tsx,ElementForm.tsx.server/pending.go,server/linktargets.go,server/translation.go,l10n/l10n.goholdsElement.Rules touched and their owners (CLAUDE.md)
elemops(new or extendedIsItem); served to the client.elemops.SlotWrapperName; served to the client.elemops.ElementSummary; served to the client.elementloader.Affected docs and skills
scaffold/site/AGENTS.md: "Insertability", "Item collections", the sidecar schema'shiddenline, and "Slots" if the nameless/nested answer changes documented behaviour.scaffold/site/.claude/skills/add-element/SKILL.md: items/hidden guidance points at the section; no restated rule.internal/element/agentsdoc_test.goandscaffold/skills_test.gostay green.Acceptance criteria
elemopsevaluates "hidden or inline" to decide whether a node is an item, and no server package other thanelemopsmatches the nameslot.GET /api/pagecarries a role verdict and a summary string per shortcode node, documented in the payload's Go type and TypeScript type.web/srccontains no function that derives item, card, slot-wrapper or summary from a node's schema;grepforisHiddenNode,isInlineGroupNode,isCardNode,isSlotWrapper,SLOT_WRAPPER_NAMEandelementSummaryfinds only payload readers or nothing.hidden: truefor an element no owner names as its item, or naming an item element that is not hidden, fails to load with a reported schema error;harness checkon the scaffold site still passes.sloteach get one documented treatment, asserted by a test on pending, on the link picker, on l10n and on the client.elementcrud.tsdiscrepancy).add-elementskill updated as above; drift tests green.How to verify
go vet ./... && go test ./...;cd web && npm test.harness check --site scaffold/sitepasses; a temporary sidecar withhidden: trueand no owner makes it fail.harness serveon the scaffold site: open the showcase page, the text-media and figures cards show the picker's summary; the link picker's element list and the cards list the same rows.Assumptions to check
content.Shortcodecan grow JSON-only fields without breaking the parse/serialize round trip and the fuzz corpus (internal/content/testdata); otherwise attach the verdicts in the server's page answer.hidden: true(checked forimgandcard; check every bundle underscaffold/anddeckle-hosted's site templates, which this repo imports read-only).l10n/uids.go) is compatible with whichever nested-slot answer is chosen.Out of scope
Blocked by
None - can start immediately.