Skip to content

[finding] The console filter builder mints a SCALAR value for the in / notIn operators, which the spec now refuses (objectstack#6227) #3958

Closed

Description

@os-zhuang

Blocked-by: objectstack-ai/objectstack#6227

Out-of-scope observation from implementing objectstack#6227 (which constrains ViewFilterRuleSchema.value by operator). Unassigned, not queued. Duplicate search: no open issue in this repo matches "filter builder operator value array".

Fact

FilterBuilder (packages/components/src/custom/filter-builder.tsx) can emit a filter row whose operator is in / notIn while its value is a scalar, by three steps that are each individually reasonable:

  1. addCondition() (:226) seeds every new row as { operator: "equals", value: "" }.
  2. Changing the operator dropdown calls updateCondition(condition.id, { operator: value }) (:493) — it does not reset value, so the scalar "" survives the switch to in.
  3. renderValueInput() (:306) only renders a multi-select widget when field?.options && isMultiOperator (:324) or for lookup-like fields (:312). A plain text/number field with no static options therefore falls through to the single-value <Input> regardless of operator, so whatever the user types stays a scalar.

foldFilterGroupToSpecRules (packages/app-shell/src/views/viewFilterFold.ts:106-126) then carries value through verbatim — it normalizes the operator but never the value shape:

if (c.value !== undefined) rule.value = c.value as ViewFilterRule['value'];

Why it matters now

This shape was already broken end-to-end before objectstack#6227 — it saved cleanly and then answered 400 INVALID_FILTER when the view was rendered (objectstack#5869). What changes is when the user finds out: after objectstack#6227 the spec schema refuses it at save time, so the console will surface a validation refusal on a row its own UI led the user into building.

That is a strict improvement in timing and a regression in felt UX unless the builder stops minting the shape: the user picks a legitimate operator from the dropdown the builder offered, types a value into the input the builder rendered, and is refused.

Secondary observation — one spelling adrift

isMultiOperator is decided from a local literal (:308):

const isMultiOperator = ["in", "notIn"].includes(condition.operator)

notIn is an alias in the spec vocabulary, not the canonical member — the canonical spelling is not_in, and normalizeFilterOperator folds one to the other. Any code path that hands this component a canonical not_in (for example a stored view read back) gets isMultiOperator === false and renders the single-value input for a set operator. objectstack#6227 exports VIEW_FILTER_LIST_VALUE_OPERATORS and VIEW_FILTER_PAIR_VALUE_OPERATORS from @objectstack/spec precisely so this list stops being a second local dialect.

Suggested direction (not pre-judged)

  • Reset / re-shape value when the operator changes between scalar-valued and list-valued families.
  • Render a multi-value input for in / not_in even when the field has no static options (a token/tag input), and a two-bound input for between.
  • Decide the operator families from the spec's exported vocabularies rather than a local literal.

Refs: objectstack#6227, objectstack#5869.


Generated by Claude Code


Generated by Claude Code

Activity

  1. os-zhuang commented on Aug 9, 2026

    @os-zhuang
    ContributorAuthor

    Triage: pm:blocked, Blocked-by: objectstack-ai/objectstack#6227 added to the body (defect-class card, deliberately not queued yet).

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. claude commented on Aug 14, 2026

    @claude
    Contributor

    Unlock sweep: blocker cleared. pm:blocked → pm:queue.

    Declared blocker: objectstack-ai/objectstack#6227 — verified closed against the GitHub API on 2026-08-14. No other blocker is declared on this card.

    Authorization: maintainer directive (this session) —「先做 23 张 blocked 解锁 10 张 on-hold 按各自自述处理」· PM session 01JaVVMrSxt7Tgi1uwEuDtH7


    Generated by Claude Code

  3. self-assigned this
    on Aug 16, 2026
  4. yinlianghui commented on Aug 16, 2026

    @yinlianghui
    Collaborator

    Claim: 批次 8(座位 repo:objectui,session session_01GTRjn8xBqp75dk7kFupVRt,batch:5)。解锁核验:Blocked-by: objectstack#6227 已于 2026-08-09 关闭(spec 侧按算子约束 value 形状 + 导出 VIEW_FILTER_LIST_VALUE_OPERATORS/VIEW_FILTER_PAIR_VALUE_OPERATORS 已落地);本卡自身范围 closed-PR 搜索无已交付实现。
    Branch: claude/issue-3958-filter-builder-set-operators
    File surface: packages/components/src/custom/filter-builder.tsx + 其测试;app-shell/src/views/viewFilterFold.ts 预计只读核验不改;⛔ 不碰 renderers/form/form.tsx(#4085 同批在飞)、不碰 packages/fields 的 FilterConditionField(#3567 同批在飞,另一组件)。

    方向裁定(契约机械,三条全做):①operator 在标量/列表/区间家族间切换时重整 value 形状(不静默携带旧标量);②in/not_in 在无静态 options 的字段上渲染多值 token 输入,between 渲染二元输入 —— builder 不再产出 spec 拒绝的形状;③家族判定改读 spec 导出词表,删除本地 ["in","notIn"] 字面量(canonical not_in 读回也必须命中多值输入,别名归一钉双向)。


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions