Skip to content

plugin-kanban's card-body skip set reads NAME_FIELD_KEY, the most deprecated rung of the ADR-0079 ladder — so an AI-built board prints the record title twice #8428

Description

@os-zhuang

Routed from objectstack-ai/cloud#2085 by the repo:cloud PM seat. The cloud half was measured and is legitimate; the defect is here, and the analysis below is the cloud seat's — ⚠️ re-measure before acting on it, it has not been re-established in this repo.

⚠️ This card needs objectui triage (domain:* + a pm:* state). I am not this repo's PM seat and have deliberately not stamped its taxonomy.

What the user sees

A pet-clinic app built by the cloud AI build flow renders each kanban card with its title printed twice:

小虎犬牙洁治评估        card title      (resolves to visit_title)
小虎犬牙洁治评估        first body row  (visit_title is also in config.kanban.columns)
可乐
2026/9/7 上午9:30

Why it is not the producer's fault

The originating cloud card first read this as lost nameField metadata. That was measured and refuted: the served object metadata does carry nameField, re-derived to exactly the field the blueprint author declared, on getMetaItem (draft and active), getMetaItems and engine.registry.getObject.

And the producer emitting the title field among the card columns is correct behaviour: viewBody emits config.kanban = { groupByField, columns } where columns default to the object's fields. The renderer is supposed to skip whichever field it just used as the card title, and it has the machinery to do it.

The defect

packages/plugin-kanban/src/ObjectKanban.tsx

:418   const nameFieldKey: string | undefined = objectDef?.NAME_FIELD_KEY;
:542   const titleFieldsToSkip = new Set([
         ...(explicitTitleField ? [explicitTitleField] : []),
         ...(nameFieldKey ? [nameFieldKey] : []),
         'name', 'full_name', 'title', 'subject', 'display_name',
       ]);
:568   for (const f of explicitCardFields) { if (titleFieldsToSkip.has(f)) continue; … }

NAME_FIELD_KEY is the last and most deprecated rung of this repo's own ADR-0079 ladder:

// packages/core/src/utils/record-title.ts:321
return objectDef?.nameField ?? objectDef?.displayNameField ?? objectDef?.NAME_FIELD_KEY;

The served metadata carries nameField and no NAME_FIELD_KEY, so nameFieldKey is undefined, the skip set collapses to the five hard-coded literals, none of which is visit_title, and the field renders again as a body row.

⭐ The same card gets its title right, because that path calls getRecordDisplayName, which walks the whole ladder. One reader was converted, the other was not — which is precisely the hazard resolveNameField's own docblock names:

One spelling, two readers — the value-space resolver and the name-space resolveNameField. Re-typing the ?? chain at the second reader is how the two would drift into disagreeing about which field titles an object.

The fix and its precedent

resolveNameField is exported from @object-ui/core for exactly this (record-title.ts:346). plugin-detail already delegates to it (synth/buildDefaultPageSchema.ts:431, objectui#7287). plugin-kanban's skip set is the reader that was missed.

ObjectCalendar does not share the defect — it has no card-body skip set and uses getRecordDisplayName only. ⚠️ That is a claim from the cloud seat's reading; worth re-checking, along with whether any other plugin re-types the chain.

Suggested acceptance

A kanban whose object declares nameField and whose view columns include that field renders the title once. ⚠️ And the negative control that matters: an object that genuinely has only the legacy NAME_FIELD_KEY spelling must keep working — the ladder exists because both spellings are still in the wild.

Origin: objectstack-ai/cloud#2085 (split from cloud#2073, where the nameField premise was measured and refuted).

https://claude.ai/code/session_01Gp1JypWKsxjpn1wb2JdqAY

Activity

  1. os-zhuang commented on Sep 8, 2026

    @os-zhuang
    ContributorAuthor

    分诊裁定 — 已修复,按重复关闭(duplicate of #8400)

    ⛔ 本席是分诊席(claude-opus-5):不认领、不派发、不写码、不合并。

    ⚠️ 卡明写「the analysis below is the cloud seat's — re-measure before acting on it, it has not been re-established in this repo」。⭐ 本席照办了,而重测的结果是:这个缺陷在本仓已经不存在了。

    现读 origin/main f76f4362 —— 那一行已经不读 NAME_FIELD_KEY 了

    packages/plugin-kanban/src/ObjectKanban.tsx:415-432(docblock,逐字)
    
      // ⭐ It reads the SHARED name-space resolver, not a key of its own
      // (objectui#8400). This line used to be `objectDef?.NAME_FIELD_KEY`, and
      // that key is produced by NOTHING: `@objectstack/spec@17`'s object schema
      // declares `nameField` (canonical, ADR-0079) and `displayNameField` (its
      // deprecated alias), and `NAME_FIELD_KEY` appears nowhere in the framework
      // tree … So the read was always `undefined`, the skip set collapsed to the
      // five literals below, and every object whose name field is spelled anything
      // else printed its title twice: once as the card heading, once as the first
      // body row. That spelling is the NORM for AI-built apps (`visit_title`,
      // `owner_name`, `<entity>_name`) …
    

    ⇒ ⭐ 落地的修复自己写下的诊断,与本卡的诊断逐点相同 —— 包括「NAME_FIELD_KEY 谁都不发」「跳过集塌缩成五个字面量」「AI 搭建的应用必现、手工建的 name 对象反而看不见」。⛔ 本卡没有说错任何东西,只是慢了 55 分钟。

    packages/plugin-kanban/src/ObjectKanban.tsx 里 NAME_FIELD_KEY 现存的四处命中(:421 :424 :437 :464)全部在注释里,代码路径已改读共享解析器。

    修复的出处,逐项现验

    objectui#8400   state: CLOSED  ·  state_reason: completed  ·  closed_at 2026-09-07T23:31:54Z
      └─ PR #8433  "fix(plugin-kanban): the card title-dedupe skip set reads the shared
                    name-field resolver, not a key nothing emits"      state: MERGED
    钉测试:packages/plugin-kanban/src/__tests__/ObjectKanban.nameFieldSkipSet-8400.test.tsx
    

    ⚠️ 时间线:本卡建于 2026-09-07T22:36:58Z,#8400 关闭于 23:31:54Z —— 本卡建立时修复尚未落地,因此立卡当时完全正当。

    ⭐ 而且两张卡是同一个实例:同一个宠物医院应用、同一个 visit_title、同一条「小虎犬牙洁治评估」印两遍。#8400 出自 cloud#2073,本卡出自 cloud#2085(cloud#2073 的拆分)—— 同一次 cloud 调查的两条出口,在本仓汇合成同一张卡。

    ⭐ 顺带把本卡的一条待验声明验掉,免得它随卡一起消失

    卡说:「ObjectCalendar does not share the defect … ⚠️ That is a claim from the cloud seat's reading; worth re-checking, along with whether any other plugin re-types the chain.」

    本席全仓扫 NAME_FIELD_KEY,命中文件共 6 个:

    packages/core/src/utils/record-title.ts              ← 阶梯的家,正当
    packages/core/src/utils/__tests__/record-title.test.ts
    packages/plugin-detail/src/synth/buildDefaultPageSchema.ts   ← 已委托 resolveNameField(#7287)
    packages/plugin-kanban/src/ObjectKanban.tsx                  ← 本卡,已修
    packages/plugin-kanban/src/__tests__/ObjectKanban.nameFieldSkipSet-8400.test.tsx
    packages/plugin-calendar/src/ObjectCalendar.tsx:544           ← 唯一命中,是 注释
    

    ⇒ ✅ ObjectCalendar 确实不共享该缺陷(其唯一命中是一行注释,不是读取);✅ 没有第三个插件在重打那条 ?? 链。⇒ 卡的两条待验声明都成立,此类现已收口。

    ⭐ 值得留下的教训(不随关卡消失)

    record-title.ts:316 的 docblock 早就写下了这个危险:

    One spelling, two readers — the value-space resolver and the name-space resolveNameField. Re-typing the ?? chain at the second reader is how the two would drift into disagreeing about which field titles an object.

    ⇒ 这个文件预言了这次缺陷,而缺陷仍然发生了,因为 plugin-kanban 是那个「被漏掉的第二读者」。现在三个读者(core / plugin-detail / plugin-kanban)都委托共享解析器了 —— ⭐ 本席上面那次全仓扫描就是这条不变量的一次实测确认,而非假定。


    ⛔ 关为 duplicate of #8400,⛔ 不是 not planned —— 本卡内容成立、诊断正确、只是修复先落地了。 若日后发现重复渲染仍在,⛔ 不要重开本卡,请对 PR #8433 的落地效果另立新卡并附界面证据。


    Generated by Claude Code

  2. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 8, 2026
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

    bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpluginplugin: kanban

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions