Found while implementing #7082. Filed rather than folded in: #7082 is fenced to docs rows, and this key cannot be corrected on the page because there is no declaration for the page to follow.
Measured on origin/main2c3cd1b7572a8aca2958da3551c990164186c9c1.
The key
packages/components/src/renderers/feedback/empty.tsx reads it, and says so in a comment:
constactionSchema=(schemaasany).actionasComponentSchema|undefined;constactionNode=actionSchema&&typeofactionSchema==='object'
? <SchemaRendererschema={actionSchemaas any}/>
: undefined;The same file strips action out of the spread props with its own note that it is "a child schema, not a DOM attribute or React node". It is a real, working capability with a shipped demo — content/docs/components/feedback/empty.mdx:18 renders components-feedback-empty/with-action-button.
EmptySchema declares it nowhere:
| surface | says |
|---|
packages/types/src/feedback.ts:210-224 | type, title, description, icon — no action |
packages/types/src/zod/feedback.zod.ts:120-125 | the same four — no action |
content/docs/components/feedback/empty.mdx:28 | action?: ComponentSchema — documented |
The action?: { label, onClick } at feedback.ts:143 is ToastSchema's, a different interface, and it is a different shape.
The cast is the tell: (schema as any).action is what a renderer writes when the key is not on the type it was handed.
Why #6150 did not catch it
#6150 measured renderer-read-but-undeclared keys "across all 76 content/docs/components pages" and declared 13 of them; its ledger is packages/types/src/__tests__/undeclared-but-consumed-keys-6150.test.ts, which pins text.content, carousel.opts, hover-card.align, context-menu.trigger and the rest. empty.action is not in it. The sweep's own blind spot is plausibly the cast: a scan looking for schema.KEY reads finds schema.action nowhere, because this renderer spells it (schema as any).action.
⚠️ That makes the cast worth a second look repo-wide, not just here — the census #6150 declared complete is complete only for the un-cast spelling.
Why it matters
Options
- (a) Declare
action?: SchemaNode on EmptySchema and mirror it, then narrow the renderer's object-only guard or widen the renderer to render primitives too — one of the two, so declared equals enforced. - (b) Declare it at the object-only type the renderer actually requires (
BaseSchema, not SchemaNode), which is the honest shape but a spelling no sibling slot uses. - (c) Retire the capability under ADR-0049 enforce-or-remove, and delete the row and the
with-action-button demo.
(a) or (b) look right — this is a working, demoed capability, so (c) would be a real removal. The choice between them is exactly "should a node slot admit primitives", which the sibling overlay slots answer with SchemaNode; matching them would mean relaxing the renderer's guard rather than narrowing the declaration. Either way the docs row and the designer inputs list are owed an update, and the as any cast should go with it.
Refs: #7082 · #6150 · #7104.
Found while implementing #7082. Filed rather than folded in: #7082 is fenced to docs rows, and this key cannot be corrected on the page because there is no declaration for the page to follow.
Measured on
origin/main2c3cd1b7572a8aca2958da3551c990164186c9c1.The key
packages/components/src/renderers/feedback/empty.tsxreads it, and says so in a comment:The same file strips
actionout of the spread props with its own note that it is "a child schema, not a DOM attribute or React node". It is a real, working capability with a shipped demo —content/docs/components/feedback/empty.mdx:18renderscomponents-feedback-empty/with-action-button.EmptySchemadeclares it nowhere:packages/types/src/feedback.ts:210-224type,title,description,icon— noactionpackages/types/src/zod/feedback.zod.ts:120-125actioncontent/docs/components/feedback/empty.mdx:28action?: ComponentSchema— documentedThe
action?: { label, onClick }atfeedback.ts:143isToastSchema's, a different interface, and it is a different shape.The cast is the tell:
(schema as any).actionis what a renderer writes when the key is not on the type it was handed.Why #6150 did not catch it
#6150 measured renderer-read-but-undeclared keys "across all 76
content/docs/componentspages" and declared 13 of them; its ledger ispackages/types/src/__tests__/undeclared-but-consumed-keys-6150.test.ts, which pinstext.content,carousel.opts,hover-card.align,context-menu.triggerand the rest.empty.actionis not in it. The sweep's own blind spot is plausibly the cast: a scan looking forschema.KEYreads findsschema.actionnowhere, because this renderer spells it(schema as any).action.Why it matters
actionis accepted only vacuously, throughBaseSchema's[key: string]: any, and no editor completes it.typeof actionSchema === 'object', so the slot is narrower thanSchemaNode— a bare string node is silently dropped rather than rendered. Whatever is declared here should say so; this is the reason finding(docs): six component pages publishtrigger/content/actionasComponentSchema, a different shipped type than theSchemaNodethose keys actually declare #7082 left the docs row alone rather than renaming it toSchemaNode, which would have swapped one false claim for another.registrationMeta.inputson the same renderer lists onlytitle,description,className— so the designer surface cannot offer it either.Options
action?: SchemaNodeonEmptySchemaand mirror it, then narrow the renderer's object-only guard or widen the renderer to render primitives too — one of the two, so declared equals enforced.BaseSchema, notSchemaNode), which is the honest shape but a spelling no sibling slot uses.with-action-buttondemo.(a) or (b) look right — this is a working, demoed capability, so (c) would be a real removal. The choice between them is exactly "should a node slot admit primitives", which the sibling overlay slots answer with
SchemaNode; matching them would mean relaxing the renderer's guard rather than narrowing the declaration. Either way the docs row and the designerinputslist are owed an update, and theas anycast should go with it.Refs: #7082 · #6150 · #7104.