You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Filed while implementing objectstack-ai/objectui#4023 (the FilterConditionField builder operator for $icontains). Measured against @objectstack/spec@17.0.0-rc.6 as pinned by objectui.
What is missing
FieldOperatorsSchema (data/filter.zod.ts) declares $icontains, and per its own docblock every driver and evaluation face the platform ships now executes it (#5702 + #6520). The spec's other two filter vocabularies do not have it:
Neither set has a case-insensitive contains, and neither has an alias that folds onto one.
Why that is a defect and not just an omission
The three vocabularies are three doors onto the same capability, and a filter authored at one door cannot always be expressed at another. Measured consequences in objectui, all on origin/main today:
packages/core/src/utils/filter-converter.ts — convertOperatorToAST('$icontains') returns null, so convertFiltersToAST throws FilterOperatorError (INVALID_FILTER, 400). Any MongoDB-style filter carrying the operator is refused before it reaches the wire.
packages/app-shell/src/views/viewFilterFold.ts — a saved view rule is refused by ViewFilterRuleSchema's enum.
So a capability that the engine executes on every face is reachable from exactly one of three authoring dialects. objectui#4023 shipped the operator as opt-in per consumer for this reason: only the widget that persists MongoDB-style criteria offers it, and the other FilterBuilder consumers deliberately do not, because there is nothing correct for them to store.
Mapping $icontains onto plain contains in the bridges is not the workaround: case sensitivity is part of the contract as of the 2026-08-06 ruling on #5701, so that is a different question silently answered.
Suggested shape (not a decision)
Add a canonical case-insensitive contains to VIEW_FILTER_OPERATORS (e.g. icontains / contains_ignore_case) with the alias folds, and the matching AST spelling in VALID_AST_OPERATORS, so the three dialects declare one capability rather than one-and-a-bit. The naming and whether the view layer should carry it at all are product decisions this issue does not take.
Provenance
Measured during objectstack-ai/objectui#4023. Not a regression — a gap that card exposed while wiring the operator into the filter UI.
Filed while implementing objectstack-ai/objectui#4023 (the FilterConditionField builder operator for
$icontains). Measured against@objectstack/spec@17.0.0-rc.6as pinned by objectui.What is missing
FieldOperatorsSchema(data/filter.zod.ts) declares$icontains, and per its own docblock every driver and evaluation face the platform ships now executes it (#5702 + #6520). The spec's other two filter vocabularies do not have it:Neither set has a case-insensitive contains, and neither has an alias that folds onto one.
Why that is a defect and not just an omission
The three vocabularies are three doors onto the same capability, and a filter authored at one door cannot always be expressed at another. Measured consequences in
objectui, all onorigin/maintoday:packages/core/src/utils/filter-converter.ts—convertOperatorToAST('$icontains')returnsnull, soconvertFiltersToASTthrowsFilterOperatorError(INVALID_FILTER, 400). Any MongoDB-style filter carrying the operator is refused before it reaches the wire.packages/plugin-list/src/ListView.tsx—mapOperatorhas no row, so the operator is emitted verbatim into the array AST. Per that repo's ownfilter-operator-ast-parity.test.ts, an operator outsideVALID_AST_OPERATORScosts either an unfiltered read or a 400 (see A filter with an operator outside VALID_AST_OPERATORS is silently dropped, not rejected — single-condition views return unfiltered results #3948) — either way not the filter the user asked for.packages/app-shell/src/views/viewFilterFold.ts— a saved view rule is refused byViewFilterRuleSchema's enum.So a capability that the engine executes on every face is reachable from exactly one of three authoring dialects. objectui#4023 shipped the operator as opt-in per consumer for this reason: only the widget that persists MongoDB-style criteria offers it, and the other FilterBuilder consumers deliberately do not, because there is nothing correct for them to store.
Mapping
$icontainsonto plaincontainsin the bridges is not the workaround: case sensitivity is part of the contract as of the 2026-08-06 ruling on #5701, so that is a different question silently answered.Suggested shape (not a decision)
Add a canonical case-insensitive contains to
VIEW_FILTER_OPERATORS(e.g.icontains/contains_ignore_case) with the alias folds, and the matching AST spelling inVALID_AST_OPERATORS, so the three dialects declare one capability rather than one-and-a-bit. The naming and whether the view layer should carry it at all are product decisions this issue does not take.Provenance
Measured during objectstack-ai/objectui#4023. Not a regression — a gap that card exposed while wiring the operator into the filter UI.