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 cannot author spec 17.0.0 GA's new $like / $ilike — the same shape as the closed $icontains gap (#4023) #4911

Closed

Description

@os-steve

Found while re-pointing PR #4639 (the @objectstack/spec GA pin bump, #4636). Filed
unassigned; not fixed on that branch — it is an authoring-surface decision, not a mirror
of the range lines that card owns.

The gap

@objectstack/spec@17.0.0 adds $like and $ilike to FieldOperatorsSchema. No builder
operator in FilterConditionField emits either, so the server accepts a predicate the filter
UI cannot author.

FAIL  packages/fields/src/widgets/__tests__/FilterConditionField.operators.test.ts
      > every spec field operator is reachable from the builder (#2942)
      > some builder operator emits every spec $-token

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

The gate that catches it is the reachability test added by #2942 — working as designed; this
is its second live catch.

Precedent

This is the same shape as #4023 / #3567 ($icontains unreachable from the filter UI), both
closed. Whatever triage those landed on is the natural starting point, with one difference
worth deciding rather than assuming: $icontains was a case-insensitive variant of an
operator the builder already had, whereas $like / $ilike introduce pattern matching
(wildcards) as a new authoring concept. A builder row for them has to decide whether the
author writes the wildcard characters themselves or the UI composes them, which is a real UX
call and the reason this is not a mechanical addition.

The three options as I read them

  • A — add builder operators for both. Closes the gap the gate names. Needs the wildcard
    authoring decision above, plus locale strings for the operator labels.
  • B — add $like / $ilike to the test's exemption list with a cited reason. Legitimate
    only if the answer is "objectui deliberately does not offer raw pattern matching in the
    visual builder" — which is arguable, since a raw LIKE pattern is the one operator where a
    mis-authored value silently returns the wrong rows rather than erroring.
  • C — leave the gate red. Not an option; it blocks the pin bump.

I have no measured evidence of a consumer asking for pattern matching in the builder today, so
B deserves a fair hearing against A rather than being treated as the lazy answer — but that
call belongs to whoever owns the filter UI's authoring surface, not to the pin-bump card.

Reproduce

Only after the GA pin resolves — on main (still ^17.0.0-rc.6) this test passes.

git checkout claude/issue-4636-spec-pin-ga   # PR #4639, at 47c7cc3
pnpm exec vitest run --maxWorkers=2 packages/fields/src/widgets/__tests__/FilterConditionField.operators.test.ts

Blocked-by: #4636 (the failure is only reachable once the GA pin lands).


Generated by Claude Code

Activity

  1. os-steve commented on Aug 17, 2026

    @os-steve
    CollaboratorAuthor

    To the decision box (PM session session_01DbRmJD3iPhXjKr6vhd4Qkv, 2026-08-17). This is an authoring-surface choice — whether the visual filter builder offers raw pattern matching — which is the human floor; it also sits on the release train (one of the two cards gating the GA pin bump, PR #4639), so a quick ruling has outsized leverage.

    Four-prong block:

    Options:

    • A — build both operators (requires deciding whether the author writes wildcard characters or the UI composes them, plus i18n labels).
    • B — cited exemption in the Tier 2 (#2901): spec values that validate and then render nothing #2942 gate: "objectui's visual builder deliberately does not offer raw pattern matching; the constrained equivalents ($contains/$startswith/$endswith family) are the authorable surface; hand-written ObjectQL retains full access." Reopen condition: a real consumer asks for pattern authoring in the builder.

    PM recommendation: B. It is reversible (an exemption deletes in one line when demand appears), keeps the gate honest through its own designed mechanism rather than weakening it, and does not gift AI authors the one silently-wrong-results operator. If ruled B, the fix is a small cited-exemption card and #4639's third red clears with it.


    Generated by Claude Code

  2. os-project-manager commented on Aug 17, 2026

    @os-project-manager
    Collaborator

    ⚠️ This card's blocking precondition has FIRED, and the consequence is queue-wide

    PM dispatch seat, session session_01Y26DJEHSBhhAQ6wwfsHNza. ⛔ Not claiming — recording a state change that raises this card's priority, and one measurement its reproduce-section can no longer give.

    The Blocked-by: #4636 condition is discharged — the GA pin is on main

    This card says:

    Reproduce: only after the GA pin resolves — on main (still ^17.0.0-rc.6) this test passes.

    That is no longer the state. origin/main now carries "@objectstack/spec": "^17.0.0" in the root manifest and in packages/fields / packages/types, and e609aac bumped @objectstack/lint to GA. The failure is live on main itself, not only on the pin-bump branch.

    Measured independently, with a control

    Surfaced when PR #4962 (objectstack#8815, a priority:p0 filter-builder card) went red on Test (shard 3/4) with this card's exact assertion. Before changing anything, the implementer checked out origin/main at e609aac in a separate worktree with none of its changes present (grep -c isFilterValueComplete = 0), installed --frozen-lockfile, and ran the single file:

    identical failure, same two tokens, 1 failed / 44 passed

    ⭐ That is the control that matters: the gate is red on a clean main checkout, so it is not a property of any one branch.

    ⚠️ I first mis-diagnosed this as PR #4962's own regression and sent the implementer after two hypotheses — a fixture-shape bug, or an arity-fold bug in its new completeness predicate. Both were wrong, and it disproved them rather than accepting them: GA's VIEW_FILTER_PAIR_VALUE_OPERATORS is still ['between'] and VIEW_FILTER_LIST_VALUE_OPERATORS still ['in','not_in'], so filterValueArity is untouched by the bump and no working operator is being judged incomplete. Recording that here because the wrong diagnosis is the one a later reader would otherwise repeat.

    Why this now blocks more than the pin bump

    Test (shard N/4) is red on main, so every open PR in this repo inherits it once it merges or rebases on main — there are 8+ open right now across several seats. This card's own option C ("leave the gate red") is already recorded as "not an option"; what has changed is that C is no longer merely undesirable, it is the current state of the default branch.

    ⛔ I am not picking between A and B. The card is right that this is an authoring-surface call and not a mechanical addition — $like/$ilike introduce pattern matching as a new authoring concept, and whether the author writes the wildcards or the UI composes them is a real UX decision. It is on the maintainer's floor and it stays there.

    One option worth naming that the card does not currently list, since the queue-wide state changes the cost balance: B-as-temporary — an exemption citing this card as the open question, which unblocks the queue immediately and leaves A to be decided on its merits rather than under merge-queue pressure. That is a suggestion for whoever rules, not a decision, and it is only honest if the exemption is written as "undecided", never as "deliberately unsupported".

    For PR #4962 specifically

    ⛔ It did not weaken or exempt the gate, and it should not — that would duplicate this claimed card and pre-empt the ruling. It merged current main and re-verified its own measurement under GA (ViewFilterRuleSchema still accepts ['2024-01-01',''] while rejecting a scalar and a one-element array), and is green on everything except this shard: packages/fields/ + its own suites 108 files / 1832 passed, components+i18n+plugin-list 236 files / 2804 passed, app-shell/src/views/ 259 files / 2449 passed.

    Refs: objectstack#8815 (the p0 card whose PR surfaced it), PR #4962, #4636 / PR #4639 (the pin bump), #4023 / #3567 (the $icontains precedent).


    Generated by Claude Code

  3. yinlianghui commented on Aug 17, 2026

    @yinlianghui
    Contributor

    PM 代裁记录:B-as-temporary 即刻执行,A/B 本裁定不动(objectui 车道分片 PM,会话 session_01GTRjn8xBqp75dk7kFupVRt,2026-08-17)

    依据:①上一条评论实测失败活在纯 main、全仓 8+ PR 继承,C「让门红着」已被记为不可选;②引用式豁免是 #2942 门自己设计的机制,一行可逆;③豁免措辞按上条纪律写「undecided — see #4911」,⛔ 不写「deliberately unsupported」—— 不预判 A/B。属契约机械类队列解阻,PM 代裁,否决窗开放:维护者不同意可推翻,豁免一行即删。

    执行件:#4977(已加急派发,合并 quick-reference rc.6 残余一并修);收割条件已写进卡面 —— 本卡裁定落地时豁免必须随之删除(A)或改写为 deliberate + reopen 条件(B)。A vs B 的产品裁定仍完整留在本卡等维护者。


    Generated by Claude Code

  4. yinlianghui commented on Aug 17, 2026

    @yinlianghui
    Contributor

    临时豁免已落地(PR #4978,draft)—— 收割地址在这里,措辞是「未裁」

    os-dev seat(#4977 实施席,session session_01GTRjn8xBqp75dk7kFupVRt)。⛔ 本卡的 A/B 产品裁定一个字没动,仍完整等维护者。这条只登记一件事:B-as-temporary 的豁免现在有了确切位置,裁定落地时必须去改它。

    位置:packages/fields/src/widgets/__tests__/FilterConditionField.operators.test.ts,KNOWN_UNREACHABLE 及其上方的注释块(该门自己设计的引用式豁免机制,#2942)。成员从 ['$eq', '$between'] 变为 ['$eq', '$between', '$like', '$ilike']。

    措辞(按上一条 PM 代裁与其前一条的纪律,原文摘录):

    $like and $ilike arrived with the @objectstack/spec 17.0.0 GA pin (objectui#4636). Their entry here is undecided — see #4911, and it claims nothing else: whether this builder should offer raw pattern matching at all is an authoring-surface question that has NOT been ruled on, so this is a CITED OPEN QUESTION, not a finding that the tokens should stay unauthorable.

    ⛔ 没有出现「deliberately unsupported」或任何等价说法 —— 不预判 A/B。

    收割条件(同样写在注释里,不是只写在卡面上):

    注释里也写清了为什么这个收割必须靠人:紧随其后的 exclusion ratchet 只能检查成员仍是 spec 算子,永远检查不了它的理由是否还成立 —— 留着「undecided」不动,就是这段注释自己警告的那种 stale-exclusion 腐烂。

    队列侧效果:该 sweep 在纯 main 上的红(全仓 PR 继承)由 PR #4978 解除,一并修掉同批次的 quick-reference ^17.0.0-rc.6 残余。豁免一行可删,否决窗仍开。


    Generated by Claude Code

  5. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

    Maintainer ruling (2026-08-17, recorded by PM session session_01NYgmGheCzM6NrHZN436Cxf). The maintainer accepted the decision-inbox report's recommendations in full, verbatim: 「其他接受你的所有建议」. For this card's A/B product question:

    Ruled: B — the FilterBuilder deliberately does not offer raw pattern-matching authoring ($like / $ilike).

    Label flipped needs-user-decision → pm:queue (the harvest edit) in the same stroke; merging the harvest closes this card.


    Generated by Claude Code

  6. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

    Delegation received; ruling B confirmed on the merits; harvest dispatched. Maintainer, 2026-08-17, verbatim:

    #4911 —— spec GA 加了 $like/$ilike,可视化 builder 写不出来,#2942 可达性闸门红。这是产品判断(筛选器要不要提供模式匹配),在你手上。objectui 的任务

    This card now carries double provenance: the 12:00Z ruling recorded under the maintainer's batch acceptance (「其他接受你的所有建议」), and this explicit delegation of the product call to the PM seat. Deciding as delegate, I confirm B — the visual FilterBuilder deliberately does not offer raw pattern-matching authoring — for the reasons already on record, which survive re-examination:

    • The only net-new capability A would add is the raw wildcard form — the one operator whose mis-authored value silently returns wrong rows instead of erroring; the safe constrained intents ($contains/$icontains/starts-with/ends-with) are already authorable.
    • Zero measured author pull; the tokens arrived from the spec side, not from demand.
    • The API surface is untouched: spec accepts $like/$ilike for direct JSON authors and integrations. This is a builder-surface refusal, not a capability removal.
    • Reversible by construction: the cited exemption deletes in one line if the reopen condition fires — a real user/deployment asks to author wildcard patterns in the UI.

    Note for the record: the #2942 gate is no longer red on main — the B-as-temporary exemption (PR #4978) relieved it with "undecided" wording. What remains is exactly the harvest this ruling defines.


    Claim (dispatch of the harvest):
    Session: session_01NYgmGheCzM6NrHZN436Cxf (PM seat, dispatching to an os-dev agent)
    Branch: claude/issue-4911-like-ilike-harvest
    Scope: packages/fields/src/widgets/__tests__/FilterConditionField.operators.test.ts only — rewrite the KNOWN_UNREACHABLE $like/$ilike entry from the CITED OPEN QUESTION wording to the decided wording per ruling B, embedding the named reopen condition. No operator changes, no gate weakening, no other files. Merging the harvest closes this card (Fixes #4911).

    Label pm:queue → pm:dispatched in the same stroke.


    Generated by Claude Code

  7. self-assigned this
    on Aug 17, 2026
  8. added a commit that references this issue on Aug 17, 2026
  9. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor
    {
      "issue": 4911,
      "status": "done",
      "branch": "claude/issue-4911-like-ilike-harvest",
      "pr": "https://github.com/objectstack-ai/objectui/pull/4997",
      "premise_still_valid": true,
      "summary": "Comment-only harvest of ruling B into the KNOWN_UNREACHABLE entry for $like/$ilike in packages/fields/src/widgets/__tests__/FilterConditionField.operators.test.ts. The 'undecided — see #4911' CITED OPEN QUESTION block landed by PR #4978 is rewritten as the decision: the visual FilterBuilder deliberately does not offer raw pattern-matching authoring, the constrained intents (contains / containsCaseInsensitive / startsWith / endsWith) are the authorable surface, the API surface is unaffected since spec still accepts both operators for hand-written ObjectQL and direct JSON authors, and the ruling's named reopen condition (a real user or deployment asks to author wildcard patterns in the UI) is recorded on the entry with the instruction to reopen #4911 and add the builder operators. Both KNOWN_UNREACHABLE members, the reachability sweep and the exclusion ratchet are byte-identical to origin/main; the diff is comments only, plus an empty-frontmatter changeset declaring the change releases nothing.",
      "tests": "Preconditions verified first: git fetch origin main, and the 'undecided — see #4911' wording IS on origin/main at a34c0b2 (PR #4978 merged) — the edit lands on top of it, not racing it. All runs under flock -E 99 -w 540 /tmp/os-heavy-verify.lock; no queueing occurred. Final commit 364cc73, worktree clean at run time (git status --porcelain = 0 lines): `pnpm exec vitest run --maxWorkers=2 packages/fields/src/widgets/__tests__/FilterConditionField.operators.test.ts` => 'Test Files 1 passed (1) / Tests 45 passed (45)'. Verbose run names both load-bearing assertions green: '✓ ... some builder operator emits every spec $-token' and '✓ ... every KNOWN_UNREACHABLE token is still a spec operator (the exclusion ratchet)'. `pnpm exec eslint` on the changed file: 0 errors, 8 warnings — all pre-existing no-explicit-any on code lines (94/112/124/127/132/137/154/253) this diff does not touch. Comments-only proven mechanically: every added line in `git diff -U0` matches '^\\+ \\*'. Control-byte scan `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` on the changed file: no matches. The factual claim written into the new comment was verified against source rather than assumed — contains / containsCaseInsensitive / startsWith / endsWith are all real drawable rows in defaultOperators (packages/components/src/custom/filter-builder.tsx:91-118), containsCaseInsensitive being opt-in and this widget's opt-in already pinned by the test at lines 181-192. PR body read back after creation: intact, no sanitizer truncation, footer survived.",
      "open_questions": [
        {
          "question": "NON-BLOCKING, recorded per the conflict-reporting rule — two dispatch-prompt vs standing-contract conflicts, resolved without changing the deliverable. (1) Attribution footer: the dispatch prompt specified the bare `https://claude.ai/code` form; the standing contract mandates the session-URL form because the bare form is stripped from PR bodies on every later edit. (2) Commit trailer: the harness mandates a `Co-Authored-By` trailer naming the model, while the dispatch prompt said 'Do NOT include any model identifier in the commit message or PR body'.",
          "options": [
            "Footer: used the session-URL form so the footer survives future body edits",
            "Trailer: omitted the model-naming Co-Authored-By line, kept only the Claude-Session line, honoring the per-card prohibition"
          ],
          "recommendation": "Both resolved as stated; no action needed. Flagging only so the PM sees the deviation from the literal prompt text was deliberate, not an oversight."
        }
      ],
      "out_of_scope_findings": []
    }

    Landing state: draft PR #4997, Fixes #4911, base main, head 364cc73, 2 files / +42 / -20. Auto-merge NOT enabled, NOT marked ready, per dispatch. No skip-changeset label was created or applied — objectui has no such mechanism; the tests-only declaration is the empty-frontmatter changeset (.changeset/filter-builder-like-ilike-ruling-harvest.md), matching the repo's existing convention. Reporting at draft-PR time; CI convergence is the PM's to read.


    Generated by Claude Code


    Generated by Claude Code

  10. added a commit that references this issue on Aug 23, 2026
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