Skip to content

fix(spec): enforce ViewFilterRule operator enum with legacy-alias normalization#3373

Merged
os-zhuang merged 3 commits into
mainfrom
claude/viewfilter-operator-enum
Jul 21, 2026
Merged

fix(spec): enforce ViewFilterRule operator enum with legacy-alias normalization#3373
os-zhuang merged 3 commits into
mainfrom
claude/viewfilter-operator-enum

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #3368.

Summary

ViewFilterRuleSchema.operator was a free-form z.string(), so JSON-Schema consumers (notably ObjectUI's SchemaForm) could not render an operator dropdown and invalid operators passed validation silently. This replaces it with a canonical enum + on-parse alias normalization, chosen over a bare strict enum so already-stored view metadata does not break.

  • operatorz.preprocess(normalizeFilterOperator, z.enum(VIEW_FILTER_OPERATORS)).
    • The z.enum surfaces the canonical operator list to z.toJSONSchema in io: 'input' mode (verified), so SchemaForm auto-renders a clean dropdown.
    • The z.preprocess folds legacy shorthand/camelCase spellings (eq, gt, gte, isNull, notEquals, nin, …) to canonical on parse, so consumers see exactly one vocabulary and stored data keeps validating.
    • Genuinely unknown operators are rejected.
  • Exports the single source of truth: VIEW_FILTER_OPERATORS, ViewFilterOperator, VIEW_FILTER_OPERATOR_ALIASES, and normalizeFilterOperator().
  • Pins widget: 'filter-builder' on the list view filter field in view.form.ts — matching the existing dataset.form.ts / page.form.ts and the schema's own JSDoc example — so the Studio editor renders the field-type-aware visual builder instead of raw repeater rows (contract-first: the fix lives in the producer/metadata, not the renderer).
  • Canonicalizes plugin-sharing sys_share_link filters (isNullis_null, isNotNullis_not_null), preserving null semantics (not the is_empty rewrite).
  • Keeps the legacy string sort form (deprecation note only) and retains the objectui#2601 regression fixture — the sort removal is intentionally left to its own deprecation cycle.

Decision: relative-date operators

this_quarter et al. are not filter-rule operators — they are date-range presets (DashboardFilterSchema.defaultRange) and were unimplemented at the filter-rule runtime level. They are dropped from the operator vocabulary; JSDoc and fixtures updated accordingly.

Why not the previous attempt (#3363)

That PR used a strict enum with no aliases, which breaks stored data and ObjectUI's own FilterBuilderWidget writes (gt/gte), bundled the unrelated breaking sort removal, deleted the objectui#2601 regression fixture, and rewrote isNullis_empty (changing semantics). Its "all tests pass" came from editing the tests themselves.

Test plan

  • pnpm --filter @objectstack/spec test — 256 files, 6809 tests pass.
  • pnpm --filter @objectstack/spec exec tsc --noEmit — clean.
  • Added coverage: alias normalization (gtegreater_than_or_equal, eqequals, isNullis_null, ninnot_in, …) and unknown-operator rejection.

Paired with objectstack-ai/objectui#2764.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PwbhoMeqh33xFWq5M77QTJ


Generated by Claude Code

…malization

Replace the free-form `operator: z.string()` on ViewFilterRuleSchema with a
canonical `z.enum` (VIEW_FILTER_OPERATORS) wrapped in `z.preprocess` that folds
legacy shorthand/camelCase spellings (eq, gt, isNull, notEquals, …) to canonical
on parse. This surfaces a clean operator enum to JSON-Schema consumers (ObjectUI
SchemaForm renders a dropdown) and rejects genuinely unknown operators, while
already-stored metadata keeps validating and upgrades to one vocabulary.

- Export VIEW_FILTER_OPERATORS, ViewFilterOperator, VIEW_FILTER_OPERATOR_ALIASES
  and a normalizeFilterOperator() helper as the single source of truth.
- Pin `widget: 'filter-builder'` on the list view `filter` field in view.form.ts,
  matching dataset/page forms so the visual builder renders instead of a repeater.
- Canonicalize plugin-sharing sys_share_link filters (isNull -> is_null,
  isNotNull -> is_not_null), preserving null semantics.
- Keep the legacy string `sort` form (deprecation note only); retain the
  objectui#2601 regression fixture.
- Relative-date operators (this_quarter, …) are not filter-rule operators; tests
  updated to canonical operators and alias-normalization / unknown-rejection
  coverage added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PwbhoMeqh33xFWq5M77QTJ
@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 20, 2026 6:33pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/plugin-sharing, @objectstack/spec.

104 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/cli.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via packages/plugins/plugin-sharing, @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via packages/plugins/plugin-sharing, @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-sharing, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/plugins/plugin-sharing, packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Auto-generated reference docs reflect the ViewFilterRule operator enum
(replacing the free-form `string`). Regenerated via gen:schema && gen:docs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PwbhoMeqh33xFWq5M77QTJ
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Jul 20, 2026
Additive-only: VIEW_FILTER_OPERATORS, VIEW_FILTER_OPERATOR_ALIASES,
ViewFilterOperator, normalizeFilterOperator (0 breaking, 4 added).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PwbhoMeqh33xFWq5M77QTJ
@os-zhuang
os-zhuang marked this pull request as ready for review July 21, 2026 00:56
@os-zhuang
os-zhuang merged commit d419826 into main Jul 21, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/viewfilter-operator-enum branch July 21, 2026 00:56
os-zhuang added a commit that referenced this pull request Jul 21, 2026
…ty dir (#3376)

The `Check Changeset` gate counted every `.md` in `.changeset/`
(`find | wc -l`), so it only asserted the directory was non-empty — never
that THIS PR contributed a changeset. In pre-release (RC) mode `changeset
version` retains consumed changeset files, so the directory is permanently
non-empty and the gate can never go red: #3373 merged a real spec /
api-surface fix with no changeset while the step reported "Found 104
changeset(s)".

Diff the base commit instead and count only changesets the PR adds. An
empty-frontmatter changeset still counts — it is the sanctioned "releases
nothing" declaration. Proven to go RED on #3373's diff (0 added) and GREEN
on #3360's (1 added).

Also backfills the missing #3373 changeset so the ViewFilterRule operator
enum fix actually ships in the next release.

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec(view): canonicalize ViewFilterRule operator vocabulary (enum + legacy aliases) and deprecate legacy string sort

2 participants