Skip to content

docs(guide): author object-grid filter in the canonical array form - #7973

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-7713-objectos-integration-filter-array-form
Sep 6, 2026
Merged

baozhoutao merged 1 commit into
mainfrom
claude/issue-7713-objectos-integration-filter-array-form

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #7713

content/docs/guide/objectos-integration.mdx authored object-grid.filter as an and:-keyed object of { field, operator, value } entries. It now authors the registry's canonical array, the two entries AND-ed implicitly, and the comment above the key says what the key actually is.

-  // ObjectQL filter syntax
-  filter: {
-    and: [
-      { field: 'status', operator: 'eq', value: 'active' },
-      { field: 'created_date', operator: 'gte', value: '2024-01-01' }
-    ]
-  },
+  // Grid filter, JSON-rules form: an array of `{ field, operator, value }`
+  // entries, AND-ed together. `operator` takes the canonical view-filter
+  // vocabulary (`equals`, `greater_than_or_equal`, ...).
+  filter: [
+    { field: 'status', operator: 'equals', value: 'active' },
+    { field: 'created_date', operator: 'greater_than_or_equal', value: '2024-01-01' }
+  ],

One file, 7 insertions, 7 deletions. No packages/** change: adding an and / $and branch to convertFiltersToAST is objectui#6948's card, not this one.

The declared type of filter, and why the gate was green on the wrong shape

Measured on this branch:

  • Registry — packages/plugin-grid/src/index.tsx:218 registers { name: 'filter', type: 'array', ... 'Filter criteria in JSON-rules form. The canonical spelling ...' }.
  • Declared type — packages/types/src/objectql.ts:679, on ObjectGridSchema: filter?: any[]; (doc comment: "Array-based filter configuration"). The array form is the DECLARED form, not only the registry's.
  • Prior art in the tree — packages/plugin-grid/README.md:468 and :529 already show the array form, both under const schema: ObjectGridSchema = { ... }.

check:doc-snippets was green on the object form for one reason, and it is not coverage: the example's literal is a bare const schema = { ... } with no type annotation, so ObjectGridSchema.filter?: any[] was never consulted. Two controls, both run on this tree and both restored byte-identically:

  1. The block IS compiled. Injecting dataSource: zzzUndefinedIdent, at :278 turned the gate RED — content/docs/guide/objectos-integration.mdx:278:15 TS2304: Cannot find name 'zzzUndefinedIdent'. / Semantic phase: 477 of 477 block(s) judged, 1 failed. Green was never "not covered".
  2. The annotation would have caught it. Adding import type { ObjectGridSchema } from '@object-ui/types'; and annotating the literal, with the pre-fix and: object still in place, turned the gate RED with exactly one diagnostic: content/docs/guide/objectos-integration.mdx:282:5 TS2353: Object literal may only specify known properties, and 'and' does not exist in type 'any[]'.

Control 2 is not applied here — annotating the literal is outside this card's scope fence (the fence is the filter value and the comment above it). It is filed as objectui#7972, same class as objectui#7778.

Operator vocabulary: eq / gte were deprecated aliases, not members

The card's prescription named only the array shape, so the operator spelling was measured separately, against the shipped @objectstack/spec:

VIEW_FILTER_OPERATORS = ["equals","not_equals","contains","not_contains","icontains",
  "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"]

normalizeFilterOperator('eq')  === 'equals'
normalizeFilterOperator('gte') === 'greater_than_or_equal'

Neither eq nor gte is a member; both live only in VIEW_FILTER_OPERATOR_ALIASES, whose own doc comment reads: "Deprecated: new producers MUST emit the canonical forms; these aliases are a migration bridge and may be dropped in a future major." packages/plugin-grid/README.md — the tree's own authority for this key — already spells the canonical equals / after.

The lowering is byte-identical either way (viewFilterRuleToNode normalises first), so this changes nothing at runtime. Along the four decision axes: real need — a doc example is a producer, and every copy of it emits the deprecated spelling; long-term — contract-first, one vocabulary rather than N dialects, which is the alias table's own stated purpose; AI-error prevention — teaching the alias is exactly the tolerance-hides-mistakes shape, since the fold is silent and the author never learns the canonical name; startup focus, no gradualism — the maintainer's 2026-08-27 ruling retires deprecated spellings immediately rather than in stages, and there is no named external user of the alias in the docs. All four point the same way, so there is no trade-off to hand up.

Ablation, reported as observed (no red/green pin is owed here)

On the committed tree, the and:-keyed object was put back — the mutated file hashed to 3115b38eafb92ccf8ba1ba15b138668a61eea3f8, which is origin/main's own blob for this page, so the mutation provably reproduced the pre-fix bytes exactly. With it in place:

ABLATION check:doc-snippets EXIT=0
Semantic phase: 477 of 477 block(s) judged, 0 failed.
Every covered documentation snippet compiles against the built types.
ABLATION check:doc-types EXIT=0  ✅  Every documented component type is registered.

The gate stays GREEN on the defect. That is the finding's point: no reader in this repository refuses this shape. Restore leg: git checkout HEAD -- ..., blob back to 1db05a62866e59329427841aaf020f10da81ad6d, git diff HEAD empty. Marker observation on disk: the canonical-array marker counted 1 before the mutation, 0 after it, 1 after the restore.

This PR owes no new red/green pin. The docs-only fix has no runtime face, and the mechanism gap (convertFiltersToAST has no and branch) is objectui#6948's card. What this PR can honestly claim is that the page no longer teaches a shape no reader lowers; it cannot claim anything now stops the shape coming back — objectui#7972 is that repair.

Verification (all redirect-then-capture; each line is the gate's own verdict)

Dependency closure built first through the shared lock (os-verify-lock: VERDICT command-exit 0, 34 successful, 34 total), using this gate's own --build-filter closure.

gate exit verdict line
check:doc-snippets 0 Semantic phase: 477 of 477 block(s) judged, 0 failed. / Every covered documentation snippet compiles against the built types.
check:doc-types 0 ✅ Every documented component type is registered. (188 files, 889 type literals)
check:doc-fences 0 ✅ check:doc-fences — every TypeScript block in 227 document(s) is fenced ts/tsx/typescript ...
check-doc-links 0 Links are valid across 17 scan roots.
check:control-bytes 0 ✅ check-control-bytes: OK (scanned 6417 tracked text file(s); skipped 85 binary).
check:docs-route-closure 0 run clean (the page is a docs route; included because the diff is under content/docs)
check-changeset-presence 0 ✅ No source or published contract of a released package changed in this range, so no changeset is owed. — followed literally, so no .changeset/*.md is added
check-governed-queue-guard --test content/docs/guide/objectos-integration.mdx 0 ✅ NOT GOVERNED — 1 path(s) checked against 5 governed surface(s); none matched.

Ran on the final commit 8b363fe41. Plus a self-scan for control bytes on the changed file: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' returned no matches.

Not run, and declared rather than skipped: check:doc-example-readers reads JSDoc @example blocks in packages/*/src/**, a surface this diff does not touch; repo-wide pnpm lint is CI's run.

The page is NOT a governed surface (the guard's verdict above), so no maintainer-brief section is owed. This PR stays draft regardless — the dispatch fences it that way.

Out of scope, filed unassigned

  • objectui#7971 — content/docs/fields/lookup.mdx:43 and content/docs/fields/user.mdx:43 author lookupFilters with the same deprecated aliases (eq, ne). Different pages, different key: not touched here.
  • objectui#7972 — the bare-literal / no-annotation hole described above; 24 unannotated const schema = { literals across 12 doc pages, against 20 annotated across 9.

Measured and deliberately left alone: and: [ occurs exactly once under content/docs on origin/main — the instance this PR fixes. The // ObjectQL sorting comment two lines below is untouched (it describes sort, outside the fence), and $filter: { status: 'active' } at :306 is a MongoDB-style record, which IS one of the three shapes toFilterNode lowers.


Generated by Claude Code

`objectos-integration.mdx` authored `object-grid.filter` as an `and:`-keyed
object of `{ field, operator, value }` entries. No reader lowers that shape:
`toFilterNode` recognises a `ViewFilterRule[]`, an AST node array and a
MongoDB-style record, so a record whose value is an array takes
`convertFiltersToAST`'s simple-equality branch and becomes
`['and', '=', [...]]` — which `isFilterAST` accepts and the engine evaluates
as a comparison on a field literally named `and`. Wrong rows, silently.

The grid's own registry entry is the authority: `filter` is registered
`type: 'array'` and described as "the canonical spelling"
(`packages/plugin-grid/src/index.tsx`), `ObjectGridSchema.filter?: any[]`
declares the same, and `packages/plugin-grid/README.md` shows the array form
twice. The two entries are AND-ed implicitly, so the example now authors the
array directly.

`eq` / `gte` were also swapped for the canonical `equals` /
`greater_than_or_equal`: both were only members of
`VIEW_FILTER_OPERATOR_ALIASES`, the spec's deprecated migration bridge, whose
own doc comment says new producers must emit the canonical forms. The
lowering is identical either way, so this changes nothing at runtime and
stops the page teaching a spelling that may be dropped in a future major.

The comment above the key no longer says "ObjectQL filter syntax": this is
the grid's JSON-rules filter, not ObjectQL filter syntax.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uAaxiwgYDybsTNV9xwa1M
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants