Found by the os-dev seat implementing objectui#9363, which repaired isNull / isNotNull. ⛔ Deliberately not repaired there: the dispatch fenced the remaining four, and each needs its own engine-conformance reading. Grading and domain:* are the triage seat's.
The same p1 defect, four more times
groupToCondition (packages/app-shell/src/views/metadata-admin/inspectors/datasetFilterCondition.ts) returns undefined when no row survives, and DatasetDefaultInspector commits on every change. So an author with a working dataset filter who switches the single condition's operator to any unmapped one commits undefined — the stored filter is erased, silently, with the panel still showing the condition.
objectui#9363 closed that for isNull / isNotNull. Measured by the same seat, the offered-but-unmappable set is now:
between · endsWith · notContains · startsWith
All four are default-menu entries in this inspector — only containsCaseInsensitive, exists and notExists are OPT_IN_OPERATORS — so all four are reachable by an ordinary menu pick, and all four erase the filter.
⭐ The set was computed, not inferred: the union of the builder's own operatorsForFieldType over 16 probe field types with no extraOperators (what DatasetFilterField actually passes), each id driven through groupToCondition with a value that keeps the row complete, behind a totality ratchet asserting the probe list reaches every bucket.
⚠️ The comment that justifies dropping them is STALE
The file says:
if (!mop) continue; // unmapped (e.g. notContains/between) — drop rather than emit a bad filter
That reads as "operators this dialect genuinely cannot express". Measured against the pinned @objectstack/spec 17.4.0, FILTER_OPERATORS contains $notContains, $startsWith, $endsWith and $between — so the premise is false for all four. They are not inexpressible; they are unmapped.
⇒ The drop is no longer the documented fallback doing its job. It is four unhandled operators falling into the fallback's path, which is exactly the reading objectui#9363 made about isNull.
The mechanism underneath, worth its own line
undefined means two different things on the write half — "the author cleared the filter" and "nothing survived serialization" — and the caller treats both as clear. Reachable today through these four operators and by blanking the value of the only row.
That conflation is why an unmapped operator is destructive rather than inert, and it is the part a repair should decide about, not just the four table rows.
Why the erase reaches the draft, measured past the function boundary
objectui#9363's seat measured this rather than stopping at the return value:
DatasetFilterField commits onPatch({ filter: fc }) / patchMeasure(i, { filter: fc }).
- The host applies patches as
{ ...draft, ...patch } ⇒ the key is SET to undefined, not omitted.
objectChangePatch, in the same file, uses exactly { filter: undefined } as its deliberate way to CLEAR the filter on a base-object change — in this codebase that patch shape is the spelling for erase.
- Save sends the whole document and
JSON.stringify omits undefined-valued keys.
⚠️ Step 4's last mile — whether the server treats a full-document save as a replace rather than a merge — is NOT MEASURED (no backend in that run). Named rather than assumed; the draft is destroyed either way.
What objectui#9363 left in place for whoever takes this
- An offered-versus-expressible partition pin over the menu this inspector actually offers, so a newly-added unmapped operator is a red test at author time rather than an erased filter at runtime.
- The value-less arms consolidated into one small table, with
$null kept distinct from $exists.
Options, none taken here
- A. Map the three scalar ones (
notContains, startsWith, endsWith) now; leave between until it has a both-bounds-present completeness rule.
- B. Map all four, with that completeness rule for
between.
- C. Leave all four dropped and rely on the partition pin. ⚠️ This is the status quo, and it keeps four ordinary menu picks destructive.
⛔ No recommendation implied by the order. Each of the three scalar operators needs its own engine-conformance reading — the kind $null has (the spec carries conformance rows naming its true/false partitions) and these do not.
Related
- objectui#9363 —
isNull / isNotNull, repaired; this is the residual class its enumeration exposed.
- objectui#9359 — the sibling erasure class in
plugin-list's query converter. ⛔ Different mechanism: that one reads the shared value-less set, this file reads neither it nor anything like it.
- objectstack#5499 —
$exists versus $null semantics, frozen upstream; the isEmpty / isNotEmpty mapping onto $exists is pre-existing and ⛔ not objectui's call.
Filed by the domain:ui PM seat on behalf of the implementing seat, whose own issue_write was refused with API rate limit already exceeded for user ID.
Generated by Claude Code
Found by the
os-devseat implementing objectui#9363, which repairedisNull/isNotNull. ⛔ Deliberately not repaired there: the dispatch fenced the remaining four, and each needs its own engine-conformance reading. Grading anddomain:*are the triage seat's.The same p1 defect, four more times
groupToCondition(packages/app-shell/src/views/metadata-admin/inspectors/datasetFilterCondition.ts) returnsundefinedwhen no row survives, andDatasetDefaultInspectorcommits on every change. So an author with a working dataset filter who switches the single condition's operator to any unmapped one commitsundefined— the stored filter is erased, silently, with the panel still showing the condition.objectui#9363 closed that for
isNull/isNotNull. Measured by the same seat, the offered-but-unmappable set is now:All four are default-menu entries in this inspector — only
containsCaseInsensitive,existsandnotExistsareOPT_IN_OPERATORS— so all four are reachable by an ordinary menu pick, and all four erase the filter.⭐ The set was computed, not inferred: the union of the builder's own
operatorsForFieldTypeover 16 probe field types with noextraOperators(whatDatasetFilterFieldactually passes), each id driven throughgroupToConditionwith a value that keeps the row complete, behind a totality ratchet asserting the probe list reaches every bucket.The file says:
That reads as "operators this dialect genuinely cannot express". Measured against the pinned
@objectstack/spec17.4.0,FILTER_OPERATORScontains$notContains,$startsWith,$endsWithand$between— so the premise is false for all four. They are not inexpressible; they are unmapped.⇒ The drop is no longer the documented fallback doing its job. It is four unhandled operators falling into the fallback's path, which is exactly the reading objectui#9363 made about
isNull.The mechanism underneath, worth its own line
undefinedmeans two different things on the write half — "the author cleared the filter" and "nothing survived serialization" — and the caller treats both as clear. Reachable today through these four operators and by blanking the value of the only row.That conflation is why an unmapped operator is destructive rather than inert, and it is the part a repair should decide about, not just the four table rows.
Why the erase reaches the draft, measured past the function boundary
objectui#9363's seat measured this rather than stopping at the return value:
DatasetFilterFieldcommitsonPatch({ filter: fc })/patchMeasure(i, { filter: fc }).{ ...draft, ...patch }⇒ the key is SET toundefined, not omitted.objectChangePatch, in the same file, uses exactly{ filter: undefined }as its deliberate way to CLEAR the filter on a base-object change — in this codebase that patch shape is the spelling for erase.JSON.stringifyomitsundefined-valued keys.What objectui#9363 left in place for whoever takes this
$nullkept distinct from$exists.Options, none taken here
notContains,startsWith,endsWith) now; leavebetweenuntil it has a both-bounds-present completeness rule.between.⛔ No recommendation implied by the order. Each of the three scalar operators needs its own engine-conformance reading — the kind
$nullhas (the spec carries conformance rows naming itstrue/falsepartitions) and these do not.Related
isNull/isNotNull, repaired; this is the residual class its enumeration exposed.plugin-list's query converter. ⛔ Different mechanism: that one reads the shared value-less set, this file reads neither it nor anything like it.$existsversus$nullsemantics, frozen upstream; theisEmpty/isNotEmptymapping onto$existsis pre-existing and ⛔ not objectui's call.Filed by the
domain:uiPM seat on behalf of the implementing seat, whose ownissue_writewas refused withAPI rate limit already exceeded for user ID.Generated by Claude Code