Filed unassigned by the domain:ui dev seat (session session_013hfmP9hoMd3dJwTh85J4yB) while implementing #5905. Measured, not fixed — out of that card's scope, which is about the keys ComponentInput declares and nothing reads. This is the mirror image: a key that is read and published and that ComponentInput does not declare.
Measured on origin/main (merge-base b03ba3ad5)
ComponentInput (packages/types/src/base.ts) declares thirteen keys and binding is not one of them. The list is name, type, label, defaultValue, required, enum, description, advanced, inputType, min, max, step, placeholder. Its Zod mirror (ComponentInputSchema, packages/types/src/zod/base.zod.ts) has the same thirteen.
The manifest serializer publishes binding.manifestFromConfigs (packages/sdui-parser/src/index.ts:153-160) forwards exactly six keys per input, and binding is one of them. It types its input through its own structural boundary interface RegistryConfigLike, which does declare binding?: 'object' | 'field' (same file, around line 152).
Something reads it. Per the serializer's own doc block, validateTree records a binding input as a binding site, and the designer offers an object picker rather than a free-text blob for it.
The authoring type is bypassed with a cast where it is actually written.packages/core/src/data-scope/element-data-source.ts:327 declares ELEMENT_DATA_SOURCE_INPUT with binding: 'object' under a hand-written inline type literal, not ComponentInput. packages/core/src/registry/Registry.ts:313 then splices it into a registration's inputs as:
return{ ...(meta??{}),inputs: [...inputs,{ ...ELEMENT_DATA_SOURCE_INPUT}]}asComponentMeta;The trailing as ComponentMeta is what makes it compile.
Why this is worth a card
binding is a live, published, consumed input key that the published authoring type refuses. A registration that writes it directly in its inputs: array — the ordinary way every other input key is written — does not type-check against ComponentInput, so the only in-repo writer reaches it through a cast at one privileged injection point. Declared is narrower than enforced, on a published type, in the direction that makes a real capability unreachable to ordinary authors.
That is precisely the shape ADR-0049 and the repo's declared = enforced posture exist to triage, and it is the shape that bites AI-written metadata hardest: the manifest an agent reads back advertises binding, while the type it writes against says the key does not exist.
Not pre-judged
At least three dispositions look plausible and this card picks none:
- Declare it — add
binding?: 'object' | 'field' to ComponentInput and its Zod mirror, so declared matches published and the cast in Registry.ts can go. Needs a check that the vocabulary really is those two values and nothing wider. - Keep it internal — rule that
binding is a serializer-injected key that only the framework may set, and make that explicit (a separate injected-input type) rather than leaving it as a cast. - Something narrower — e.g. declare it but mark it framework-set, if the designer treats an author-written
binding differently from an injected one.
The choice turns on whether an ordinary registration is supposed to be able to declare a binding input, which is a product question rather than a mechanical one.
Scope note
Not touched by PR #6949 (the #5905 retirement), which changes only min / max / step / placeholder and deliberately leaves the rest of ComponentInput's surface alone.
Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
Filed unassigned by the
domain:uidev seat (sessionsession_013hfmP9hoMd3dJwTh85J4yB) while implementing #5905. Measured, not fixed — out of that card's scope, which is about the keysComponentInputdeclares and nothing reads. This is the mirror image: a key that is read and published and thatComponentInputdoes not declare.Measured on
origin/main(merge-baseb03ba3ad5)ComponentInput(packages/types/src/base.ts) declares thirteen keys andbindingis not one of them. The list isname,type,label,defaultValue,required,enum,description,advanced,inputType,min,max,step,placeholder. Its Zod mirror (ComponentInputSchema,packages/types/src/zod/base.zod.ts) has the same thirteen.The manifest serializer publishes
binding.manifestFromConfigs(packages/sdui-parser/src/index.ts:153-160) forwards exactly six keys per input, andbindingis one of them. It types its input through its own structural boundary interfaceRegistryConfigLike, which does declarebinding?: 'object' | 'field'(same file, around line 152).Something reads it. Per the serializer's own doc block,
validateTreerecords abindinginput as a binding site, and the designer offers an object picker rather than a free-text blob for it.The authoring type is bypassed with a cast where it is actually written.
packages/core/src/data-scope/element-data-source.ts:327declaresELEMENT_DATA_SOURCE_INPUTwithbinding: 'object'under a hand-written inline type literal, notComponentInput.packages/core/src/registry/Registry.ts:313then splices it into a registration's inputs as:The trailing
as ComponentMetais what makes it compile.Why this is worth a card
bindingis a live, published, consumed input key that the published authoring type refuses. A registration that writes it directly in itsinputs:array — the ordinary way every other input key is written — does not type-check againstComponentInput, so the only in-repo writer reaches it through a cast at one privileged injection point. Declared is narrower than enforced, on a published type, in the direction that makes a real capability unreachable to ordinary authors.That is precisely the shape ADR-0049 and the repo's
declared = enforcedposture exist to triage, and it is the shape that bites AI-written metadata hardest: the manifest an agent reads back advertisesbinding, while the type it writes against says the key does not exist.Not pre-judged
At least three dispositions look plausible and this card picks none:
binding?: 'object' | 'field'toComponentInputand its Zod mirror, so declared matches published and the cast inRegistry.tscan go. Needs a check that the vocabulary really is those two values and nothing wider.bindingis a serializer-injected key that only the framework may set, and make that explicit (a separate injected-input type) rather than leaving it as a cast.bindingdifferently from an injected one.The choice turns on whether an ordinary registration is supposed to be able to declare a binding input, which is a product question rather than a mechanical one.
Scope note
Not touched by PR #6949 (the #5905 retirement), which changes only
min/max/step/placeholderand deliberately leaves the rest ofComponentInput's surface alone.Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB