Background
ViewFilterRuleSchema.operator is currently a free-form z.string(), so Studio (ObjectUI SchemaForm) cannot render an operator dropdown, and invalid operators pass validation silently. PR #3363 attempted to fix this by hard-switching to a 19-value z.enum, but that approach breaks existing data and consumers (see "Why the previous attempt was rejected" below). This issue tracks a redesign.
Paired UI work lives in objectui (field-type-aware operator/value widgets); this spec change is its blocking dependency.
Problem
The operator vocabulary actually in use today is wider than any single canonical list:
- ObjectUI's Studio
FilterBuilderWidget writes shorthand operators to stored view metadata: gt, gte, lt, lte (see FB_TO_SPEC in packages/app-shell/src/views/metadata-admin/widgets.tsx).
- ObjectUI's runtime accepts shorthands:
eq, ne, neq, gt, lt, gte, lte, nin (see specOperatorToAst in packages/plugin-list/src/UserFilters.tsx).
- Existing object definitions in this repo use
isNull / isNotNull (plugin-sharing) and relative-date operators like this_quarter (documented in the schema's own JSDoc).
A strict enum containing only long-form snake_case names invalidates all of the above, including data ObjectUI's own editor writes today.
Requirements
- Define the canonical operator vocabulary (proposed: long-form snake_case —
equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than, greater_than_or_equal, less_than_or_equal, in, not_in, is_empty, is_not_empty, is_null, is_not_null, before, after, between).
- Accept legacy aliases on parse —
eq, ne, neq, gt, lt, gte, lte, nin, isNull, isNotNull, isEmpty, isNotEmpty, camelCase forms (notEquals, greaterThan, …) — normalized to the canonical form (e.g. via z.preprocess/transform), so existing stored metadata keeps validating. Alternatively ship a migration; either way, no silent breakage.
- Decide the fate of relative-date operators (
this_quarter, etc.). They are documented in the current schema JSDoc; removing them is a product decision, not a cleanup. If removed, provide a replacement (e.g. relative-date value tokens) or an explicit deprecation.
- Deprecate legacy string
sort separately. ListViewSchema.sort accepting "field desc" is a documented compatibility form, and the task.view.ts fixture using it is intentional live regression coverage for objectui#2601 ("schema.sort.map is not a function"). Removal must be its own deprecation-cycle change — keep the union (optionally with a deprecation note) for now, and do not delete the regression fixture until the string form is actually dropped.
- Fixture updates must be semantics-preserving. E.g. rewriting
isNull → is_empty changes meaning unless the data layer treats them identically; use the alias mapping instead of rewording filters.
Acceptance criteria
Prior art
Background
ViewFilterRuleSchema.operatoris currently a free-formz.string(), so Studio (ObjectUI SchemaForm) cannot render an operator dropdown, and invalid operators pass validation silently. PR #3363 attempted to fix this by hard-switching to a 19-valuez.enum, but that approach breaks existing data and consumers (see "Why the previous attempt was rejected" below). This issue tracks a redesign.Paired UI work lives in objectui (field-type-aware operator/value widgets); this spec change is its blocking dependency.
Problem
The operator vocabulary actually in use today is wider than any single canonical list:
FilterBuilderWidgetwrites shorthand operators to stored view metadata:gt,gte,lt,lte(seeFB_TO_SPECinpackages/app-shell/src/views/metadata-admin/widgets.tsx).eq,ne,neq,gt,lt,gte,lte,nin(seespecOperatorToAstinpackages/plugin-list/src/UserFilters.tsx).isNull/isNotNull(plugin-sharing) and relative-date operators likethis_quarter(documented in the schema's own JSDoc).A strict enum containing only long-form snake_case names invalidates all of the above, including data ObjectUI's own editor writes today.
Requirements
equals,not_equals,contains,not_contains,starts_with,ends_with,greater_than,less_than,greater_than_or_equal,less_than_or_equal,in,not_in,is_empty,is_not_empty,is_null,is_not_null,before,after,between).eq,ne,neq,gt,lt,gte,lte,nin,isNull,isNotNull,isEmpty,isNotEmpty, camelCase forms (notEquals,greaterThan, …) — normalized to the canonical form (e.g. viaz.preprocess/transform), so existing stored metadata keeps validating. Alternatively ship a migration; either way, no silent breakage.this_quarter, etc.). They are documented in the current schema JSDoc; removing them is a product decision, not a cleanup. If removed, provide a replacement (e.g. relative-date value tokens) or an explicit deprecation.sortseparately.ListViewSchema.sortaccepting"field desc"is a documented compatibility form, and thetask.view.tsfixture using it is intentional live regression coverage for objectui#2601 ("schema.sort.map is not a function"). Removal must be its own deprecation-cycle change — keep the union (optionally with a deprecation note) for now, and do not delete the regression fixture until the string form is actually dropped.isNull→is_emptychanges meaning unless the data layer treats them identically; use the alias mapping instead of rewording filters.Acceptance criteria
ViewFilterRuleSchema.operatorexposes an enum (so JSON-schema consumers like SchemaForm getenumchoices) while still parsing all legacy aliases listed above.ObjectSchemadefinitions in this repo (plugin-sharing, examples) validate unchanged, without semantic rewrites.ListViewSchema.sortstring form still parses; its removal is tracked in a separate deprecation issue.Prior art
FilterBuilderWidgetwrites; bundled the unrelated breakingsortchange and deleted the objectui#2601 regression fixture; test-suite pass was achieved by rewriting the tests/fixtures themselves.