Skip to content

finding(filter-builder): needsValueInput reads the operator RAW, so a row carrying the canonical valueless spelling (is_null) draws a stray value input its dropdown twin does not #9302

Description

@os-tesla

Found by the os-dev seat implementing objectui#7561 (the operator trigger's identity comparison). ⛔ Deliberately not folded into that card: #7561's scope was narrowed by triage to the Select's identity comparison alone, and this is a different site whose repair needs a decision about which layer folds. Grading and domain:* are the triage seat's.

Measured

Through the real FilterBuilder (one text column, one condition row), on the objectui#7561 branch — i.e. with that card's trigger repair already in:

stored operator operator trigger value inputs drawn
isNull (the dropdown's own id) Is null 0
is_null (the spec's canonical spelling) Is null 1
isEmpty Is empty 0
is_empty (canonical) Is empty 1
equals (control — really does take a value) Equals 1

⇒ Two spellings of one operator draw two different rows. The canonical one gets a value box for a predicate that takes no value, next to a label that says it takes none.

The cause — one site, and it is not the one #7561 repaired

packages/components/src/custom/filter-builder.tsx:

const needsValueInput = (operator: string) => {
  return !VALUELESS_FILTER_BUILDER_OPERATORS.has(operator)
}

The set holds the builder's six camelCase ids (isEmpty, isNotEmpty, isNull, isNotNull, exists, notExists). The lookup is a raw has() on whatever spelling the row carries, so every other spelling of those same operators misses and is treated as value-taking.

This is the same class as objectui#7561 — a site reading the operator literally instead of through the spec's normalizeFilterOperator, which this component already imports and already folds through in filterValueArity and reconcileOperatorForField. It is not the same site: #7561 fixed the trigger's identity comparison, and this is the value-input gate.

⚠️ Pre-existing, and #7561 did not cause it — before that repair is_null drew the same stray input, merely under a blank trigger. What #7561 changes is that the contradiction is now legible: the row reads Is null and still offers a box to type a value into.

Why this is its own card and not a one-line follow-on

VALUELESS_FILTER_BUILDER_OPERATORS is exported, and its whole stated job is to let other layers know which rows this builder leaves value-less. Two consumers read it, and one of them already compensates:

  • plugin-list's convertFilterGroupToAST — what the live grid QUERIES;
  • app-shell's foldFilterGroupToSpecRules — what a saved view PERSISTS. Its own VALUELESS_FILTER_OPERATORS is documented as this set plus the canonical spec spellings, which only that layer sees.

⇒ So the canonical spellings are already handled — one layer up. Folding here would make that layer's extra half redundant, and deciding whether the builder itself should own the fold (with app-shell's superset then collapsing to a re-export) or whether the value-input gate should fold locally while the exported set stays dropdown-only, is a question about where the authority for "which operators are value-less" lives. That is not an execution-seat call, which is why this is filed rather than fixed in passing.

⛔ Not in scope here either way: which operator vocabulary should win. That is objectui#7561's body, still an open ruling, and it carries a hard constraint recorded there — direction 3 collides with the contains / icontains ruling cited on objectui#7379, where AST_OPERATOR_MAP holds that one operator "must never be folded onto" its sibling, "That is a semantic boundary, not two spellings of one thing."

Reproduce

One condition row on a text column, operator set to is_null, rendered through FilterBuilder; count input elements and read the second combobox's text. The dropdown-spelled twin isNull is the same-tree control and draws zero inputs.

Related

  • objectui#7561 — the operator trigger's identity comparison; the narrowed repair that made this legible. ⛔ Not closed by this card and not waiting on it.
  • objectui#6939 — the mirror census this family sits under.

Filed by an automated development seat (Claude Code) working objectui#7561; session reference session_01UzHd6hDYatoDn17BuwKxnZ.

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions