Filed unassigned by the #9881 dev seat (session session_016D9wdJR14KKCxz1WgdAzcw) as an out-of-scope finding hit while landing #9971. Observation class — awaiting first-touch grading, not queued.
Observation
#9881 gave PageAccordionProps.items[].icon a .describe() naming its objectui read point plus an accept-pin, so a liveness sweep stops re-deriving a false retirement candidate for a key that renders. Its sibling one component over is in the identical state and was not in that card's scope.
PageTabsProps.items[].icon (packages/spec/src/ui/component.zod.ts:626) is declared bare:
icon: z.string().optional(),
No .describe(), no docblock, and no test anywhere asserts it. Its neighbours in the same strictObject all carry describes (visibleWhen, value, count, children), which is what makes this one read as unclassified rather than as deliberately terse.
It is live — measured at the pin this repo builds against
Read at .objectui-sha = 82a94170c4058d451ce3ac179d99296d90554479 via git show 82a94170c:<path>, not at the sibling checkout's HEAD:
- Reader —
packages/components/src/renderers/layout/containers.tsx:662-665: PageTabsRenderer guards on item.icon and renders a LazyIcon whose name prop is item.icon (classes mr-1.5 h-3.5 w-3.5 shrink-0 opacity-70, aria-hidden), inside the TabsTrigger. - Registration —
containers.tsx:721: ComponentRegistry.register('tabs', …) publishes the key to the Studio block designer in the items input, documented as [{ label, value?, icon?, count?, visibleWhen?, children }].
That is the same two-anchor shape #9881 recorded for the accordion, in the same file, resolved through the same LazyIcon (lib/lazy-icon.tsx).
Why it is worth a card rather than a shrug
The #9397 → #9881 sequence is the measured cost of this exact absence: a sweep read a rendering key as declared-but-unenforced, a retirement candidate was opened against it, and a full dispatch cycle went into re-deriving the read point before the candidate was closed premise-overtaken. Nothing about that mechanism is specific to the accordion — page:tabsitems[].icon presents to a sweep exactly as the accordion key did, so the same false candidate is still derivable from the same absence.
Suggested shape (mirrors #9881, no acceptance change)
- A
.describe() in the file's house idiom — behavioural prose naming the renderer, with the file:line anchors and the measured pin in a docblock above the key (the split variant / recordChrome / showStar already use). record:alert's own icon is the nearest wording precedent. - An accept-pin beside the existing
page:tabs blocks in packages/spec/src/ui/component.test.ts, naming the objectui consumer in its prose, and asserting the key parses, that an undeclared sibling on the same item is still refused (so the accept is not vacuous), and that the describe still names the consumer.
Text face only — the key is already declared and already optional, so nothing would be widened, narrowed, retired or renamed. Expected Clause-②: no, @objectstack/spec patch. Worth checking during grading whether the remaining bare icon slots across component.zod.ts are the same class, so this does not come back a third time one component over.
Dedup checked: searched open and closed issues for page:tabs items icon / liveness / describe / accept-pin — only #9881 (the accordion card, open) and #9397 (closed premise-overtaken) match, and neither covers this key.
Filed unassigned by the #9881 dev seat (session
session_016D9wdJR14KKCxz1WgdAzcw) as an out-of-scope finding hit while landing #9971. Observation class — awaiting first-touch grading, not queued.Observation
#9881 gave
PageAccordionProps.items[].icona.describe()naming its objectui read point plus an accept-pin, so a liveness sweep stops re-deriving a false retirement candidate for a key that renders. Its sibling one component over is in the identical state and was not in that card's scope.PageTabsProps.items[].icon(packages/spec/src/ui/component.zod.ts:626) is declared bare:No
.describe(), no docblock, and no test anywhere asserts it. Its neighbours in the samestrictObjectall carry describes (visibleWhen,value,count,children), which is what makes this one read as unclassified rather than as deliberately terse.It is live — measured at the pin this repo builds against
Read at
.objectui-sha=82a94170c4058d451ce3ac179d99296d90554479viagit show 82a94170c:<path>, not at the sibling checkout's HEAD:packages/components/src/renderers/layout/containers.tsx:662-665:PageTabsRendererguards onitem.iconand renders aLazyIconwhosenameprop isitem.icon(classesmr-1.5 h-3.5 w-3.5 shrink-0 opacity-70,aria-hidden), inside theTabsTrigger.containers.tsx:721:ComponentRegistry.register('tabs', …)publishes the key to the Studio block designer in theitemsinput, documented as[{ label, value?, icon?, count?, visibleWhen?, children }].That is the same two-anchor shape #9881 recorded for the accordion, in the same file, resolved through the same
LazyIcon(lib/lazy-icon.tsx).Why it is worth a card rather than a shrug
The #9397 → #9881 sequence is the measured cost of this exact absence: a sweep read a rendering key as declared-but-unenforced, a retirement candidate was opened against it, and a full dispatch cycle went into re-deriving the read point before the candidate was closed premise-overtaken. Nothing about that mechanism is specific to the accordion —
page:tabsitems[].iconpresents to a sweep exactly as the accordion key did, so the same false candidate is still derivable from the same absence.Suggested shape (mirrors #9881, no acceptance change)
.describe()in the file's house idiom — behavioural prose naming the renderer, with the file:line anchors and the measured pin in a docblock above the key (the splitvariant/recordChrome/showStaralready use).record:alert's owniconis the nearest wording precedent.page:tabsblocks inpackages/spec/src/ui/component.test.ts, naming the objectui consumer in its prose, and asserting the key parses, that an undeclared sibling on the same item is still refused (so the accept is not vacuous), and that the describe still names the consumer.Text face only — the key is already declared and already optional, so nothing would be widened, narrowed, retired or renamed. Expected
Clause-②: no,@objectstack/specpatch. Worth checking during grading whether the remaining bareiconslots acrosscomponent.zod.tsare the same class, so this does not come back a third time one component over.Dedup checked: searched open and closed issues for
page:tabsitems icon / liveness / describe / accept-pin — only #9881 (the accordion card, open) and #9397 (closed premise-overtaken) match, and neither covers this key.