Skip to content

finding(app-shell): four MORE offered operators erase the stored dataset filter the same way isNull did — and the file's "cannot express" comment is stale for all four #9372

Description

@os-tesla

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:

  1. DatasetFilterField commits onPatch({ filter: fc }) / patchMeasure(i, { filter: fc }).
  2. The host applies patches as { ...draft, ...patch } ⇒ the key is SET to undefined, not omitted.
  3. 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.
  4. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpackage: app-shellpriority:p1

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions