fix(plugin-kanban): the card title-dedupe skip set reads the shared name-field resolver, not a key nothing emits - #8433
Conversation
…ame-field resolver, not a key nothing emits A kanban card printed its record title twice — once as the card heading, once as the first row of the card body. `ObjectKanban` resolves each heading through ADR-0079's `getRecordDisplayName`, then builds `titleFieldsToSkip` so the title field's raw value is not rendered again as a card field. That skip set read `objectDef.NAME_FIELD_KEY`, a key NOTHING produces: `@objectstack/spec@17`'s object schema declares `nameField` (canonical) and `displayNameField` (its deprecated alias), and `NAME_FIELD_KEY` occurs 0 times in the framework tree and 0 times in the published package — this repo reads it only as the last rung of `declaredNameField` in `record-title.ts`, and never emits it. So the read was always `undefined` and the skip set collapsed to its five hard-coded literals (`name` / `full_name` / `title` / `subject` / `display_name`). Any object whose name field is spelled otherwise duplicated its title, which made the defect universal on AI-built apps — whose objects name fields `visit_title`, `owner_name`, `<entity>_name` — and invisible on hand-built objects whose name field is literally `name`. The skip set now reads `@object-ui/core`'s `resolveNameField`, the name-space twin of the resolver that produced the heading, so the two agree on WHICH field titles the object. Deliberately ONE rung, unlike the same dedupe in `record-details.tsx` (objectui#8175), which also carries `deriveTitleField`: that ladder filters a SYNTHESIZED field list, whereas this one filters an AUTHOR-DECLARED `cardFields`, where dropping a field the author asked for is a worse failure than a repeated title. The fourth test is the guard — an object whose declared (`code`) and derived (`owner_name`) pointers disagree — and it goes RED if the second rung is ever added. Closes #8400 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
补上你声明缺的那一块:活栈界面证据你在汇报里如实写了「测试是 jsdom 里真实渲染看板卡片 DOM,不是活栈截图……如果你要界面证据,需要在 rig 上跑一遍」。我跑了,修复在真实栈上生效。 装置:把本分支( DOM 计数(不是看截图数): // 每个标题字符串在叶子节点里出现的次数
{ "小虎犬牙洁治评估": 1 }, // 修复前 2
{ "豆包皮肤瘙痒复诊": 1 } // 修复前 2卡片正文完整性(证明没有过滤过头): 标题一次、其余四个字段一个不少。对照 issue 正文里修复前那张卡(标题连着印两遍、然后才是「可乐」),这是同一张卡的前后对照。 你那个「只用一级梯子」的判断,我同意
两处你纠正我的地方,我确认收到
CI 还在跑(Lint / Type Check / 4 个 test shard)。绿了我入队。 |
Kanban cards printed the record title twice — once as the card heading, once as the first row of the card body.
Root cause
ObjectKanbanresolves each heading through ADR-0079'sgetRecordDisplayName, then buildstitleFieldsToSkipso the title field's raw value is not rendered again as a card field.packages/plugin-kanban/src/ObjectKanban.tsx:418fed that set fromobjectDef?.NAME_FIELD_KEY— a key nothing produces, so the set collapsed to its five hard-coded literals and the title field flowed straight into the body.The three premises, re-verified independently (not taken from the issue)
NAME_FIELD_KEYgit grep -i NAME_FIELD_KEYinobjectstackat the cloud pinafbf2711and at frameworkmain; positive controlnameFieldmatches in bothgrep -raovernode_modules/@objectstack/spec@17.0.0(dist + json-schema + api-surface)nameFieldanddisplayNameFieldObjectKanban.tsx:418 → :542-549 → :568;titleFieldsToSkipis consulted only in the explicit-cardFieldsbranch, and its other members areexplicitTitleField(unset unless the view declarescardTitle/titleField) plus 5 literalsresolveNameField()(packages/core/src/utils/record-title.ts:346) is this repo's shared name-space resolver, and it is correct: it delegates the declared ladder todeclaredNameField()(:320,nameField ?? displayNameField ?? NAME_FIELD_KEY— the identical??chaingetRecordDisplayNamereads at:551) and then falls through toderiveTitleField().Blast radius: the repo-wide grep
NAME_FIELD_KEYhas exactly two read sites in source:packages/core/src/utils/record-title.ts:321— the correct back-compat fallback. Left alone.packages/plugin-kanban/src/ObjectKanban.tsx:418— the defect. Fixed.Every other occurrence is prose (docblocks, changesets) or the
record-titlealias test. The only sibling of this dedupe,record-details.tsx, was already moved onto the shared resolver by objectui#8175, so no third site needs the same change.Why one rung, not two
objectui#8175 unrolls the same ladder into two candidates —
resolveNameField()andderiveTitleField()— so a declared-but-blank pointer still dedupes against the derivation its value-keyed header fell through to. That second rung is deliberately not copied here:record-detailsfilters a synthesized field list, whereas this set filters an author-declaredcardFields. Carrying the derivation alongside a declared pointer would drop a field the author explicitly asked for whenever the two disagree (nameField: 'code'titles the card while the derivation answersowner_name) — on a four-field card that is a worse failure than a repeated title. The fourth test pins that boundary and goes RED if the second rung is ever added.Ablation (measured, both directions)
New file:
packages/plugin-kanban/src/__tests__/ObjectKanban.nameFieldSkipSet-8400.test.tsx, written to the AI-built shape from the report — an object withnameField: 'visit_title'and a board listingvisit_titleincardFields. Assertions count occurrences rather than asserting presence, because the defect is a duplicate and an absence assertion would bless the wrong fix.Fix reverted (one token:
resolveNameField(objectDef)→objectDef?.NAME_FIELD_KEY):Fix restored:
The one test green in both runs is the positive control (
pet_name/visit_atstill render) — without it every count assertion would be satisfied by a board that dropped the body entirely.Wider verification
vitest run packages/plugin-kanban/— 31 files / 208 tests, all passvitest run packages/core/src/utils packages/plugin-view packages/plugin-detail— 224/225 files pass. The one failure iscore/src/utils/__tests__/date-display.optionsStyle-7745.test.ts(2 assertions), pre-existing and timezone-dependent: it is green underTZ=UTC(15/15) and this diff does not touch that file.turbo run build --filter=@object-ui/plugin-kanban...— 13/13 packages build (this is the real typecheck path; baretsc -pin the package cannot resolve workspace@object-ui/*types without the build).eslinton both changed files — 0 errors (only the file's pre-existingno-explicit-anywarnings).Note on the upstream attribution
cloud#2073 attributed this to
nameFieldnot being persisted intosys_metadata. That attribution is wrong and withdrawn —objectDefcarries the correct answer at this call site; this one line simply did not read it.Closes #8400
🤖 Generated with Claude Code