feat(core,components,fields): labelling 扩为三值闭枚举,display 四 + grid 可编辑态不再丢 host id (#4857) - #5036
Conversation
… + grid stop dropping host ids in editable state ComponentMeta.labelling extends to the closed three-value enum 'control' | 'group' | 'display' (objectui#4857, ruled jointly with objectui#4871): 'display' declares a widget whose whole surface is a pure display in EVERY state, so the form renderer applies the objectui#4788 host container (id + aria-labelledby + aria-describedby + role="group") in the editable state too, while the readonly === true arm keeps its exact #4788 semantics. formula / summary / auto_number / vector declare 'display' — on the real object-form path they arrive `disabled`, never `readonly` (a deliberate distinction left untouched; option 1 was rejected), so their labels' `for` dangled in every editable form. grid was re-measured before classification: bare config offers one focusable — the auxiliary "Add line" button, which `for` must not address (a label click would insert a row) — and realistic configs are tables of per-cell inputs. A composite, so it declares labelling: 'group' and its root container consumes the host id, name and description (CheckboxesField's split: `name` and `aria-invalid` withheld). Companion registry gate: FIELD_WIDGET_LABELLING is a Record keyed by the widget map's literal key union — registering a widget without a labelling decision is a compile error, not a silent fall-through to the dangling-`for` path — with runtime parity assertions in the declaration test. Fixes #4857 Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
|
PM 验收 ✅ ACCEPT(#4857,批次 21 —— 05:03Z 合裁 + 06:36Z 更正的实施件,声明切片按裁定由指定模型席执行) 实物核验(merge-base CI 亲读:20/20 check runs completed,零失败(两项 path-filter skipped 计绿)。 grid 重测裁定采信:卡面「控件是单个 labelable button」被同套 probe 推翻 —— bare grid 唯一 button 是加行动作,label 落上去等于命名「加行」且点 label 插行;realistic 是 8 焦点复合体。按裁定「先重测勿外推」改声明 反向验证读数:①撤 display 分支 7 红且落点精确;②Record 摘键 → tsc TS2741 + vitest 3 红 —— 如实归类为比 PR5023 变异①先例强一档(本门另有运行时奇偶断言);③错标 'control' → 恰 2 红,且「声明↔渲染面一致性有没有人管」被两文件分工(声明文件抓注册边界、e2e 抓声明⇒渲染)实测确认,无新缺口不立 finding —— 变异③正是派发词要求探测的那个缺口,答案是实测出的「有人管」。 #4871 于本 PR 落 main 后解锁回队(其 meta 层复用本 PR 导出的 Record 与类型)。undraft + auto-merge(squash)。 Generated by Claude Code |
Fixes #4857
实施依据
2026-08-17 05:03Z 维护者裁定(评论 5312059193,与 #4871 合裁)+ 06:36Z 实测更正(评论 5312685254)。卡面三选不再开放:方案 1(disabled→readonly 重映射)被裁否,
ObjectForm.tsx:687一字未动;slider / signature 已由 PR #4933 交付,本 PR 不触碰;剩余五行 = display 四 + grid。变更
1. 声明扩展(公开契约键,
packages/core)ComponentMeta.labelling从二值扩为三值闭枚举'control' | 'group' | 'display'—— 全仓唯一词表,即合裁给「host 如何得知 widget 将渲染什么」的统一答案,⛔ host 本地变体。'display'语义写入注释:渲染面在任何状态下都是纯展示、无可聚焦控件;host 不发 label 的for(任何状态下都不存在 labelable 元素可指),改走 #4788 已裁的容器通道(id +aria-labelledby+aria-describedby+role="group"),且与'group'不同,widget 侧不消费任何键 —— host 容器即命名面。zod 侧核查:全仓不存在
ComponentMeta的 zod schema(packages/core与@objectstack/spec均无labelling相关 schema),无可同步项。2. display 四接 host 容器(
packages/components+packages/fields)formula/summary/auto_number/vector声明'display'。form renderer 的 host 门从「readonly === true」扩为「readonly === true或 声明'display'」:for悬空、description 零消费者(email / url / phone 实测) #4788 原行为;disabled: true,从不readonly)也被覆盖 —— 这正是 [fields] 非 group-labelled widget 的只读替换显示丢掉 host 下发的整份 plumbing —— label 的for悬空、description 零消费者(email / url / phone 实测) #4788 门管不到这四个的原因;for悬空、description 零消费者(email / url / phone 实测) #4788 的结构性主张:机制落 host 一次,不留「记得 spread」的入口)。3. grid:先重测,实测推翻卡面预设后按复合体落地
06:36Z 更正明言勿按 slider/signature 外推、grid 需重测。同套 probe(真 form renderer + 裸注册,
description设置,编辑态,RESOLVES-LABELABLE 口径)实测origin/maine71c854ce:读数裁决:bare 配置唯一 focusable 是辅助动作 "Add line" button —— 它确实 labelable,但把 host id 落上去意味着字段 label 命名「加行」动作、点 label 会插行(label 激活转发);realistic 配置是 8 个 focusable 的单元格输入矩阵。即 grid 是复合体(
address形态),「其控件是单个 labelable button」的前提被实测推翻,方案 3 单用不成立。落地:声明labelling: 'group',root 容器按CheckboxesField的键位逐键消费 host 通道 —— 编辑/list 态容器收 id、label IDREF、describedby,name与aria-invalid按 #3291/#3318 界线扣下(grid 的 invalid 本就按单元格上报;aria-invalid轴归 #3318 账本,本 PR 前后都不落任何 hostaria-invalid,该轴零变化);readonly 分支(替换性表格,无字段输入)走toHostGroupProps('instead-of-the-inputs'),名字与描述同落容器。4. 配套门:registered ⇒ declared
FIELD_TYPES_GROUP_LABELLED(Set)升级为FIELD_WIDGET_LABELLING—— 以fieldWidgetMap字面键联合为键的穷尽 Record(#3935 的 Record 形态,并照group-labelling-declaration.test既有集合断言扩展,未另起炉灶):往 map 加 widget 而不定 labelling 是编译期红(tsc 报缺键),而非静默退回 control 路径 —— 裁定点名的漏声明陷阱。运行时 meta 侧保留 #3961 已钉的「'control'以缺省拼写」约定(第三方 widget 本就以缺省进入 control 路径,host 读法不因此双轨;Record 里的'control'条目仍是强制决定 ——「被缺省」与「决定用缺省」是两个事实)。声明测试新增:display 逐项 + 精确集合断言、record↔FORM_FIELD_TYPES键位奇偶断言、record↔注册 meta 逐键一致断言。Record 已导出,供姊妹单 #4871 的 metadata-admin 宿主复用同一词表。测试
packages/fields/src/__tests__/display-grid-host-channels-e2e.test.tsx:display 四可编辑态(含disabled: true的真实 ObjectForm 形态)+ readonly 态、grid 编辑/readonly/无 label 三态、control 绿对照;packages/componentsform-readonly-host-group.test.tsx增补 display 声明 describe(编辑态包裹、disabled 路径、双臂同真仅一层、未声明回退钉、无 label 不包);readonly-host-plumbing-e2e.test.tsx中 formula 编辑态残留钉按其自述「后改需是 deliberate 的」升级为未声明回退钉(该文件裸注册无声明,断言不变,语义改为钉住门要防的静默退化形状);turbo run type-check通过,check-control-bytes通过,改动文件 eslint 0 error。反向验证(先书面预判,commit 后变异,checkout 还原)
expect(host).not.toBeNull()、components 新 describe 2 例红,readonly 与 grid 全绿 —— 实测 7 红 24 绿,与预判一致。vector声明:预判 tsc 红(缺键)+ 声明测试红且点名 vector;实测 tscTS2741: Property 'vector' is missing、vitest 3 红(逐项 + 精确集合 + registered⇒declared 奇偶)—— 比 PR fix(metadata-admin): 审计面板锁状态列补齐中文,词表键绑 lockState union (#5004) #5023 变异①先例的「tsc 红 vitest 绿」更强一档:本门在 Record 之外另有读 Record 的运行时奇偶断言,vitest 同红。formula错标'control':预判 tsc 绿、声明测试的 display 逐项与精确集合断言红(record↔meta 奇偶断言绿 —— 两者一致地错),e2e 绿(裸注册硬编码 'display',不读 Record)。实测 tsc exit 0、恰好 2 红,与预判一致。声明↔渲染面一致性由两文件合取持有:声明文件钉注册边界(本变异被它抓住),e2e 文件钉「声明 ⇒ 渲染」—— [fields] 复合/分组 field widget 丢弃或错投 host 下发的控件 id:address / geolocation 的表单 labelfor悬空,checkboxes / radio / rating / file 的for落在不可 label 的 div 上 #3961 起两文件自述的既有分工,无新缺口,故不另立 finding。边界
ObjectForm.tsx:687disabled 映射未动(方案 1 裁否);app-shellmetadata-admin 宿主([app-shell] metadata-admin 的 SchemaForm 无条件给每个 widget 发Label htmlFor={id},但 17 个注册 widget 里只有 5 个消费 id —— 分组类控件面的for悬空且组无可访问名 #4871 姊妹单持有)未伸手;其DashboardWidgetInspector内部Field组件的本地二值labellingprop 属 [app-shell] metadata-admin 的 SchemaForm 无条件给每个 widget 发Label htmlFor={id},但 17 个注册 widget 里只有 5 个消费 id —— 分组类控件面的for悬空且组无可访问名 #4871 已裁范围,类型上与ComponentMeta无耦合,本 PR 不动;aria-invalid轴归 29 个注册 field widget 在校验失败后从不携带aria-invalid—— #3306 全注册表守卫实测的账本 #3318 账本,前后零变化。changeset:core / components / fields 三包
minor(公开契约键扩展,向后兼容 —— 对照 #3985/#3832 先例;仓规 fixed group ⛔ major)。Generated by Claude Code