Found while implementing objectui#9187 (same interface, different key —
that card is RecordHighlightsComponentProps.layout, this is the fields[]
entry union one member down). Filed rather than carried on that branch because
the evidence balance points the other way and the direction is not triage-able
from the #9187 diff alone — see "Why this is not the same repair" below.
packages/types/src/record-components.ts (⚠️ generic written with SQUARE
brackets — GitHub's body sanitizer eats tag-shaped fragments, fences included):
fields: Array[
string | { name: string; label?: string; icon?: string; type?: string; readonly?: boolean }
];
@objectstack/spec RecordHighlightsProps.fields[]'s object arm declares
FOUR keys and is $strict. icon is not one of them.
Measured, on the installed pin (17.4.0), against controls
Reading the arm's own shape rather than transcribing anything:
fields[] object-arm keys : ["name","label","type","readonly"]
arm strictness : $strict (an unlisted key is refused, not stripped)
The parse probes, one instrument, one loop:
{ fields: [{ name: 'x', label: 'L' }] } GREEN <- CONTROL
{ fields: [{ name: 'x', icon: 'star' }] } RED invalid_union at fields.0
{ fields: [{ name: 'x', zzzNonsense: 1 }] } RED invalid_union at fields.0 <- CONTROL
{ fields: ['x'] } GREEN <- CONTROL
All three controls fired as they should: a declared key parses green, so the
arm is not refusing everything; an arbitrary key is refused with the SAME code
as icon, so icon is not special-cased; and the string arm is unaffected.
Why it matters
An author writes fields: [{ name: 'amount', icon: 'dollar-sign' }] on a
record:highlights block, tsc is green, and the contract refuses the whole
document at publish. Same class as objectui#9187 and objectui#8604: the
published TypeScript face offers something the only layer that matters rejects.
⚠️ Why this is NOT the same repair as objectui#9187
On objectui#9187 every other layer already agreed with the contract, so
narrowing the declaration was the whole fix. Here the balance is inverted —
icon is live on three layers and dead only on the contract:
packages/types declares it (the arm quoted above).
RecordHighlightsRenderer reads it, in the normalizer that accepts the
bare-string and object forms.
@object-ui/plugin-detail's registry manifest publishes it to authors, in
the fields input description.
⇒ deleting the declaration alone would leave a renderer read and a published
manifest promise pointing at a key nothing can author. The two candidate
directions are a spec widening (declare icon on the arm upstream) and a
three-layer retirement here. Widening a published accepted set is on the
human floor and is explicitly not this repository's call, so this card is a
measurement plus a fork, not a plan.
⛔ Not claimed, ⛔ not graded — labels, domain:* and priority are triage's.
Dedup — the channel, its control, and its GAP
Read the full open population over repository-scoped REST (5 pages,
per_page=100, 467 items, 455 of them issues) and grepped title plus body: 10
cards mention "highlight", none of them also mentions icon. Known-hit
control fired — objectui#9187 itself is in the listing, so the enumeration
reaches the population rather than returning a confident empty.
⚠️ The closed half was not measured, and that is a gap, not a silence. Both
text-search channels were refused in this session: MCP search_issues returned
API rate limit already exceeded on the shared seat identity, and
repository-scoped REST /search/issues is refused by this container's proxy
(sessions are bound to their configured repositories), which leaves no text
query over closed cards. Nearest open neighbour checked by hand and ruled NOT a
duplicate: objectui#8649 is the opposite direction (renderer reads keys the
TYPE does not declare); this is the type declaring a key the CONTRACT does not.
Refs: objectui#9187 (the sibling key, same interface) · objectui#8604 (same
class, one interface over) · objectui#8649 (opposite direction, same renderers).
Reported by an ObjectStack dev agent seat while working objectui#9187; session
reference https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ (written as
prose because an attribution footer does not survive issue creation).
Found while implementing objectui#9187 (same interface, different key —
that card is
RecordHighlightsComponentProps.layout, this is thefields[]entry union one member down). Filed rather than carried on that branch because
the evidence balance points the other way and the direction is not triage-able
from the
#9187diff alone — see "Why this is not the same repair" below.packages/types/src/record-components.ts(brackets — GitHub's body sanitizer eats tag-shaped fragments, fences included):
@objectstack/specRecordHighlightsProps.fields[]'s object arm declaresFOUR keys and is
$strict.iconis not one of them.Measured, on the installed pin (17.4.0), against controls
Reading the arm's own shape rather than transcribing anything:
The parse probes, one instrument, one loop:
All three controls fired as they should: a declared key parses green, so the
arm is not refusing everything; an arbitrary key is refused with the SAME code
as
icon, soiconis not special-cased; and the string arm is unaffected.Why it matters
An author writes
fields: [{ name: 'amount', icon: 'dollar-sign' }]on arecord:highlightsblock,tscis green, and the contract refuses the wholedocument at publish. Same class as objectui#9187 and objectui#8604: the
published TypeScript face offers something the only layer that matters rejects.
On objectui#9187 every other layer already agreed with the contract, so
narrowing the declaration was the whole fix. Here the balance is inverted —
iconis live on three layers and dead only on the contract:packages/typesdeclares it (the arm quoted above).RecordHighlightsRendererreads it, in the normalizer that accepts thebare-string and object forms.
@object-ui/plugin-detail's registry manifest publishes it to authors, inthe
fieldsinput description.⇒ deleting the declaration alone would leave a renderer read and a published
manifest promise pointing at a key nothing can author. The two candidate
directions are a spec widening (declare
iconon the arm upstream) and athree-layer retirement here. Widening a published accepted set is on the
human floor and is explicitly not this repository's call, so this card is a
measurement plus a fork, not a plan.
⛔ Not claimed, ⛔ not graded — labels,
domain:*and priority are triage's.Dedup — the channel, its control, and its GAP
Read the full open population over repository-scoped REST (5 pages,
per_page=100, 467 items, 455 of them issues) and grepped title plus body: 10
cards mention "highlight", none of them also mentions
icon. Known-hitcontrol fired — objectui#9187 itself is in the listing, so the enumeration
reaches the population rather than returning a confident empty.
text-search channels were refused in this session: MCP
search_issuesreturnedAPI rate limit already exceededon the shared seat identity, andrepository-scoped REST
/search/issuesis refused by this container's proxy(
sessions are bound to their configured repositories), which leaves no textquery over closed cards. Nearest open neighbour checked by hand and ruled NOT a
duplicate: objectui#8649 is the opposite direction (renderer reads keys the
TYPE does not declare); this is the type declaring a key the CONTRACT does not.
Refs: objectui#9187 (the sibling key, same interface) · objectui#8604 (same
class, one interface over) · objectui#8649 (opposite direction, same renderers).
Reported by an ObjectStack dev agent seat while working objectui#9187; session
reference
https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ(written asprose because an attribution footer does not survive issue creation).