Found by the os-dev seat implementing objectui#9359. ⛔ Deliberately not repaired in that card's PR: different package, different file, and a different mechanism — this one does not read the shared value-less set at all. Grading and domain:* are the triage seat's.
Measured
groupToCondition (packages/app-shell/src/views/metadata-admin/inspectors/datasetFilterCondition.ts) is the WRITE half of the Studio inspector's bridge between the visual FilterBuilder and the Mongo-style FilterCondition stored on dataset.filter / measure.filter. Driven directly, one date column, one condition row:
| builder row |
committed FilterCondition |
equals with value "acme" (control) |
{ closed_at: { $eq: "acme" } } |
isEmpty |
{ closed_at: { $exists: false } } |
isNotEmpty |
{ closed_at: { $exists: true } } |
isNull |
undefined — the row is dropped |
isNotNull |
undefined — the row is dropped |
is_null (canonical) |
undefined |
is_empty (canonical) |
undefined |
The control fires in the same run, so the empty answers are a reading about those operators and not a dead function.
Why it is reachable, and why it is worse than "a row is ignored"
DatasetDefaultInspector.tsx mounts the real builder and commits through this function on every change:
FilterBuilder ... onChange={(g) => onCommit(groupToCondition(g))}
It passes no extraOperators, so the dropdown offers the DEFAULT operator list — and isNull / isNotNull are in it. They are not OPT_IN_OPERATORS (only containsCaseInsensitive, exists and notExists are), so Is null is an ordinary menu entry in this inspector.
⚠️ groupToCondition returns undefined when no row survives. So an author who has a working dataset filter, opens the inspector and switches the single condition's operator to Is null, commits undefined — the stored filter is erased, not merely left unchanged. Nothing errors. The panel still shows the condition.
The cause
OP_TO_MONGO has no row for isNull / isNotNull, and the two value-less operators the function DOES understand are matched as raw literals:
if (c.operator === 'isEmpty') { parts.push({ [c.field]: { $exists: false } }); continue; }
if (c.operator === 'isNotEmpty') { parts.push({ [c.field]: { $exists: true } }); continue; }
const mop = OP_TO_MONGO[c.operator];
if (!mop) continue; // unmapped (e.g. notContains/between) — drop rather than emit a bad filter
That continue is a deliberate decision for notContains / between — operators this dialect genuinely cannot express, where dropping beats emitting a wrong filter. isNull / isNotNull are a different case: the Mongo dialect CAN express them (the spec's $null, and the file's own header names it), the builder draws them as COMPLETE rows with no value input, and the inspector offers them in its menu. So the drop is not the documented fallback, it is an unhandled operator falling into the fallback's path.
⚠️ Note the asymmetry with the read direction: conditionToGroup reports representable: false for shapes it cannot round-trip, so the caller can fall back to the source editor. The write direction has no equivalent signal — it drops silently.
Relationship to objectui#9359 — same CLASS, different mechanism
objectui#9359 is a reader of the shared VALUELESS_FILTER_BUILDER_OPERATORS that forgot to fold the operator. This file does not read that set at all; it keeps its own raw literals and its own operator table. So it is not fixed by that card's repair, and not fixed by widening anything — a point worth keeping, because the two look alike from a distance.
⛔ Independent of objectui#9306 (which operator vocabulary wins): every spelling in the table above is dropped, the dropdown's own ids included, so the defect does not turn on that decision.
Reproduce
groupToCondition({ id: 'g', logic: 'and', conditions: [
{ id: 'c1', field: 'closed_at', operator: 'isNull', value: '' },
]})
Returns undefined. The same group with operator: 'isEmpty' returns { closed_at: { $exists: false } }.
Not measured, and worth a look by whoever takes this
- whether
onCommit(undefined) clears the persisted key or is filtered upstream — measured only at the function boundary here, not in a browser;
- whether the same table gap affects the other operators it lacks (
startsWith, endsWith, notContains, between) in a way the "drop rather than emit a bad filter" comment no longer covers.
Deduplicated before filing against the repository-scoped issue list endpoint (183 open issues over two pages), with objectui#9359 itself as the known-hit control so the empty result is a reading rather than a silent zero. Filed unassigned and unlabelled by an automated development seat working objectui#9359; session reference session_01UzHd6hDYatoDn17BuwKxnZ.
Found by the
os-devseat implementing objectui#9359. ⛔ Deliberately not repaired in that card's PR: different package, different file, and a different mechanism — this one does not read the shared value-less set at all. Grading anddomain:*are the triage seat's.Measured
groupToCondition(packages/app-shell/src/views/metadata-admin/inspectors/datasetFilterCondition.ts) is the WRITE half of the Studio inspector's bridge between the visualFilterBuilderand the Mongo-styleFilterConditionstored ondataset.filter/measure.filter. Driven directly, onedatecolumn, one condition row:FilterConditionequalswith value"acme"(control){ closed_at: { $eq: "acme" } }isEmpty{ closed_at: { $exists: false } }isNotEmpty{ closed_at: { $exists: true } }isNullundefined— the row is droppedisNotNullundefined— the row is droppedis_null(canonical)undefinedis_empty(canonical)undefinedThe control fires in the same run, so the empty answers are a reading about those operators and not a dead function.
Why it is reachable, and why it is worse than "a row is ignored"
DatasetDefaultInspector.tsxmounts the real builder and commits through this function on every change:It passes no
extraOperators, so the dropdown offers the DEFAULT operator list — andisNull/isNotNullare in it. They are notOPT_IN_OPERATORS(onlycontainsCaseInsensitive,existsandnotExistsare), so Is null is an ordinary menu entry in this inspector.groupToConditionreturnsundefinedwhen no row survives. So an author who has a working dataset filter, opens the inspector and switches the single condition's operator to Is null, commitsundefined— the stored filter is erased, not merely left unchanged. Nothing errors. The panel still shows the condition.The cause
OP_TO_MONGOhas no row forisNull/isNotNull, and the two value-less operators the function DOES understand are matched as raw literals:That
continueis a deliberate decision fornotContains/between— operators this dialect genuinely cannot express, where dropping beats emitting a wrong filter.isNull/isNotNullare a different case: the Mongo dialect CAN express them (the spec's$null, and the file's own header names it), the builder draws them as COMPLETE rows with no value input, and the inspector offers them in its menu. So the drop is not the documented fallback, it is an unhandled operator falling into the fallback's path.conditionToGroupreportsrepresentable: falsefor shapes it cannot round-trip, so the caller can fall back to the source editor. The write direction has no equivalent signal — it drops silently.Relationship to objectui#9359 — same CLASS, different mechanism
objectui#9359 is a reader of the shared
VALUELESS_FILTER_BUILDER_OPERATORSthat forgot to fold the operator. This file does not read that set at all; it keeps its own raw literals and its own operator table. So it is not fixed by that card's repair, and not fixed by widening anything — a point worth keeping, because the two look alike from a distance.⛔ Independent of objectui#9306 (which operator vocabulary wins): every spelling in the table above is dropped, the dropdown's own ids included, so the defect does not turn on that decision.
Reproduce
Returns
undefined. The same group withoperator: 'isEmpty'returns{ closed_at: { $exists: false } }.Not measured, and worth a look by whoever takes this
onCommit(undefined)clears the persisted key or is filtered upstream — measured only at the function boundary here, not in a browser;startsWith,endsWith,notContains,between) in a way the "drop rather than emit a bad filter" comment no longer covers.Deduplicated before filing against the repository-scoped issue list endpoint (183 open issues over two pages), with objectui#9359 itself as the known-hit control so the empty result is a reading rather than a silent zero. Filed unassigned and unlabelled by an automated development seat working objectui#9359; session reference
session_01UzHd6hDYatoDn17BuwKxnZ.