Skip to content

FilterBuilder:lookup 列的 options 是空数组时被拒掉远程搜索,只剩一个没有搜索框的空 Select #5031

Closed

Description

@yinlianghui

发现于 #4874(PR #5030)的实施,回传立单。不是 #4874 的一部分:那张卡问的是「值落在选项集外时看不见」,PR #5030 已经让这类值可见了;本单问的是这一列还能不能筛 —— 值可见了,用户依然挑不出别的记录。

事实

packages/components/src/custom/filter-builder.tsx(PR #5030 后的行号)renderValueInput 里那条远程 picker 分支的条件是:

if (isLookupLike && !field?.options && (field?.referenceTo || field?.type === "user" || field?.type === "owner"))

!field?.options 对 options: [] 是 false —— 空数组是真值。于是一个带 referenceTo、options 为 [] 的 lookup 列进不到 LookupValuePicker,落进下面按 options 画的分支:

两种形态下用户都无法为这一列选出任何新值,而同一列若 options 键干脆缺席就能拿到完整的远程搜索。实测(jsdom,origin/main e71c854 + PR #5030 分支):字段 { value: 'account_pending', type: 'lookup', referenceTo: 'accounts', options: [] },值 trigger 只有行里那个值,展开后无候选、无搜索框。

可达性

@object-ui/fields 的 deriveFilterFields(packages/fields/src/widgets/FilterConditionField.tsx:104)是 Array.isArray(f.options) ? f.options.map(…) : undefined,plugin-view 的 deriveFieldOptions(packages/plugin-view/src/config/view-config-utils.ts:279)是 options: field.options。两处都原样透传,所以只要对象元数据里某个引用字段声明了 options: [],这一列的筛选就退化成上面的样子。没有哪个消费方用 ?? [] 兜底,因此不是「每个 lookup 列都中」,但也不需要任何异常状态就能触发。

修法方向(仅供 triage,未实施)

PR #5030 已经为「静态选项集是否真的在位」建了唯一判据 hasStaticOptionDomain(field)(= options 是非空数组),并且正是按「options: [] 属于远程/未到位」这一侧裁定的值域行为。这条分支条件把同一个问题按 !field?.options 又答了一遍,两个答案对 options: [] 相反:值域侧当它是远程列(保值),控件侧当它是静态列(画空 Select)。让分支条件读同一个判据即可一致。

改的是公共控件对一整类列画哪个控件,属于用户可见行为变更 —— #4874 就是因为这类变更被升级到维护者裁定的,建议同样处理,别自行拍。

邻接观察(另一件事,不在本单修法内)

plugin-view 的 deriveFieldOptions 根本不传 referenceTo,所以经它构造的筛选面板里,lookup 列即使 options 缺席也拿不到远程 picker(分支的第二个条件不满足),会退到手打 id 的文本框。那是「控件降级得看得见」,没有隐形值,与本单不同源,若要处理请另开。

Activity

  1. added theissue type on Aug 17, 2026
  2. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

    Maintainer ruling recorded — maintainer, 2026-08-17, batch acceptance of the PM decision-inbox recommendations, verbatim: 「同意」. Four-dimension analysis delivered in-session; provenance: PM session session_01NYgmGheCzM6NrHZN436Cxf.

    Ruled: unify on the adjudicated predicate. The remote-picker branch condition reads hasStaticOptionDomain(field) (PR #5030's single criterion: options is a non-empty array) instead of !field?.options, so options: [] routes to LookupValuePicker exactly as the value-domain side already treats it. Pin: a referenceTo field with options: [] gets the remote picker; a non-empty options field keeps the static Select.

    The adjacent observation (plugin-view's deriveFieldOptions not passing referenceTo) stays out of scope per the card — file separately if wanted.

    Label needs-user-decision → pm:queue in the same stroke.


    Generated by Claude Code

  3. os-zhuang commented on Aug 18, 2026

    @os-zhuang
    Contributor

    Claim: PM seat dispatch (post-ruling)
    Session: session_01NYgmGheCzM6NrHZN436Cxf
    Branch: claude/issue-5031-lookup-empty-options
    Scope: per the ruling above — the remote-picker branch reads hasStaticOptionDomain(field) (PR #5030's adjudicated predicate) in place of !field?.options; pins for options: [] ⇒ remote picker and non-empty options ⇒ static Select. Nothing else; the deriveFieldOptions/referenceTo observation stays out of scope.
    Routing authority: maintainer ruling, 2026-08-17 「同意」 batch (comment above).

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


    Generated by Claude Code

  4. self-assigned this
    on Aug 18, 2026
  5. added a commit that references this issue on Aug 18, 2026
    cd91efd
  6. os-zhuang commented on Aug 18, 2026

    @os-zhuang
    Contributor
    {
      "issue": 5031,
      "status": "done",
      "branch": "claude/issue-5031-lookup-empty-options",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5107",
      "premise_still_valid": true,
      "summary": "Verified the premise on origin/main (405d54e): PR #5030 is landed and the remote-picker branch in packages/components/src/custom/filter-builder.tsx still read `!field?.options`, which is false for `options: []`. Replaced that one conjunct with PR #5030's adjudicated predicate `hasStaticOptionDomain(field)`, so a `referenceTo` (or user/owner) lookup column with an empty options array now reaches LookupValuePicker exactly as the value-domain side already treats it, while a column with non-empty options keeps its static Select. The branch's other conjuncts are unchanged, so a lookup with nothing to search against routes as before. Also corrected the two comment spans in the same file that asserted the old routing, and re-pointed the two assertions in PR #5030's suite that pinned `account_pending` rendering through a temporary SelectItem — adding a new case on `stage` so that SelectItem path stays covered on a column that genuinely owns its option domain.",
      "tests": "All at cd91efd, the final commit (git rev-parse --short HEAD), tree clean. Heavy runs serialized under flock /tmp/os-heavy-verify.lock. Build closure first: `pnpm --filter '@object-ui/components^...' build` EXIT=0. New + amended suites: `pnpm exec vitest run packages/components/src/__tests__/filter-builder-lookup-empty-options.test.tsx packages/components/src/__tests__/filter-builder-value-option-domain.test.tsx` -> 'Test Files 2 passed (2) / Tests 37 passed (37)'. Full package: `pnpm exec vitest run packages/components/` -> 'Test Files 155 passed (155) / Tests 1428 passed (1428)'. `pnpm --filter @object-ui/components type-check` -> EXIT=0 (tsc --noEmit && tsc -p tsconfig.test.json). `pnpm --filter @object-ui/components lint` -> '868 problems (0 errors, 868 warnings)', EXIT=0, warning baseline unchanged. DOWNSTREAM consumer sweep (consumers of @object-ui/components that render the builder: packages/fields widgets, packages/plugin-view config, packages/app-shell views) -> 'Test Files 284 passed (284) / Tests 3028 passed | 1 skipped'. Repo gates: check-changeset-presence.mjs ('3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'), check-changeset-no-major.mjs, check-changeset-fixed.mjs, check-control-bytes.mjs ('scanned 4533 tracked text file(s)') all green; manual control-byte grep over the 4 changed files found none. REVERSE VERIFICATION from the committed state (restore `!field?.options`, re-run, then `git checkout <branch> -- <file>`): predicted red on the new pins and green on the guards, observed exactly that -> 'Tests 9 failed | 28 passed (37)' — red were all 7 pins under 'routes an unloaded lookup column to the remote picker' plus the 2 re-pointed visibility assertions; every blast-radius guard stayed green. CI on PR #5107 is in_progress at report time per the dispatch contract. Note: the repo's own vitest guard rejected `pnpm --filter <pkg> test -- <paths>` (objectui#3288 — args after `--` are silently dropped); all runs used the prescribed root-level `pnpm exec vitest run <path>` form.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Notes for the PM (not part of the JSON):

    • No new issues filed. The card's own adjacent observation (plugin-view's deriveFieldOptions not passing referenceTo) is explicitly out of scope per the ruling and is already recorded in this issue's body; I did not file it separately since the ruling says "file separately if wanted" — that call is yours.
    • skip-changeset was not used or created: this repo declares via a changeset file, and a real one is included (.changeset/filter-builder-lookup-empty-options.md, @object-ui/components: patch).
    • PR is draft, no auto-merge, no ready flip.

    Generated by Claude Code

  7. added a commit that references this issue on Aug 23, 2026
    93de4f6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions