Observation-class finding, measured on f5d2acc35 while repairing objectui#7898's nine button-key sites in content/docs/guide/. Filed unassigned — recording, not claiming; grading and domain:* are the triage seat's.
What was measured
PageNodeSchema (packages/types/src/zod/layout.zod.ts) does not declare actions — it is absent from the shape, and an authored value survives a parse only because BaseSchema is .passthrough():
PageNodeSchema.shape has `actions`: false
PageNodeSchema.safeParse({ type: 'page', title: 'Products',
actions: [{ type: 'button', label: 'Add Product' }] }).success === true
PageRenderer (packages/components/src/renderers/layout/page.tsx, 708 lines) has zero read points for it — grep -ni action page.tsx returns only the PageVariableActionBridge import and its render. Repo-wide, every .actions reader is a different declaration: objectDef.actions (server-declared object actions — DeclaredActionsBar, ObjectView, InterfaceListPage), page:header.actions (containers.tsx, and those are ACTION IDS, not nodes — objectui#7182), notification.actions, HeaderBarSchema.actions.
Rendered, through the real SchemaRenderer and the real renderers:
page node with actions: [ {type:'button',label:'Add Product'}, {type:'button',label:'Export'} ]
-> buttons found in the DOM: 0
-> "Add Product" appears anywhere in the DOM: false
the SAME two buttons moved into page.body
-> buttons found in the DOM: 2 texts: ["Add Product","Export"]
The second half — it does not just vanish, it lands on the DOM
page.tsx does not use pickDomProps / toFormControlDomProps; it destructures a hand-maintained list of PageSchema descriptors out of props and spreads the remainder onto the wrapper element. actions is not on that list, so the array is stringified into an invalid HTML attribute. Measured attributes on the wrapper:
class="min-h-full w-full bg-background p-3 md:p-4 lg:p-6"
data-page-type="record"
data-obj-type="page"
actions="[object Object],[object Object]"
That is the exact class objectui#4425 closed for SDUI widgets by promoting the whitelist out of packages/fields — page.tsx is one of the surfaces still closing its DOM leak by hand, and the enumeration has fallen behind the schema.
Where it is taught
content/docs/guide/layout.md teaches page.actions in three passages — "With Action Buttons", "Detail Page with Actions", and best-practice 3 "Action Buttons in Headers". objectui#7898's PR repairs the button keys inside those fences (text is not a ButtonSchema key; a top-level onClick is refused by name) and deliberately stops there: whether the page node should GROW an actions reader, or the pages should be rewritten onto body / regions + page:header, is a capability decision, not a documentation fix. That card's dispatch is explicit — a site teaching a capability that does not exist at all is reported, never routed around in prose.
Related, not duplicates
- objectui#7469 —
AppComponentSchema.actions[] (AppAction) has no reader. Same class, different declaration: that is the app node, this is the SDUI page node.
- objectui#7693 — the alert-dialog fixtures author an
actions array no surface carries. Same class, third surface.
- objectui#4425 — the DOM pass-through whitelist;
page.tsx never adopted it, which is why this key leaks rather than being dropped.
- objectui#5250 — the instrument-level card: JSON doc snippets are judged by nothing, and
.passthrough() is why a naive gate would be vacuous.
How to re-measure
git grep -n "actions" packages/components/src/renderers/layout/page.tsx
node -e "import('./packages/types/dist/zod/layout.zod.js').then(m=>console.log(Object.keys(m.PageNodeSchema.shape).includes('actions')))"
Filed by the os-dev seat working objectui#7898; session https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Observation-class finding, measured on
f5d2acc35while repairing objectui#7898's ninebutton-key sites incontent/docs/guide/. Filed unassigned — recording, not claiming; grading anddomain:*are the triage seat's.What was measured
PageNodeSchema(packages/types/src/zod/layout.zod.ts) does not declareactions— it is absent from the shape, and an authored value survives a parse only becauseBaseSchemais.passthrough():PageRenderer(packages/components/src/renderers/layout/page.tsx, 708 lines) has zero read points for it —grep -ni action page.tsxreturns only thePageVariableActionBridgeimport and its render. Repo-wide, every.actionsreader is a different declaration:objectDef.actions(server-declared object actions —DeclaredActionsBar,ObjectView,InterfaceListPage),page:header.actions(containers.tsx, and those are ACTION IDS, not nodes — objectui#7182),notification.actions,HeaderBarSchema.actions.Rendered, through the real
SchemaRendererand the real renderers:The second half — it does not just vanish, it lands on the DOM
page.tsxdoes not usepickDomProps/toFormControlDomProps; it destructures a hand-maintained list of PageSchema descriptors out ofpropsand spreads the remainder onto the wrapper element.actionsis not on that list, so the array is stringified into an invalid HTML attribute. Measured attributes on the wrapper:That is the exact class objectui#4425 closed for SDUI widgets by promoting the whitelist out of
packages/fields—page.tsxis one of the surfaces still closing its DOM leak by hand, and the enumeration has fallen behind the schema.Where it is taught
content/docs/guide/layout.mdteachespage.actionsin three passages — "With Action Buttons", "Detail Page with Actions", and best-practice 3 "Action Buttons in Headers". objectui#7898's PR repairs the button keys inside those fences (textis not aButtonSchemakey; a top-levelonClickis refused by name) and deliberately stops there: whether thepagenode should GROW anactionsreader, or the pages should be rewritten ontobody/regions+page:header, is a capability decision, not a documentation fix. That card's dispatch is explicit — a site teaching a capability that does not exist at all is reported, never routed around in prose.Related, not duplicates
AppComponentSchema.actions[](AppAction) has no reader. Same class, different declaration: that is the app node, this is the SDUIpagenode.actionsarray no surface carries. Same class, third surface.page.tsxnever adopted it, which is why this key leaks rather than being dropped..passthrough()is why a naive gate would be vacuous.How to re-measure
Filed by the
os-devseat working objectui#7898; session https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3