fix(spec): enforce ViewFilterRule operator enum with legacy-alias normalization - #3373
Merged
Conversation
…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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Merged
2 tasks
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 104 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
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
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
marked this pull request as ready for review
July 21, 2026 00:56
This was referenced Jul 21, 2026
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>
This was referenced Jul 29, 2026
Closed
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 4, 2026
…stack-ai#4894) (objectstack-ai#4896) Both red checks on objectstack-ai#4422 (`chore: version packages (rc)`) are defects in the gates themselves, and both recur on every Changesets release PR. Check Changeset was structurally unsatisfiable for the release PR. The gate counts changesets a PR *adds* (`git diff --diff-filter=A` vs the base) — right for an ordinary PR, and the fix objectstack-ai#3373 landed after a global `find | wc -l` proved unable to ever go red in RC mode. But the release PR is the *consuming* side: it applies pending changesets into versions and CHANGELOGs and adds none, by construction. Nobody labels a bot-authored PR `skip-changeset`, so the release sat blocked on a check that could only be red. `changeset-release/main` is now exempt at the job level, pinned to the bot author as well as the branch name so a hand-pushed branch of that name cannot borrow the exemption. Scaffold E2E skewed the protocol major against itself during an RC window. The install step already falls back to `latest` when the repo's version is not yet published (`@objectstack/cli@^17.0.0-rc.2` -> ETARGET -> retry as `latest`). That fallback rewrote the generated project's dependencies but not its manifest, and the template stamps the repo's protocol major (`engines: { protocol: '^17' }`, written at version time by sync-template-versions.mjs) while `latest` still pointed at 16.x. The ADR-0087 D1 handshake then correctly refused to boot the artifact — the gate working, on a skew the step had introduced: package 'e2e-app' targets protocol ^17 (engines.protocol) but this runtime is protocol 16.0.0 The fallback now re-stamps `engines.protocol` to the major actually installed, read off node_modules/@objectstack/spec (PROTOCOL_VERSION is kept in lockstep with that package's own major, asserted by protocol-version.test.ts), and logs a `::notice` so the run's true protocol is visible rather than silently rewritten. Confined to the fallback branch: on the normal path the project installs the repo's own version, the majors agree by construction, and a template stamping the wrong major must still fail — which is what template-consistency.test.ts is for. Re-stamping runs before `npm run build`, so the artifact and the Docker image (already pinned to the resolved CLI version by the same reasoning) stay in step. CI configuration only; releases nothing. Claude-Session: https://claude.ai/code/session_01BbNVKv6KgPzuQ5p76nMgnf Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3368.
Summary
ViewFilterRuleSchema.operatorwas a free-formz.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.operator→z.preprocess(normalizeFilterOperator, z.enum(VIEW_FILTER_OPERATORS)).z.enumsurfaces the canonical operator list toz.toJSONSchemainio: 'input'mode (verified), so SchemaForm auto-renders a clean dropdown.z.preprocessfolds 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.VIEW_FILTER_OPERATORS,ViewFilterOperator,VIEW_FILTER_OPERATOR_ALIASES, andnormalizeFilterOperator().widget: 'filter-builder'on the list viewfilterfield inview.form.ts— matching the existingdataset.form.ts/page.form.tsand 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).plugin-sharingsys_share_linkfilters (isNull→is_null,isNotNull→is_not_null), preserving null semantics (not theis_emptyrewrite).sortform (deprecation note only) and retains the objectui#2601 regression fixture — thesortremoval is intentionally left to its own deprecation cycle.Decision: relative-date operators
this_quarteret 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
FilterBuilderWidgetwrites (gt/gte), bundled the unrelated breakingsortremoval, deleted the objectui#2601 regression fixture, and rewroteisNull→is_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.gte→greater_than_or_equal,eq→equals,isNull→is_null,nin→not_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