Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

FilterConditionField 无法生成 spec 新增的 $icontains(大小写不敏感 contains)—— 服务端已接受,筛选 UI 到不了 #3567

Closed

Description

@hotlong

升级 @objectstack/spec 到 17.0.0-rc.5(objectui#3560)时实测发现,不是该升级引入的破坏,是升级暴露出的能力缺口。

实测

FieldOperatorsSchema 在 rc.2 → rc.5 之间新增了 $icontains(spec 侧 describe:「folds ASCII case」,即大小写不敏感的 contains):

rc.2  $eq $ne $gt $gte $lt $lte $in $nin $between $contains $notContains $startsWith $endsWith $null $exists
rc.5  $eq $ne $gt $gte $lt $lte $in $nin $between $contains $notContains $startsWith $endsWith $icontains $null $exists
                                                                                       ^^^^^^^^^^

packages/fields/src/widgets/__tests__/FilterConditionField.operators.test.ts 的双向 parity 测试
「every spec field operator is reachable from the builder (#2942)」因此变红:

AssertionError: FieldOperatorsSchema accepts these but no builder operator can author them:
  expected [ '$icontains' ] to deeply equal []

即:服务端接受这个 token,而筛选构建器没有任何 operator 能生成它 —— 该能力从筛选 UI 完全不可达。

为什么不在 objectui#3560 里顺手补

补齐需要新增一个 builder operator(如 containsIgnoreCase)并带用户可见的标签,也就是十个 locale pack 各加一个 key(t() call-site key 闸门,objectui#3547)。这属于功能增量,依赖升级 PR 按 rider 纪律不做功能类适配。

objectui#3560 的处理是显式登记而非静默吞掉:把 $icontains 加进该测试的 KNOWN_UNREACHABLE 排除集(与 $eq/$between 同列,各自带理由),并新增一条棘轮断言每个被排除的 token 仍然是真实的 spec operator —— 排除项不得比它的理由活得更久。

本单要做的

  1. 在 FilterConditionField 的 builder operator 词表里增加一个大小写不敏感的 contains(命名待定,见下),condToMongo 生成 { field: { $icontains: value } };
  2. kvToCondition 反向读回(筛选器要能往返,否则存下来的视图打开就退化);
  3. 十个 locale pack 补标签 key;
  4. 删除 FilterConditionField.operators.test.ts 里的 $icontains 排除项 —— parity 断言随即接管,棘轮那条也会同时验证删除是正当的。

一个需要定夺的小决定

现有 contains 是否本来就该是大小写不敏感的?若产品意图是「用户输入的 contains 一律忽略大小写」,更好的做法可能是让现有 contains 改emit $icontains、另加一个 containsCaseSensitive 走 $contains —— 那是行为变更,影响存量视图,需要维护者确认。本单默认按「新增一个 operator、不动 contains 语义」执行,如需前者请在本单说明后再动工。

发现来源:objectui#3560(升级到 17.0.0-rc.5)的 out-of-scope finding,未认领。

Activity

  1. claude commented on Aug 7, 2026

    @claude
    Contributor

    Triage: transferred to the main backlog as objectstack-ai/objectstack#6337, and blocked. pm:blocked here; ⛔ no domain:* (sibling repos are whole-repo seats, routed by repo:* on the objectstack side).

    Blocked-by: objectstack-ai/objectstack#5702

    Why it moved

    The deliverable is a feature increment — a new builder operator plus ten locale-pack label keys, as the body itself states. Under the multi-repo coordination rule "One main backlog", feature-level issues are filed in objectstack whatever repo the code lands in, so scheduling has exactly one entry point. objectstack#6337 now carries it with pm:queue + pm:blocked + repo:objectui; the code still lands here, in this repo, in the implementing dev's own worktree. This issue stays open as the origin record — it is not a duplicate to close, and no work is lost.

    ⚠️ One premise correction, and it is the blocking one

    The body says the server already accepts $icontains. Measured against objectstack origin/main:

    So the spec vocabulary accepts the token while no driver can execute it. Landing the builder operator first would hand users a filter that every backend fails — strictly worse than the capability being absent. Hence blocked rather than queued: this returns to the queue when #5702 merges, via the unlock sweep.

    This does not diminish the finding. objectui#3560's handling was right on both counts: registering $icontains in KNOWN_UNREACHABLE with a stated reason instead of silently widening the exclusion, and adding the ratchet asserting every excluded token is still a real spec operator — an exclusion must not outlive its reason.

    The product question, recorded not lost

    Whether today's contains should have been case-insensitive all along — and therefore whether contains should emit $icontains with a new containsCaseSensitive for $contains — changes the behaviour of stored filter views, so it is the maintainer's call. It is carried onto #6337 with the body's own default (add a new operator, leave contains untouched) so the card is dispatchable without waiting. If the maintainer prefers the flip, that re-scopes #6337 into the decision inbox rather than stalling this one.

    本评论来自分诊座位 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#5702 — verified closed against the GitHub API on 2026-08-14. No other blocker is declared on this card.

    ⚠️ This card's Blocked-by: line lives in a comment, not the body. The unlock sweep greps the body, so this card was not discoverable by the automated sweep and was found only by a manual read of every pm:blocked card. Several seats recorded that they placed the line in a comment deliberately, to avoid the MCP body round-trip re-encoding the card's punctuation as HTML entities. That trade-off is sound per card and structurally lossy in aggregate: 26 of the 40 open pm:blocked cards across the three repos carry no body-level Blocked-by:. The durable fix belongs in the sweep (grep comments too), not in asking seats to risk body rewrites.

    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
    Contributor

    Claim: 批次 8(座位 repo:objectui,session session_01GTRjn8xBqp75dk7kFupVRt,batch:5)
    Branch: claude/issue-3567-icontains-operator
    File surface: packages/fields/src/widgets/FilterConditionField*(词表/condToMongo/kvToCondition)+ 十个 locale pack 各一 key + FilterConditionField.operators.test.ts(删 KNOWN_UNREACHABLE 排除项,parity 断言接管);⛔ 不碰 packages/components/src/custom/filter-builder.tsx(#3958 同批在飞,不同组件)。

    按卡内默认路线执行:新增大小写不敏感 operator(命名对齐仓内惯例,如 containsIgnoreCase),contains 语义不动;「现有 contains 是否该改 emit $icontains」的产品问题不在本单,如实施中发现强证据,回传报告不动代码。往返完整性(kvToCondition 读回)必须钉双向。


    Generated by Claude Code

  5. removed their assignment
    on Aug 16, 2026
  6. yinlianghui commented on Aug 16, 2026

    @yinlianghui
    Contributor

    【PM 关单】座位 repo:objectui(session_01GTRjn8xBqp75dk7kFupVRt):closed as completed —— 本卡四项交付物已全部落地:实施经迁移卡 #4023 → PR #4737(2026-08-15 合并,其正文明确「Part of #3567 — the card's four deliverables, implementing its recorded DEFAULT」)。本批派出的 dev 在最新 main 上零改动逐条核实:词表 containsCaseInsensitive + condToMongo 生成 $icontains、kvToCondition 读回、十个 locale pack key 齐、KNOWN_UNREACHABLE 排除项已删且 parity 接管;并以三次先预判后跑的变异证明既有钉子各自承重(报告在案)。「现有 contains 是否本该大小写不敏感」的产品问题未被触碰,维护者如要重裁另立卡。

    流程记账(本座位失误):批次 8 认领时未读本卡评论区的分诊迁移记录,closed-PR 查重只搜了实现未追迁移链,空派一名 dev(零改动收场,无害但浪费)。「认领前读全评论 + 查重含迁移目标卡」已并入座位常设承诺执行细则。


    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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions