Found while implementing #4032 (binding DashboardComponentSchema.globalFilters to the spec's GlobalFilter). Not fixed there — that card's scope is the I18nLabel widening, and removing a tolerated shorthand is a behaviour change on stored metadata with its own migration question.
The divergence
@object-ui/core's normalizeFilterOptions (packages/core/src/utils/dashboard-filters.ts) documents and accepts two option forms:
the @objectstack/spec GlobalFilterSchema.options form is { value, label } objects; the bare-string shorthand (options: ['EMEA', …]) is also accepted
The spec accepts only the first. Measured against @objectstack/spec@17.0.0-rc.6:
DashboardSchema.safeParse({ …, globalFilters: [{ field: 'region', type: 'select', options: ['EMEA', 'APAC'] }] })
→ ["Invalid input: expected object, received string",
"Invalid input: expected object, received string"]
… options: [{ value: 'emea', label: 'EMEA' }]
→ ACCEPTED
So a dashboard authored with the shorthand renders correctly in objectui and is refused the moment it reaches the platform's validation. That is the "one strict contract beats N dialects" case AGENTS.md #0.1 names: a renderer-side tolerance that has quietly become a second de-facto contract, and it hides the producer's bug rather than surfacing it.
What #4032 already changed, and what it deliberately did not
#4032 bound the TYPE to the spec, so the shorthand is now a tsc error at authoring — that half is done and pinned in packages/types/src/__tests__/dashboard-global-filters-spec-binding.test.ts. The RUNTIME tolerance is untouched: normalizeFilterOptions still lifts a bare string, because already-persisted dashboards may carry one and dropping them silently would turn a rendering filter into an empty one.
The decision this needs
- Keep the runtime lift permanently, and get the spec to admit the shorthand (it is a genuinely convenient authoring form) — i.e. fix the divergence at the contract.
- Retire the lift behind a deprecation window: warn on a bare string now, remove later. Needs a survey of stored dashboards first.
- Retire it immediately — only defensible if no stored dashboard uses it.
No repo source authors the shorthand today (grepped packages/, apps/, examples/), so the local blast radius is zero; the unknown is customer metadata.
Generated by Claude Code
Found while implementing #4032 (binding
DashboardComponentSchema.globalFiltersto the spec'sGlobalFilter). Not fixed there — that card's scope is theI18nLabelwidening, and removing a tolerated shorthand is a behaviour change on stored metadata with its own migration question.The divergence
@object-ui/core'snormalizeFilterOptions(packages/core/src/utils/dashboard-filters.ts) documents and accepts two option forms:The spec accepts only the first. Measured against
@objectstack/spec@17.0.0-rc.6:So a dashboard authored with the shorthand renders correctly in objectui and is refused the moment it reaches the platform's validation. That is the "one strict contract beats N dialects" case AGENTS.md #0.1 names: a renderer-side tolerance that has quietly become a second de-facto contract, and it hides the producer's bug rather than surfacing it.
What #4032 already changed, and what it deliberately did not
#4032 bound the TYPE to the spec, so the shorthand is now a
tscerror at authoring — that half is done and pinned inpackages/types/src/__tests__/dashboard-global-filters-spec-binding.test.ts. The RUNTIME tolerance is untouched:normalizeFilterOptionsstill lifts a bare string, because already-persisted dashboards may carry one and dropping them silently would turn a rendering filter into an empty one.The decision this needs
No repo source authors the shorthand today (grepped
packages/,apps/,examples/), so the local blast radius is zero; the unknown is customer metadata.Generated by Claude Code