Found by the os-dev seat implementing objectui#9302. ⛔ Deliberately not repaired in that card's PR: the ruling on objectui#9302 fences the repair at FilterBuilder's value-input gate and explicitly refuses to reach this site by widening the exported set. This is a different package, a different file, and a strictly worse symptom. Grading and domain:* are the triage seat's.
Measured
Through the real convertFilterGroupToAST (packages/plugin-list/src/ListView.tsx), one text column, one condition row seeded value: '' — the state addCondition writes and the state a row keeps after the operator dropdown is changed:
stored operator |
emitted AST |
isNull (the dropdown's own id) |
["title","isnull",null] |
is_null (the spec's canonical spelling) |
[] |
equals with value: "acme" (control) |
["title","=","acme"] |
⇒ For the canonical spelling the converter emits no filter at all. The live grid then returns every record while the panel shows a filter. Silent — nothing errors, and the answer looks plausible.
⚠️ This is worse than the symptom objectui#9302 measured. That one is a stray value input beside a label saying the operator takes none — visible, and wrong only cosmetically. This one is a wrong result set.
The cause
convertFilterGroupToAST reads the operator RAW, twice:
if (VALUELESS_FILTER_BUILDER_OPERATORS.has(c.operator)) return true;
...
return isFilterValueComplete(c.operator, c.value);
VALUELESS_FILTER_BUILDER_OPERATORS holds the builder's six camelCase ids, so is_null misses the completeness short-circuit and falls through to isFilterValueComplete. That function DOES fold the operator — through the spec's normalizeFilterOperator, to decide arity — lands on scalar, and then reads value === '' as an unfinished row. The row is dropped, conditions comes out empty, and the function returns [].
So the two halves of one predicate disagree about which vocabulary they speak: the value-less short-circuit is raw, the completeness test folds. A row that is complete by the operator's own meaning is judged unfinished.
This is the exact failure the exported set was created to prevent, recorded in its own docblock: "the live-grid copy listed only isEmpty/isNotEmpty … the grid read that as an unfinished row, dropped it, and applied NO filter at all while the panel showed one. Silent, and every record came back." The set repaired that for the dropdown's spellings and left the canonical ones on the old path.
Why this is its own card
- It is not repairable from objectui#9302's file. Widening
VALUELESS_FILTER_BUILDER_OPERATORS is the change the ruling on that card refused, and for a reason that still holds: app-shell's foldFilterGroupToSpecRules documents itself as that set PLUS the canonical spellings, so widening the export would make another layer's deliberate compensation redundant by side effect.
app-shell already compensates and plugin-list does not. The persistence layer's VALUELESS_FILTER_OPERATORS superset covers the canonical spellings; the query layer has no equivalent. So a saved view can PERSIST correctly and still QUERY as though it had no filter.
- The repair is local, by the same shape objectui#9302 used: fold at the reader, leave the exported set alone.
Reproduce
convertFilterGroupToAST({ id: 'root', logic: 'and', conditions: [
{ id: 'c1', field: 'title', operator: 'is_null', value: '' },
]})
Returns []. The same group spelled isNull returns ["title","isnull",null].
Related
- objectui#9302 — the sibling site in
FilterBuilder's value-input gate. ⛔ Not repaired by this card and not waiting on it.
- objectui#4744 — the card that created the exported set, whose failure mode this reproduces for the canonical spelling.
- objectui#9306 — the open decision on which operator vocabulary wins. ⛔ Independent: this card is a defect at one reader regardless of how that is decided.
Filed unassigned by an automated development seat working objectui#9302; session reference session_01UzHd6hDYatoDn17BuwKxnZ.
Generated by Claude Code
Found by the
os-devseat implementing objectui#9302. ⛔ Deliberately not repaired in that card's PR: the ruling on objectui#9302 fences the repair atFilterBuilder's value-input gate and explicitly refuses to reach this site by widening the exported set. This is a different package, a different file, and a strictly worse symptom. Grading anddomain:*are the triage seat's.Measured
Through the real
convertFilterGroupToAST(packages/plugin-list/src/ListView.tsx), onetextcolumn, one condition row seededvalue: ''— the stateaddConditionwrites and the state a row keeps after the operator dropdown is changed:operatorisNull(the dropdown's own id)["title","isnull",null]is_null(the spec's canonical spelling)[]equalswithvalue: "acme"(control)["title","=","acme"]⇒ For the canonical spelling the converter emits no filter at all. The live grid then returns every record while the panel shows a filter. Silent — nothing errors, and the answer looks plausible.
The cause
convertFilterGroupToASTreads the operator RAW, twice:VALUELESS_FILTER_BUILDER_OPERATORSholds the builder's six camelCase ids, sois_nullmisses the completeness short-circuit and falls through toisFilterValueComplete. That function DOES fold the operator — through the spec'snormalizeFilterOperator, to decide arity — lands onscalar, and then readsvalue === ''as an unfinished row. The row is dropped,conditionscomes out empty, and the function returns[].So the two halves of one predicate disagree about which vocabulary they speak: the value-less short-circuit is raw, the completeness test folds. A row that is complete by the operator's own meaning is judged unfinished.
This is the exact failure the exported set was created to prevent, recorded in its own docblock: "the live-grid copy listed only
isEmpty/isNotEmpty… the grid read that as an unfinished row, dropped it, and applied NO filter at all while the panel showed one. Silent, and every record came back." The set repaired that for the dropdown's spellings and left the canonical ones on the old path.Why this is its own card
VALUELESS_FILTER_BUILDER_OPERATORSis the change the ruling on that card refused, and for a reason that still holds:app-shell'sfoldFilterGroupToSpecRulesdocuments itself as that set PLUS the canonical spellings, so widening the export would make another layer's deliberate compensation redundant by side effect.app-shellalready compensates andplugin-listdoes not. The persistence layer'sVALUELESS_FILTER_OPERATORSsuperset covers the canonical spellings; the query layer has no equivalent. So a saved view can PERSIST correctly and still QUERY as though it had no filter.Reproduce
Returns
[]. The same group spelledisNullreturns["title","isnull",null].Related
FilterBuilder's value-input gate. ⛔ Not repaired by this card and not waiting on it.Filed unassigned by an automated development seat working objectui#9302; session reference
session_01UzHd6hDYatoDn17BuwKxnZ.Generated by Claude Code