Skip to content

Lookup picker: the subtitle repeats the record name when the referenced object's name field is not name — every AI-built lookup #25860

Closed

Description

@objectstack-fleet

Filing gate ① a reproduced UX defect (class a, public door: a lookup picker on a record form). reach: local stack, objectui pin 6f629a7 (cloud main 36b07fdf), zh-CN, 2026-10-25. In an AI-built app, the 关联客户 picker on 新建家装项目 lists each customer as the title 「周明轩」 over a subtitle that repeats it: 「客户姓名: 周… · 联系电话: 1862457… · 客户来源: 门店」.

Batch-of: 6 · 2026-10-25 desktop E2E, second pass (magic-flow UX, reviewed in-session). Filed at the maintainer's request: 「必要的合理的建议都可以立卡」 / 「体验问题 也请fable 审核是否需要立卡」.

Business reason. The subtitle exists to tell similar records apart. Its first, and widest, slot is spent on the name the title already shows, which pushes the phone number (the field that actually disambiguates two 王静 records) into truncation. This happens on every lookup to an object whose name field is not literally name, which is every AI-built app.

Reader who acts. The repo:objectui ui seat: packages/fields/src/widgets/LookupField.tsx. objectui-only: AI-built lookups declare no displayField / descriptionField, so no metadata change is needed.

Where (objectui pin 6f629a7)

  • LookupField.tsx:376 displayField = declaredDisplayField || DEFAULT_DISPLAY_FIELD ('name').
  • The option title resolves through getRecordDisplayName → objectDef.nameField (core/src/utils/record-title.ts:26-31), here customer_name.
  • previewColumns (:585-599) seeds seen with displayField ('name') only. deriveLookupColumns.ts:86 leads with that absent name, so customer_name becomes the first subtitle item.
  • objectui#10343 (closed) fixed the title ladder, not the subtitle's seen set.

Expected

seen also holds the field the title actually resolved from (objectDef.nameField, or whatever getRecordDisplayName used), so the subtitle starts with 联系电话.

Dedupe

gh api search/issues on objectui, incl. closed:

  • lookup descriptionField repeats name: 0.
  • lookup picker subtitle display field nameField: 1, a seat card.
  • DEFAULT_DISPLAY_FIELD nameField lookup: 1. objectui#10343 (closed) is the other half (the title).

Activity

  1. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Transferred 2026-10-26T01:52Z from objectstack-ai/objectui#13542, with its comments and timeline; the old URL redirects here. objectui moved into this repository as the ui/ workspace (epic #25677) and is being archived (#25854). The body is left exactly as filed.

    Paths. Read every repository path in the body with a ui/ prefix: packages/fields/src/widgets/LookupField.tsx is now ui/packages/fields/src/widgets/LookupField.tsx.

    No stale pm:dispatched or assignee to strip.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first touch — bug · priority:p3 · domain:ui · area:records · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-26T06:58Z

    ⛔ Not a claim, ⛔ not a dispatch. First touch after the transfer from objectui. The card had no triage audit comment there or here, which is what the domain:ui seat (#25966) needs before it can pick it.

    • p3, as filed: the picker's subtitle spends its first slot on the name the title already shows. It is a UX cost and nothing is wrong in the data. The fix: skip the object's display (name) field in the subtitle, whatever that field is called. Paths are under ui/.

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-26T08:46Z
    Session: session_01SMps7wq7rBTk53e8KTSEup
    Account: os-steve
    Branch: claude/issue-25860-lookup-subtitle-name-field
    Worktree: objectstack-issue-25860
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: ui/packages/fields/src/widgets/LookupField.tsx (the previewColumns memo and its imports only), a new or existing LookupField test beside it; scripts/sdui-manifest.record.json (re-record); root .changeset/25860-lookup-subtitle-name-field.md (stop on breach; explain in the report). ⛔ ui/.github/** and the root .github/workflows/ci.yml are out of bounds (maintainer's word). ⛔ ui/packages/fields/src/index.tsx is held by #25897 / #25937.
    Container & model: S, mode:subagent, model: default tier (dispatch-gates --tier --repo objectstack-ai/objectstack: no path-derived mandate), effort: medium
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the lookup's own preview list, which skips only the configured display field | the record-title resolver already knows the object's real name field | every lookup to an object whose name field is not literally name, in the picker subtitle
    Thread-read: 6410157597
    Premise: holds 2026-10-26T08:42Z — on origin/main 6a6ebff97f LookupField.tsx previewColumns seeds seen with displayField only, and @object-ui/core exports resolveNameField (utils/record-title.ts:366)
    Serial constraints cleared: none — no open claim holds LookupField.tsx.
    Writes: PR only via the relay op pr_create; comments only via the relay write tools; ⛔ no GitHub MCP writes.

    • Ruling section: triage 6410157597.
    • Suggested route: also skip the referenced object's resolved name field when building previewColumns (seen.add(resolveNameField(refObjectSchema)), guarded for undefined); test a lookup whose target's name field is title and confirm the subtitle no longer repeats it.
  4. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 25860,
      "status": "done",
      "branch": "claude/issue-25860-lookup-subtitle-name-field",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/26062",
      "session": "session_01SMps7wq7rBTk53e8KTSEup",
      "premise_still_valid": true,
      "summary": "Measured premise on origin/main 6a6ebff97f: previewColumns in ui/packages/fields/src/widgets/LookupField.tsx seeded seen with displayField only (the 'name' guess when undeclared), while recordToOption titles the option through getRecordDisplayName (declared nameField, else titleFormat, else derived title field); resolveNameField is exported from @object-ui/core. Fix: when the lookup declares no displayField, previewColumns also adds resolveNameField(refObjectSchema) to seen, except when a titleFormat (and no declared name field) titles the record; deps gain declaredDisplayField and refObjectSchema, imports gain declaredNameField and resolveNameField. A declared displayField and an authored descriptionField behave as before. scripts/sdui-manifest.record.json re-recorded on the merged tree (registry digest only; sdui.manifest.json byte-unchanged); patch changeset .changeset/25860-lookup-subtitle-name-field.md for @object-ui/fields. Card assignee untouched; PR assignee os-steve.",
      "tests": "New ui/packages/fields/src/widgets/LookupField.subtitleNameField-25860.test.tsx (5 cases: declared nameField 'title' -> subtitle [phone, source]; derived 'title' -> same; authored descriptionField leads [source, phone]; controls: declared displayField keeps 'title' in subtitle; titleFormat-only keeps derived 'title'). All under os-verify-lock: (1) closure build turbo run build --filter='@object-ui/fields^...' VERDICT command-exit 0 (46/46 tasks); (2) cd ui && pnpm exec vitest run --maxWorkers=2 packages/fields/ at a200f71d: Test Files 278 passed | 1 skipped (279), Tests 4295 passed | 7 skipped (4302), VERDICT command-exit 0 (merge brought no change under ui/packages/fields, core or types); (3) at dcee0130 after merging origin/main: new test + labelOrder-10343 + optionDescription + previewBadges-13123: Test Files 4 passed (4), Tests 21 passed (21), VERDICT command-exit 0; (4) pnpm --filter @object-ui/fields type-check (tsc --noEmit && tsc -p tsconfig.test.json, which includes src/**/*.test.tsx) TC_EXIT=0. Ablation (fix committed first, test imports source, no dist): node scripts/ablation-replace.mjs replaced 'if (nameField) seen.add(nameField);' (anchor 1 -> 0, blob 04c7cbb215e3 -> b6be553ded33) -> Tests 3 failed | 2 passed (5), the 3 behaviour cases red, both controls green; restored blob == HEAD 04c7cbb215e3 and git diff HEAD empty.",
      "gates": "Local at dcee0130: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 46 commands; all 46 exit 0 (exit captured per command before any pipe), incl. node scripts/check-sdui-manifest.mjs (present, intact, registry sha256 d71fa5f15902), pnpm check:nul-bytes, pnpm check:type-check-coverage, check-empty-changeset, check-changeset-no-major, check-adr-0087-registration. --ran reconciliation with exit codes: 46 derived, 46 run, 0 NOT-MEASURED (a DERIVED zero). CI on PR #26062 at dcee0130: in_progress at report time (Type Check, Lint lanes, Check Changeset, Governed Surface Queue Guard, card-claim checks); 'No other open PR may claim the same issue' success. Not waited on per contract.",
      "dogfood_battery": "n/a — no  in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "2 — (1) fleet-write relay POST /repos/objectstack-ai/objectstack/dispatches executing pr_create (POST /pulls + POST /issues/26062/assignees), read back identical 5454 bytes, assignees os-steve; (2) this os-dev-report comment via scripts/pm/post-stamped.mjs (POST /issues/25860/comments). git push not counted.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none — noted in PR Acceptance notes, not filed: effectiveDescriptionField still falls back to the first non-displayField picker column, which for an undeclared lookup can be the name field; it only feeds the option's description (popover text match), not the rendered subtitle; outside this card's file surface; no user-visible effect measured."
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #26062 comment 6414061234 · seat 2 · 2026-10-26T10:52Z

  6. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · seat 2 · 2026-10-26T15:25Z

    probe reading
    PR #26062 MERGED 2026-10-26T14:58Z, merge commit dfaad4da, head dadc26bd (fields re-record after #26120)
    origin/main at 7111e14a, contains dfaad4da (git merge-base --is-ancestor exit 0)
    queue branch gh-readonly-queue/main/pr-26062-*: none left (consumed)
    card closed (completed) by Fixes #25860 on #26062

    Status: pm:dispatched removed; the card is done. Nothing remains.

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

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:uipriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions