fix(fields): grid columns 收敛到声明拼写 name,spec 合规元数据不再渲染空单元格 (#3951) - #4909
Conversation
`GridField` declared a local `GridColumn` interface keyed by `field` and read `c.field` everywhere, while the published `GridColumnDefinition`, the grid docs page and the `fields-grid` catalog examples all declare the key as `name`. Metadata authored against the published type rendered the right row count with every cell empty, plus a React "unique key" warning per column. Align the reader to the declared spelling and migrate the master-detail producer (`deriveColumns` / `hydrateColumns` / `pickAmountField`) with it. No `col.field ?? col.name` alias — one spelling at the producer (AGENTS.md #0.1). Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
…ding it as a dual read The comment on `GridColumn.name` stated the rule by spelling out the alternation it forbids. `column-identity.ratchet.test.ts` scans package sources line by line and cannot tell prose from code, so that sentence registered as a new dual read and failed the gate — on the very commit that removed the second spelling. Restate the rule in words and leave a note telling the next editor not to write it as an expression again. The file is deliberately NOT added to the ratchet inventory: there is no dual read here to record, and listing one would corrupt the inventory it exists to keep honest. Second instance of the text-level-gate blind spot tracked in objectui#4902. Co-authored-by: Claude <noreply@anthropic.com>
返工:
|
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
|
PM 验收:ACCEPT(1 轮返工后)(#3951,批次 16→17,PM 会话 裁定执行:声明拼写 反向验证:双向变异各自预判逐例命中(reader 回翻 → 6 红空单元格;producer 回翻 → 5 红含双读守卫反转);React key 警告钉的一次性消费陷阱(once-per-owner,断言空过)由 dev 自查抓出 —— 移独立文件 + 正查对照后重跑转红,假绿不采信纪律执行到位。 返工记账(1 轮,非行为缺陷):首轮 CI 唯一红是 附带产出:os#9227(spec 侧 → undraft + auto-merge (SQUASH)。 Generated by Claude Code |
Fixes #3951
按 2026-08-10 维护者裁定实施:已发布的声明拼写胜出,修 reader。
GridField对齐GridColumnDefinition(@object-ui/types)读name,master-detail 派生侧(deriveColumns/hydrateColumns/pickAmountField)随迁。没有col.field ?? col.name别名 —— 一套拼写落在生产者侧,AGENTS.md #0.1。前提复核(改前先验)
卡面写于 2026-08-09,行号已随文件增长漂移(正文的
:558/:590/:620现为:659/:689/…),但前提完全成立:packages/types/src/field-types.ts:677GridColumnDefinition.name是GridFieldMetadata.columns的声明类型 —— 现状未变。packages/fields/src/widgets/GridField.tsx本地GridColumn声明field: string,31 处c.field读点,无任何 normalize —— 现状未变。content/docs/fields/grid.mdx:散文、ColumnDefinitioninterface、三组 pattern 示例全部是name侧;页内唯一field键的代码块是object-grid(plugin-grid 的ListColumn,另一份契约,已在原文注明)。核实无误,未反向改动。examples/schema-catalog/src/schemas/fields-grid/三例 9 个列条目全部name侧。核实无误,未反向改动。存量
field拼写普查(仅 grid-field 列,即GridColumnDefinition/GridColumn)甄别口径:list view /
object-grid的ListColumn、record picker 的LookupColumnDef、sort/filter子句、form section 的field都是另外的契约,合法保留field,不计入、不改动。field拼写objectstack仓examples/app-crm、app-showcase、app-todo的inlineColumns)relatedListColumns是裸字符串数组,lookupColumns是 record picker 契约examples/schema-catalogfields-grid 三例(9 条)name侧content/docs/fields/grid.mdxname侧apps/**DevLookup/DevMasterDetail),已逐处改拼写结论:授权侧(stored/example metadata)存量为 0,无需数据迁移;转换面全部落在仓内代码、dev harness 与 ADR 示例,已逐处显式改写,无兼容层。
迁移清单
Reader / producer(行为改动)
packages/fields/src/widgets/GridField.tsx——GridColumn.field改为name(带注释锚定 [fields] grid columns have two incompatible key spellings: declared type saysname, GridField readsfield— spec-compliant grid metadata renders empty cells #3951 与「不设别名」),31 处读点全迁:key=、row[…]、blank[…]、applyCell、columns.findIndex、extraShown.has、data-computed、aria-label兜底等;两个局部形参field: string更名columnName,免得退役拼写在读路径上留影子。packages/plugin-form/src/deriveMasterDetail.ts——deriveColumns产出{ name, … };hydrateColumns改查fields[col.name];curateColumns/pickAmountField全部按name判定。Fixture / harness / 文档示例(拼写对齐)
GridField.test.tsx、date-locale-channel.test.tsx、complex-widgets.test.tsx、deriveMasterDetail.test.ts、LineItemsPanel.test.tsx、LineItemsPanel.elementDataSource.test.tsx、MasterDetailForm.test.tsx、MasterDetailForm.elementDataSource.test.tsx、ObjectForm.test.tsx、subformHosts.test.tsx、MetadataProvider.merge.test.ts(subform columns 那一条;同文件的 list view columns 保持field不动)、apps/console/src/dev/DevLookup.tsx、DevMasterDetail.tsx、docs/adr/0001-master-detail-subform.md(两处授权示例,ADR 原文自称「reuses the existingGridColumnDefinitionshape」却写着field,正是本卡记录的分歧本身)。消费半径按规则被谁读扫,不按被改包扫:
GridColumn的 TS 消费者只有 plugin-form(LineItemsPanel / MasterDetailForm / deriveMasterDetail);plugin-grid 的VirtualGridColumn/ObjectGridColumnState是另一套类型,未涉及。新增钉(pin,纪律同 #4041)
packages/fields/src/widgets/GridField.declaredSpelling.test.tsx—— fixture 以GridColumnDefinition[]显式标注类型(钉在声明契约上,而不是 widget 自己的理解上),断言单元格逐格非空、只读面有值、空行按声明列名开、编辑按声明列名回写。packages/fields/src/widgets/GridField.keyWarning.test.tsx—— React missing-key 警告那一半,单独成文件。原因见下。packages/plugin-form/src/deriveMasterDetail.declaredSpelling.test.tsx—— master-detail 路径的钉:产出/消费均为name、field键列不被容忍性复活、以及一条端到端(派生列直接喂进真GridField,渲染出非空单元格)—— 生产者与消费者再也不能各说各话而两边单测都绿。一处必须讲清的测试设计
key 警告最初和「单元格非空」放在同一文件的第二个用例里,反向验证时它没有变红。查因:React 的 missing-key 警告对同一 owner 组件只发一次,第一个用例的渲染已经把它消费掉,第二个用例观察到的是「什么都没有」—— 断言绿是因为没东西可看,不是因为逻辑对。这正是「空绿」陷阱,故拆成独立文件(
domproject 是isolate: true,按文件隔离,能保证它拥有首次渲染),并加了一个阳性对照:先渲染一个故意不带 key 的列表,断言 spy 确实抓得到警告,再断言 grid 自己的渲染没有新增警告。这样绿是被证明过的绿。反向验证(先预判、后执行;变异前已 commit,还原用
git checkout HEAD --,未用 stash)预判方向:Red(常规向) —— reader 读的键是 fixture 不拼写的那个,还原退役读法会同时抽掉「值」和「诊断」,不是只动诊断。
变异一:reader 还原成读
c.field(git checkout 97da1b0d6 -- GridField.tsx),fixture 保持name:与预判一致:值与诊断同时变红,派生侧不受影响的 4 例如期保持绿。
变异二:生产者还原成产出
field: name且hydrateColumns查col.field(reader 保持已修):第三条按预期反向变红:旧生产者读
col.field,{ field: 'amount' }反而被补全了 type/label —— 恰好把「容忍性双读会长回来」这件事钉住。两次变异后均
git checkout HEAD --还原,复跑全绿。验证
(全部重验证经
flock /tmp/os-heavy-verify.lock串行,NODE_OPTIONS=--max-old-space-size=4096,--maxWorkers=2。)Breaking change 与 changeset
.changeset/grid-column-declared-name-spelling.md(patch,@object-ui/fields+@object-ui/plugin-form)已写明:作者显式提供的 grid / line-items / master-detail subform 列必须由{ field: 'amount' }改写为{ name: 'amount' };自动派生列(未写columns)无需改动;list view 与object-grid的ListColumn是另一份契约,保留自己的field键。顺带发现(未在本 PR 修)
Field.inlineColumns在 spec 里是z.array(z.any()),任何拼错的列键都能发布通过、只在浏览器里表现为空单元格。本 PR 修好了 renderer,但授权面仍然没有守卫;例应用中该键用量为 0,故对本仓无影响,已另立卡(未指派)。