Uh oh!
There was an error while loading. Please reload this page.
test(core,data-objectstack): measure the two filter dialects one authored view filter reaches the wire in - #7348
Merged
Conversation
… view filter reaches the wire in objectui#7221 asked for a measurement, not a repair: take an authored two-rule filter, run both lowered shapes through `ValueDataSource` and through the ObjectStack adapter's serializer, and compare the row sets and the wire strings. These two test-only pins record the answer so it cannot silently drift. Wire and server: EQUIVALENT. Both observed strings are reproduced from named inputs, both pass `isFilterAST`, and both lower through the shipped `@objectstack/spec` to the identical FilterCondition — the null-ness direction comes from the operator name, so the `null` value slot is filler. Row sets through `ValueDataSource`: NOT equivalent in general. `matchesASTFilter` reads only an `and`/`or` head or a three-element comparison node, so a flat implicit-AND array falls through its closing `return true` and selects every row — at the top level and as a nested child of an `and`. On the card's own filter the two dialects agree only because neither is evaluated: the flat shape is unread as a shape, and the null-ness operators are unimplemented as operators. The two diverging cases are `it.fails` pins that go red the day one lowered form is agreed on. No product code changes. Which dialect wins is a ruling, not a test's decision. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-litant
marked this pull request as ready for review
September 2, 2026 12:32
Uh oh!
There was an error while loading. Please reload this page.
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 freeto 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.
Refs #7221
The measurement objectui#7221 asked for, and nothing else. No product code changes — not
ListView.tsx, notObjectGantt.tsx, notfilter-converter.ts, notElementDataSourceGate.tsx, not the adapter. Which dialect wins is a ruling; this PR only establishes what today does, as two test-only pins so the answer cannot silently drift.The answer in one line
The two dialects are NOT equivalent. They agree on the card's own filter — and they agree because
ValueDataSourceevaluates neither of them. Swap in an operator its matcher implements and they part: the flat dialect selects every row, theand-wrapped one filters. On the wire they are genuinely equivalent: the server reads both as the same query. So the row-set half is a correctness finding, the wire half is tidiness.Premise, re-verified on the base commit
2956d7af8Holds, with two refinements worth recording because both change the attribution.
ElementDataSourceGate.tsx:224still runsmergeFilterNodes(base.filter, composed.filter)for a mapping declaringfilter: true, andplugin-gantt/src/index.tsx:77-78declares it. Unchanged.plugin-listdeclares it too —ListViewBlock.tsx:31,LIST_VIEW_DATA_SOURCE. SoListViewsits behind the same gate, andListView.tsx:1696then callsbuildEffectiveFilter, which is itselfmergeFilterNodes. The two paths do not differ by "one lowers, one forwards"; they differ by how many sources survive, and a lone surviving source comes back unchanged from both.and+ 3-tuple +isnotnulldialect is notmergeFilterNodes' output. Measured: it is this repo's own adapter lowering an unloweredViewFilterRule[]—objectFilterEntriesToASTemits[field, op, entry.value]always three long, mapsis_not_nulltoisnotnull, and wraps two or more entries inand. Thenullslot isentry.valuebeingundefinedand survivingJSON.stringify. The wire pin reproduces the card's exact string from that input.1. The lowering, quoted
Two answers to the open questions the dispatch raised: the authored array is one source, wrapped whole (never split per rule), and the rules stay 2-tuples — no invented
nullvalue slot, and the spec's canonicalis_not_nullspelling survives.isnotnullnever appears anywhere in this function's output.2. Row sets through
ValueDataSourceTen rows, one per edge value,
visible_fromanddue_dateboth set to it;missing-keycarries no keys at all.[[f,"is_not_null"],[g,"is_not_null"]]["and",[f,"isnotnull",null],...]nullundefined''0falseNaN[]{}Every row, both dialects, including the rows the filter exists to exclude. They agree at "no filter applied", for two different reasons, and the pin records each separately:
matchesASTFilterrecognises anand/orhead or a three-element comparison node; a flat implicit-AND array is neither, so it reaches the closingreturn true.switchhas no null-ness arm and itsdefaultreturns true. The value slot is never reached, so filler,nulland an absent slot behave identically — the card's specific question about the value slot, answered.Change the operator to one the matcher does implement and the dialects part (three rows,
role = admin AND age > 24):[["role","=","admin"],["age",">",24]](A's shape)[["role","=","admin"]](A's shape, one rule)["and",["role","=","admin"],["age",">",24]](B's shape)["and",[[...A...]],["id","!=","zzz"]](the gate's two-source output)Reachable, not theoretical:
resolveDataSource.ts:70builds aValueDataSourceforprovider: 'value'(inline rows), andListView.tsxsends$filter: finalFilter— the flat array — to whatever data source it resolved.3. The wire, and what the server makes of it
Both strings objectui#7210 observed, reproduced from named inputs through
ObjectStackAdapter.find(both the plain and the$expandroute):Read back through the shipped
@objectstack/spec17.2.0 the adapter already depends on:Identical. The server takes the null-ness direction from the operator NAME, not the value — objectstack
packages/spec/src/data/filter.zod.ts:1911-1922atorigin/main53d3689— so thenullslot is filler either way, and both spellings areVALID_AST_OPERATORSmembers with the same lowering (:1728,:1730). A 2-tuple comparison node is accepted:isFilterASTrequireslength >= 2, not 3 (:1824). The gate's nested shape is accepted too, as a nested$andof the same conditions.What this means for severity
ValueDataSource, at the top level and nested under anand. Both dialects reach that matcher from real authoring paths, and they disagree there for every operator it implements. A ruling on which lowered form is canonical will also decide whethermatchesASTFilteris the thing that must learn to read the other one.The two diverging cases are pinned as
it.failsnamed "diverges — objectui#7221": green today because they fail, red the day someone makes the dialects agree.Tests
All on the final commit
b02bad53d.Both new files are proved to be inside their type-check file sets, not merely adjacent to them:
tsc --listFilesnames each exactly once.Gates derived from the changed paths and run locally, all green:
check-changeset-presence("2 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"),check-changeset-no-major("No changeset declares a major bump"),check-changeset-fixed,check-control-bytes,check-vi-mock-specifiers,check-vi-mock-inherit,check-type-check-coverage("41/41 packages compile their tests"),check-lint-coverage("46/46 packages linted"). The changeset carries empty frontmatter — the presence gate's own text calls that a pass for a change that should release nothing.Ablation
Test-only pins, so the mutation is of the test INPUT, never of product code. No build step is involved — both files import their subject from source in the same package. Three input mutations, each proved on disk before the run (injected text present, replaced text gone,
git diff --numstat HEADnon-empty):CONTROL_Bso the two dialects coincide — theit.failsdivergence pin went red, which is the behaviour that matters: it detects the fixed state;nullslot from the expected dialect-B wire string — 2 wire assertions went red.6 failed | 17 passed | 1 expected fail. Restoration is byte-proven, not assumed:git checkout HEAD --on both files, thengit hash-objectequals the HEAD blob for each (56487c133...,986a64418...) andgit diff HEADis empty.🤖 Generated with Claude Code
https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
Generated by Claude Code