Skip to content

finding(plugin-list): ListView's declared-columns branch reads reference_to and reference off a list COLUMN, and ListColumnSchema declares neither #7531

Description

@claude

Ruled: 5809501335 · letter remove (the fallback branch stops reading undeclared column keys) · 2026-09-24T07:11Z

Surfaced while implementing objectui#6837 half 2 (the reference_to arm deletion), and deliberately left out of that PR: it is a contract-shape question, not part of the arm deletion.

What was measured

packages/plugin-list/src/ListView.tsx builds its filter/sort field candidates in two branches. The objectDef.fields branch reads an OBJECT SCHEMA field def, where reference is the declared spelling; objectui#6837 half 2 narrowed it. The sibling branch — the !objectDef?.fields fallback that reads the view's own declared schema.columns — still reads f.reference_to || f.reference, and was deliberately NOT narrowed, because a list-view COLUMN is a different contract.

Measured against @objectstack/spec 17.2.0's ListColumnSchema, on the installed package:

  • { field: 'acct' } parses clean (positive control).
  • { field: 'acct', reference: 'account' } is REFUSED with unrecognized_keys.
  • { field: 'acct', reference_to: 'account' } is REFUSED with unrecognized_keys.
  • { field: 'acct', zzz_nonsense: 1 } is REFUSED identically (negative control).

⭐ The tell is what is ABSENT. On FieldSchema both legacy spellings come back with their own rename hint ("Did you mean reference_to to reference?") because they sit in that schema's aliases map. On ListColumnSchema NEITHER key gets a rename hint — they are not aliases of anything there, they are simply undeclared, exactly like the nonsense key.

Why this is observation-class, not a live defect

Nothing renders wrong today: the branch is a fallback that only runs before objectDef has loaded, and a column that carries neither key just yields referenceTo: undefined, which the filter builder already tolerates. The point is that the read exists at all — it consults two keys that a spec-compliant producer cannot put on a list column, which is the consumer-side tolerance AGENTS.md commandment 0.1 forbids, and the same shape objectui#6711, objectui#6625 and objectui#6597 each retired elsewhere.

What a fix would be

A measurement first, not a sweep. Establish whether any producer in or outside these repos puts a relational target on a list-view COLUMN (as opposed to on the field def the column names). If none does, the read is dead weight and goes; if some does, the question is whether ListColumnSchema should declare the key rather than whether the renderer should keep absorbing it.

Filed unassigned, for triage, by the seat that implemented objectui#6837 half 2. Related: objectui#6837.


Generated by Claude Code

Activity

  1. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    Independently reproduced, and the affected key set is wider than reference_to / reference

    Measured during the objectui#7642 census (PR objectui#7649). That card's table listed this branch as if it read an object-schema field def; it does not, and this card already had it right.

    Re-measured against the installed ListColumnSchema, base { field: 'account_id' }, both controls lit (minimal column ACCEPTED; zzz_not_a_real_key REJECTED unrecognized_keys):

    display_field       REJECTED unrecognized_keys      displayField        REJECTED unrecognized_keys
    description_field   REJECTED unrecognized_keys      descriptionField    REJECTED unrecognized_keys
    id_field            REJECTED unrecognized_keys      idField             REJECTED unrecognized_keys
    reference_field     REJECTED unrecognized_keys      reference           REJECTED unrecognized_keys
    reference_to        REJECTED unrecognized_keys      lookupFilters       REJECTED unrecognized_keys
    lookup_filters      REJECTED unrecognized_keys
    

    So beyond the two relational-target spellings this card names, the same branch also reads f.display_field || f.reference_field and f.id_field — and ListColumnSchema declares neither casing of those either. This reinforces this card's own conclusion: it is not a snake-vs-camel question on this bag, because there is no declared spelling to converge on. Narrowing it the way an object-schema read was narrowed would leave an equally undeclared read behind.

    The branch is now annotated in place (PR objectui#7649) so the next sweep does not mistake it for the object-schema population sitting a few lines below it in the same file.

    Generated by Claude Code, session session_01KbJQ1y1J12nZxYzFWhP8Q3.


    Generated by Claude Code

  2. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 5, 2026
  3. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 — domain:ui / priority:p3 / pm:queue / finding

    锚定 (anchoring):packages/plugin-list/src/ListView.tsx ⇒ domain:ui。⚠️ 若结论是"ListColumnSchema 应当声明这个键",那一半在上游 spec ⇒ 届时另议。

    在 origin/main a472b07 上复核 —— 站点成立

    packages/plugin-list/src/ListView.tsx:2713   referenceTo: f.reference_to || f.reference,
    

    ⛔ spec 侧的四次 safeParse 实测({ field: 'acct' } 通过;加 reference / reference_to / zzz_nonsense 三者同样被 unrecognized_keys 拒)本席验不了 —— 本环境未装 @objectstack/spec。采信,未复核。

    ⭐ 但那组实测的设计本身值得点名,因为它是本轮见过最讲究的一次

    卡片没有停在"这两个键被拒了",而是用一个无意义键做负对照,并从缺失的东西里读出结论:

    The tell is what is ABSENT. On FieldSchema both legacy spellings come back with their own rename hint ("Did you mean …") because they sit in that schema's aliases map. On ListColumnSchema NEITHER key gets a rename hint — they are not aliases of anything there, they are simply undeclared, exactly like the nonsense key.

    ⭐ 一个正对照(合法键通过)、一个负对照(无意义键被拒)、外加拒绝消息形状的比对(有无 rename hint)—— 三者合起来把"这两个键在 list column 上不是遗留别名,而是根本没声明"钉死了。⛔ 后来者不要用"它们在 FieldSchema 上是别名"去推翻这个结论,那是另一个 schema。

    定级理由

    priority:p3 —— 采纳卡片的 observation-class 自评并复核其论证:

    ⛔ 定型与边界 —— 卡片自己开的处方是对的,请照做

    A measurement first, not a sweep. Establish whether any producer in or outside these repos puts a relational target on a list-view COLUMN (as opposed to on the field def the column names).

    ⭐ 这个先后顺序是硬要求,因为两种结果导向相反的动作:

    普查结果 动作 型
    无生产者 这个读取是死重,删掉 收窄 ⇒ tidy,不踩底线
    有生产者 问题变成"ListColumnSchema 该不该声明这个键" 上游 accept-set 变更 ⇒ 踩底线,且跨仓

    ⛔ 不做普查就删,可能砍掉某个真实生产者的唯一通路;不做普查就去上游要声明,可能为零用户加宽契约。

    ⛔ 给执行席的其他边界

    1. 普查范围要含仓外(卡片写的 "in or outside these repos")。⚠️ 仓外测不到时,请明说"仅凭仓内证据",⛔ 不要把"仓内没有"写成"没有"。
    2. ⛔ 不要顺手narrow 另一条分支。 objectDef.fields 那条已经由 finding(objectui): ~20 more relationship-target readers still run a legacy-first reference_to chain the ingestion normalizer already made redundant #6837 half 2 收窄过了,是对的;本卡只管 !objectDef?.fields 的回落分支。
    3. ⚠️ 注意这条分支只在加载竞态窗口里跑,所以任何回归测试都要刻意构造 objectDef 未就绪的状态,⛔ 否则测试根本走不到 :2713。

    ⛔ 分诊席不认领、不派单、不写码、不裁决删除 vs 上游声明。


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    Ruling: UI closure batch 3 item 3 · letter remove (the fallback branch stops reading undeclared column keys) · maintainer 「同意」 (chat, director seat summon #29, session_01EcrTi7s5oDYPHS4Pi7h31d) 2026-09-24T07:09Z

    Thread re-read to its last comment (5548769257) in this act. Read on objectui origin/main 62597c5: packages/plugin-list/src/ListView.tsx:3399-3401 still reads f.reference_to || f.reference, f.display_field || f.reference_field and f.id_field off a list-view COLUMN. ListColumnSchema declares none of these in either casing (measured with lit controls in the body and in 5542021508).

    Ruled: remove the reads (protocol first; AGENTS.md 0.1). The columns-only fallback builds its filter and sort candidates from what a column declares. Relational targets come from the object definition once it loads, as the objectDef.fields branch already does. ⛔ No key is added to ListColumnSchema to keep the reads alive. Stop condition, run first: census the producers (in-repo, the sibling repos, examples and fixtures) for any of these keys on a list column. If one is found, stop and report instead of deleting.

    Execution: pm:queue · priority:p3 · domain:ui stand.


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1 — domain:ui execution seat
    Session: session_01BA3nKVUwKQJf8DBxrSVtNC
    Branch: claude/issue-7531-listview-column-undeclared-reads
    Worktree: objectui-issue-7531
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/plugin-list/src/ListView.tsx (the !objectDef?.fields columns-only fallback's referenceTo / displayField / idField reads and their census comment ONLY), a pin under packages/plugin-list/src/__tests__/, and one .changeset/7531-…md (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (default judgement tier) — priority:p3: a consumer-side read of three keys ListColumnSchema does not declare
    Clause-②: no
    Thread-read: 5809501335
    Serial constraints cleared: open-PR file lists read 2026-09-25T07:26Z ⇒ none names packages/plugin-list/src/ListView.tsx. PR objectui#10510 (objectui#10326) merged 59636b3a6, and no live pm:dispatched claim names the file. This seat's own objectui#9306 claim excludes it. ⚠️ This seat owes separate stale-docblock edits in the same file (LIST_VIEW_EXTRA_OPERATORS / VALUELESS_FILTER_BUILDER_OPERATORS_CANONICAL) after PR objectui#10534 lands. ⛔ They are not part of this card.

    Scope

    The ruling 5809501335 (UI closure batch 3 item 3, letter remove, maintainer 「同意」):

    • Stop condition, run first: census the producers (in-repo, the sibling repos, examples and fixtures) for any of reference_to / reference / display_field / reference_field / id_field on a list-view COLUMN. If one is found, stop and report instead of deleting.
    • Otherwise remove the three reads from the columns-only fallback. That branch builds its filter and sort candidates from what a column declares. ⛔ No key is added to ListColumnSchema to keep the reads alive.
    • ⛔ The objectDef.fields branch (an object-schema field def, a different contract) is not this card.
    • Pin: a columns-only list view whose column carries those keys offers the same candidates with no referenceTo / displayField / idField, with a lit control.

    ⇒ Fixes #7531.

    domain:ui seat #1 · session_01BA3nKVUwKQJf8DBxrSVtNC · claim · 2026-09-25T07:30Z

  6. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 7531,
      "status": "done",
      "branch": "claude/issue-7531-listview-column-undeclared-reads",
      "pr": "https://github.com/objectstack-ai/objectui/pull/10544",
      "session": "session_01BA3nKVUwKQJf8DBxrSVtNC — harness-stamped id of this run (subagent = the PM seat's session); every commit carries it as the Claude-Session trailer",
      "premise_still_valid": true,
      "summary": "The stop-condition census ran first. It found no list-column producer of reference_to / reference / display_field / reference_field / id_field or of their camelCase forms. In objectstack origin/main bc80e1626 it found 0 of 515 column objects. In objectui base 6881e9e47 it found 4 of 956: three line-item GridField columns, which are a different contract, and one inert plugin-grid ObjectGrid test fixture. I classified that fixture as not a producer, and the reader's call on it is the one open question. So the three reads came out of ListView's !objectDef?.fields columns-only fallback, per ruling 5809501335. The objectui#7642 KEEP comment became one line pointing at the card. Nothing else in ListView.tsx changed, and the objectDef.fields branch and the LIST_VIEW_EXTRA_OPERATORS / VALUELESS docblocks are untouched. A pin with two lit controls and a patch changeset landed, and the ablation turned only the pin red. The assignee was already os-bill when I picked the card up and was left untouched. The claim was confirmed: branch claude/issue-7531-listview-column-undeclared-reads matches comment 5828646659. The worktree was removed after the PR opened.",
      "tests": "All at HEAD 086e71456. pnpm exec vitest run packages/plugin-list/ from the repo root, through os-verify-lock: Test Files 93 passed (93), Tests 1098 passed (1098); VERDICT command-exit 0. Build and type-check: pnpm --workspace-concurrency=2 --filter @object-ui/plugin-list^... build built 12 packages (Scope: 13 of 47); VERDICT command-exit 0. Then pnpm --filter @object-ui/plugin-list type-check (tsc --noEmit && tsc -p tsconfig.test.json): VERDICT command-exit 0. tsc -p tsconfig.test.json --listFiles counts the new test (1). New pin ListView.columnsOnlyRelationalReads-7531.test.tsx: 4 passed. The cases are spec refusal with a bare-column control, a plain-column control, the pin (toStrictEqual against the plain candidates, with referenceTo, displayField and idField undefined), and a definition-supplies-referenceTo control. Ablation: node objectstack/scripts/ablation-replace.mjs in wrap mode, under the lock, put the three reads back. The anchor count went 1 to 0, the ListView.tsx blob went 03798ad2b3be to 39feb0e2facd, and the in-mutation count of the read line was 1. Result: Tests 1 failed | 3 passed (4). Only the pin failed, with a diff of referenceTo \"account\", displayField \"name\" and idField \"id\" against undefined. Restore: the blob after restore is 03798ad2b3be, equal to the HEAD blob, and git diff HEAD is EMPTY. The test imports ../ListView (source), so no dist/ is in the resolution path and no rebuild was needed. The first attempt was a no-op: the tool refused it because the replacement still contained the anchor, so no test ran, and it was restored and verified before the second attempt. Gates, all exit 0: pnpm check:control-bytes (OK); pnpm check:new-line-citations (0 new); node scripts/check-changeset-presence.mjs (1 changeset declared); pnpm check:changeset-claims. The claims gate is report-only: 7 pending changesets name ListView.tsx, and I read all of them. They cover gantt, kanban, userActions and sort comments, and all remain true. Hand-derived gate families, all exit 0: check-vi-mock-inherit, check-vi-mock-specifiers, check-vi-mock-override-shape, check-test-path-roots, check-changeset-fixed / no-major / overwrite, check:spec-symbols, check:unreferenced-sources, check:phantom-deps, check-type-check-coverage, check-lint-coverage. check-governed-queue-guard --test answered NOT GOVERNED. eslint on the touched files: ListView.tsx has 0 errors and 183 warnings, with per-rule counts identical to the base content (read via --stdin at the same path); the new test has 0 errors and 0 warnings. eslint.config.js has no type-aware linting, so this diff cannot move any untouched file's verdict. The full pnpm lint is left to CI. CI: NOT MEASURED, reason: the report is delivered once local verification is done, and CI convergence is the PM's.",
      "mcp_calls": "0 — no MCP tool was called",
      "api_writes": "2 — both through the fleet-write relay, each as one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]: (1) op pr_create, POST /repos/objectstack-ai/objectui/pulls, which opened draft #10544; the body was read back byte-identical (11353 bytes); (2) this report, via post-stamped --comment=7531 --repo=objectstack-ai/objectui, which is POST /repos/objectstack-ai/objectui/issues/7531/comments. Zero label writes. Four git pushes to the branch, which are not REST writes.",
      "open_questions": [
        {
          "question": "Does the stop condition in ruling 5809501335 count a unit-test fixture? The census found one list column carrying a target key: packages/plugin-grid/src/__tests__/ObjectGrid.linkCellAnchor.test.tsx, a grid-view column with reference_to: crm_account. It renders ObjectGrid directly, so ListView's fallback never sees it. No ObjectGrid code reads a relational key off a column: the lookup cell uses applyRelationalMeta(fieldMeta, objectDefField). And the fixture has no object definition, so the key is inert. I read it as not a producer and opened the removal PR.",
          "options": [
            "A: not a producer. Keep PR #10544, and optionally drop the inert key from that fixture later.",
            "B: the literal reading, where any fixture counts. Close PR #10544, then either rule the fixture out or clean it, and re-dispatch the removal."
          ],
          "recommendation": "A, because the census exists to protect a producer's only path, and this fixture has no path to the removed read. It does not feed ListView, and its key is read by nothing, before or after this PR. So it raises no question of whether ListColumnSchema should declare the key."
        }
      ],
      "out_of_scope_findings": [
        "class: b · Seam: spec:FieldSchema.displayField → renderer:plugin-list ListView candidateFields objectDef.fields branch, which reads field.display_field || field.reference_field and never displayField, feeding components FilterBuilder LookupValuePicker (displayField || name). Evidence: on the installed @objectstack/spec 17.4.0, FieldSchema ACCEPTS displayField, REFUSES display_field with the rename hint display_field → displayField, REFUSES reference_field with the hint → referenceVia, and REFUSES id_field and idField with no hint, the same as the nonsense-key control. A named probe (a temporary test run under the lock, then deleted) showed a definition field carrying only displayField: full_name gives a candidate with displayField undefined, while display_field: full_name gives full_name. The picker consequence, labels falling to r.name, then r.label, then the id, is read from code, not measured in the UI. UserFilters.tsx in the same package has the same display_field ?? reference_field read (code reading, not probed). plugin-grid already converged on displayField (relationalMetaKeys.ts, objectui#6875 / #7155). · dedupe words: ListView filter displayField, candidateFields display_field, LookupValuePicker displayField name, UserFilters display_field reference_field",
        "class: b · Seam: spec:ListColumnSchema, a closed shape that refuses options → renderer:plugin-list ListView candidateFields columns-only fallback buildOptions(fieldName, f.options). Evidence: ListColumnSchema.safeParse on a column carrying options is REFUSED unrecognized_keys (Unrecognized key(s) on this list column: `options`, no rename hint). Its declared keys are field, label, width, align, hidden, sortable, resizable, wrap, type, pinned, summary, prefix, link and action. The same AST census for options over list columns found 0 list-column producers: 6 hits in objectui (line-item and flow-config columns) and 2 in objectstack (record:line_items columns in examples/app-showcase project-detail.page.ts). This is the same shape AGENTS.md 0.1 forbids and the sibling of this card, but it is not in ruling 5809501335 and the dispatch fenced it off. · dedupe words: ListView columns-only fallback options, buildOptions f.options list column, ListColumnSchema options undeclared, candidateFields column options",
        "carrier: domain:ui seat #1, in its planned post-objectui#10534 same-file docblock edits to ListView.tsx · the objectDef.fields branch comment that says the SIBLING branch above is NOT narrowed and is filed, not answered here is stale after this PR; left untouched per the dispatch fence · noted, not filed",
        "carrier: 承接者:无 · the ObjectGrid.linkCellAnchor.test.tsx case about a lookup column pointing at ANOTHER object staying plain passes whatever the column carries, because its reference_to is inert and there is no object definition. It is a test-quality note, recorded in the PR Acceptance notes · noted, not filed"
      ]
    }

    Generated by Claude Code

  7. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    ✅ ACCEPT — PR objectui#10544 at 086e71456 · entering the merge queue once CI is green

    domain:ui seat #1, session_01BA3nKVUwKQJf8DBxrSVtNC. The seat read the dev report and the diff against the ruling 5809501335 (UI closure batch 3 item 3, letter remove, maintainer 「同意」) and the claim 5828646659. No review-tier record is owed, because Clause-②: no holds: removing reads of keys ListColumnSchema refuses changes nothing for a spec-compliant producer.

    Implemented-by:  claude/issue-7531-listview-column-undeclared-reads
    Reviewed-by:     session_01BA3nKVUwKQJf8DBxrSVtNC
    
    item reading
    shape draft · base main · Fixes #7531 · Clause-②: no at line start · 3 files (+196 / −11) · merges clean onto current main
    stop condition, run first The census of list-column producers of reference_to / reference / display_field / reference_field / id_field, and their camelCase forms, found: objectstack origin/main 0 of 515 column objects; objectui 4 of 956. Three of those are line-item GridField columns (a different contract) and one is a test fixture (below). objectstack-ai/cloud is unreadable here, and the dev says so
    the fix The three reads are removed from the !objectDef?.fields columns-only fallback. The objectui#7642 "KEEP" comment becomes one line naming this card. ⛔ The objectDef.fields branch and the LIST_VIEW_EXTRA_OPERATORS / value-less docblocks are untouched, as fenced
    ablation The three reads put back: 1 red / 3 green, only the pin. Its diff is referenceTo "account", displayField "name", idField "id" against undefined. Restored by blob 03798ad2b3be
    tests + gates packages/plugin-list/ 93 files / 1098 tests passed. Type-check passes after the closure build. eslint per rule matches base (183 warnings, 0 errors). Control bytes, new line citations, changeset presence / claims / fixed / no-major / overwrite and the vi-mock gates exit 0, and the queue guard says NOT GOVERNED. check:changeset-claims names 7 pending changesets on ListView.tsx; the dev read each and none is falsified. Changeset: patch on @object-ui/plugin-list

    The dev's open question, answered by the seat: A. The census found one list column carrying a target key: packages/plugin-grid/src/__tests__/ObjectGrid.linkCellAnchor.test.tsx, { field: 'account_id', type: 'lookup', reference_to: 'crm_account' } (the seat re-read it on main). The ruling's stop condition exists so that the removal cannot take away a producer's only path. This fixture renders ObjectGrid directly, never reaches ListView's fallback, and its key is read by nothing, before or after this PR (the lookup cell resolves through the object definition). So it is not a producer for the removed read, and the stop condition does not fire. This is a verification-strategy call, which is inside the seat's discretion, and it goes into the round report's veto window.

    Dev findings — each one routed

    domain:ui seat #1 · review · 2026-09-25T08:21Z

  8. added a commit that references this issue on Sep 28, 2026
    fee1da5
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

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions