Found while implementing objectui#5170 (PR objectui#5226). This is a measurement of the contract that PR leaves behind, filed unassigned for PM triage — the shipped behaviour is correct, this is about how easy it stays correct.
The reading
PR objectui#5226 routed the three option-picker loaders through a four-arm LoadState union (idle | loading | loaded | error) in ResourceEditPage.tsx, so the loaders themselves can no longer spell a fault as a measurement. But at the WidgetContext boundary in packages/app-shell/src/views/metadata-admin/widgets.tsx the union is projected back down into a pair:
- the catalog arrays (
objectNames, objectFields, objectViews, objectActions) are still [] on a failed load — byte-identical to a completed load that found nothing; - the fault travels alongside them, in a separate
catalogErrors record.
Every picker in the file consults catalogErrors first today, and the PR's tests pin that per loader. The residue is that the contract does not require it. A picker written next month that reads
constfields=context?.objectFields??[];
and renders "no fields" is type-correct, reads naturally, passes review, and silently reintroduces exactly the defect objectui#5170 was filed for. The declaration and the enforcement have come apart: CatalogErrors's own doc comment has to say "a picker MUST consult this before it renders its catalog", which is a comment doing a type's job.
Why the PR did not just do it
Stated so the trade-off is on the record rather than rediscovered: making the catalogs themselves the union is the honest shape, and it was measured as reachable — the true WidgetContext picker reads are confined to 11 sites in widgets.tsx plus one in SchemaForm.tsx (detectFieldRefWidget). The cost is that it also breaks every test fixture that constructs a context literal (SchemaForm.widgetLabelling, ViewRefWidget, SchemaForm.actionObjectPicker, selector-placeholder.i18n, FilterModeWidget — roughly 17 lines across 5 files), none of which were in #5170's declared file surface. That is a mechanical but real widening of a bug-fix PR's blast radius into other people's fixtures, so it was left for a decision rather than taken unilaterally.
Shape, if it is taken
Replace the pair with the union at the boundary — e.g. objectFields?: LoadState<...> reusing views/metadata-admin/loadState.ts — so an unchecked ?? [] stops compiling instead of silently rendering a lie. The loud break at each read site is the point: it is a compile error at exactly the places that must decide what a failure looks like. A small readCatalog() accessor keeps each call site to a couple of lines, and the fixtures move to a loadedCatalog([...]) helper.
Worth doing as one change with objectui#5227 (the fourth loader, FieldSelectorWidget), since that one needs new render wiring anyway and would be the first consumer of the tightened contract.
Severity
Deliberately not asserted. Nothing is user-visible today — this is a hardening/drift card, not a defect — and objectui#5110's own card records that severity judged at filing time is unreliable in both directions. Filed plainly for the triage round.
Dedup
Searched open issues for the WidgetContext option-catalog contract, for the empty-array-on-failure spelling, and for picker error side-channels; nothing covering it (zero hits, including the control terms). Control: the neighbouring search family returns objectui#5040 and objectui#4871, both real WidgetContext / metadata-admin widget cards, so the query family is live rather than dead. No duplicate.
Related
objectui#5170 and objectui#5169 (the cards whose fix introduced this boundary, PR objectui#5226) · objectui#5227 (the fourth loader, which this would tighten first) · objectui#5110 (the union) · ADR-0110 D3, "a miss and a fault are different facts"
Found while implementing objectui#5170 (PR objectui#5226). This is a measurement of the contract that PR leaves behind, filed unassigned for PM triage — the shipped behaviour is correct, this is about how easy it stays correct.
The reading
PR objectui#5226 routed the three option-picker loaders through a four-arm
LoadStateunion (idle | loading | loaded | error) inResourceEditPage.tsx, so the loaders themselves can no longer spell a fault as a measurement. But at theWidgetContextboundary inpackages/app-shell/src/views/metadata-admin/widgets.tsxthe union is projected back down into a pair:objectNames,objectFields,objectViews,objectActions) are still[]on a failed load — byte-identical to a completed load that found nothing;catalogErrorsrecord.Every picker in the file consults
catalogErrorsfirst today, and the PR's tests pin that per loader. The residue is that the contract does not require it. A picker written next month that readsand renders "no fields" is type-correct, reads naturally, passes review, and silently reintroduces exactly the defect objectui#5170 was filed for. The declaration and the enforcement have come apart:
CatalogErrors's own doc comment has to say "a picker MUST consult this before it renders its catalog", which is a comment doing a type's job.Why the PR did not just do it
Stated so the trade-off is on the record rather than rediscovered: making the catalogs themselves the union is the honest shape, and it was measured as reachable — the true
WidgetContextpicker reads are confined to 11 sites inwidgets.tsxplus one inSchemaForm.tsx(detectFieldRefWidget). The cost is that it also breaks every test fixture that constructs a context literal (SchemaForm.widgetLabelling,ViewRefWidget,SchemaForm.actionObjectPicker,selector-placeholder.i18n,FilterModeWidget— roughly 17 lines across 5 files), none of which were in #5170's declared file surface. That is a mechanical but real widening of a bug-fix PR's blast radius into other people's fixtures, so it was left for a decision rather than taken unilaterally.Shape, if it is taken
Replace the pair with the union at the boundary — e.g.
objectFields?: LoadState<...>reusingviews/metadata-admin/loadState.ts— so an unchecked?? []stops compiling instead of silently rendering a lie. The loud break at each read site is the point: it is a compile error at exactly the places that must decide what a failure looks like. A smallreadCatalog()accessor keeps each call site to a couple of lines, and the fixtures move to aloadedCatalog([...])helper.Worth doing as one change with objectui#5227 (the fourth loader,
FieldSelectorWidget), since that one needs new render wiring anyway and would be the first consumer of the tightened contract.Severity
Deliberately not asserted. Nothing is user-visible today — this is a hardening/drift card, not a defect — and objectui#5110's own card records that severity judged at filing time is unreliable in both directions. Filed plainly for the triage round.
Dedup
Searched open issues for the
WidgetContextoption-catalog contract, for the empty-array-on-failure spelling, and for picker error side-channels; nothing covering it (zero hits, including the control terms). Control: the neighbouring search family returns objectui#5040 and objectui#4871, both realWidgetContext/ metadata-admin widget cards, so the query family is live rather than dead. No duplicate.Related
objectui#5170 and objectui#5169 (the cards whose fix introduced this boundary, PR objectui#5226) · objectui#5227 (the fourth loader, which this would tighten first) · objectui#5110 (the union) · ADR-0110 D3, "a miss and a fault are different facts"