Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

[fields] 七个 widget 在**可编辑态**也丢掉 host 下发的 id —— label 的 for 悬空、description 零消费者(formula / summary / auto_number / vector / grid / slider / signature 实测) #4857

Closed

Description

@yinlianghui

在 #4788(只读替换显示丢整份 host plumbing)的实施中实测发现的相邻一条,与那个 PR 无关也不由它引入,按 Prime Directive #10 立单(不认领、不扩围)。

#4788 修的是只读态:host 在「readonly + 注册 field widget」时包一层带 id / aria-labelledby / aria-describedby / role="group" 的容器。本条是同一族里 readonly 门管不到的那一半 —— 有七个 widget 在可编辑态也一样丢掉 host 下发的 id,因为它们根本没有可编辑分支,或者有分支但不展开 DOM pass-through。

实测

origin/main 1ef236e18,与 #4788 同一套 probe(真 form renderer + 裸注册,字段带 description: 'Some help',一行一个字段),看 editable 列:

formula      editable  for=DANGLING hostIdEl=NONE consumers=0
summary      editable  for=DANGLING hostIdEl=NONE consumers=0
auto_number  editable  for=DANGLING hostIdEl=NONE consumers=0
vector       editable  for=DANGLING hostIdEl=NONE consumers=0
grid         editable  for=DANGLING hostIdEl=NONE consumers=0
slider       editable  for=DANGLING hostIdEl=NONE consumers=0
signature    editable  for=DANGLING hostIdEl=NONE consumers=0

对照:其余 27 个注册的非 group-labelled 类型在 editable 列全部是 for=RESOLVES-LABELABLE hostIdEl=input/textarea/button consumers=1。

即:可见 label 的 for 指向一个整个文档里没有任何元素带的 id,只读态之外也一样 —— 这七个字段在无障碍树里没有名字,渲染出来的帮助文本零消费者。

为什么 #4788 的修法覆盖不到

#4788 的 host 门是 readonly === true。而在真实的对象表单路径上,计算型字段并不走 readonly:

packages/plugin-form/src/ObjectForm.tsx:687 把 formula / summary / auto_number 映射成 formField.disabled = true,不是 readonly: true。所以 ObjectForm 渲染出来的公式字段拿不到 #4788 的容器,for 照旧悬空。

另外三个(grid / slider / signature)与 readonly 无关:它们的唯一渲染分支就不展开 host props。

涉及文件

需要一次方向裁决,不要直接推

至少三种形状,取舍不同,不要按 #4788 外推:

  1. 把 D 组映射成 readonly: true(改 ObjectForm)—— 最小,直接落进 [fields] 非 group-labelled widget 的只读替换显示丢掉 host 下发的整份 plumbing —— label 的 for 悬空、description 零消费者(email / url / phone 实测) #4788 已有的容器机制;但 disabled 与 readonly 在本仓是有意区分的两件事(form.tsx 的注释写明了),这条改的是语义分类而不只是 a11y;
  2. 给 host 一个 display-only 声明(ComponentMeta 加一维,类似 labelling)—— 契约优先,host 不再靠「registered + readonly」推断;代价是新增一个公开契约键,且漏声明会静默退回今天的坏形状;
  3. widget 侧各自展开 pass-through —— grid / slider / signature 有真控件,这条对它们大概是对的(host id 落到可 label 元素上,for 自然解析);但对 D 组无效,它们渲染的 span 不是 labelable 元素,for 落上去依然惰性([fields] 复合/分组 field widget 丢弃或错投 host 下发的控件 id:address / geolocation 的表单 label for 悬空,checkboxes / radio / rating / file 的 for 落在不可 label 的 div 上 #3961 当初换掉 for 的原因)。

三者可能要分组合用:grid / slider / signature 走 3,D 组走 1 或 2。

参考

Activity

  1. added theissue type on Aug 17, 2026
  2. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

    Triage: needs-user-decision, type Bug. The card itself says "needs a direction ruling, do not extrapolate from #4788", and triage agrees: the candidate fixes disagree on whether the answer is a semantic re-mapping (disabled → readonly, changing a deliberately-distinguished classification), a new public contract key (a display-only declaration on ComponentMeta), or per-widget pass-through — and adding a contract key is the manual floor.

    ⚠️ Decide together with #4871 — same design axis, two hosts. #4871 (metadata-admin's SchemaForm + bare WIDGETS record) needs a per-widget labelling/display declaration for exactly the same reason this card's option 2 does. One ruling on "how does a host learn what a widget will render" should settle both; ruling them separately risks two divergent declaration shapes for one question.

    Decision facets:


    Generated by Claude Code

  3. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

    Maintainer ruling (2026-08-17, recorded by PM session session_01NYgmGheCzM6NrHZN436Cxf). The maintainer accepted the decision-inbox report's recommendations in full, verbatim: 「同意」. Ruled together with #4871 — one declaration shape settles both hosts:

    The declaration: the already-ruled ComponentMeta.labelling vocabulary (#3961) extends to a three-value closed enum — 'control' | 'group' | 'display' — as the single repo-wide answer to "how does a host learn what a widget will render". ⛔ No host-local variants. The declaration slice expands a public contract key ⇒ claude-fable-5 for that slice.

    This card's landing:

    Label flipped needs-user-decision → pm:queue in the same stroke. Sibling ruling on #4871.


    Generated by Claude Code

  4. yinlianghui commented on Aug 17, 2026

    @yinlianghui
    ContributorAuthor

    【来自 #3318 残留批的实测 —— 不认领本单,只报两条:七分之二已在别处交付,以及本单「方案 3」对其中两个不成立】

    实施 #3318 的账本残留时同一套 probe 重跑了本单的七行,读数与本单一致(for=DANGLING / hostIdEl=NONE / consumers=0 全中)。PR #4933(draft)作为 #3318 的交付,顺带关掉了这七行里的 slider 与 signature 两行:

                    修前                                    修后
    slider     for=DANGLING hostIdEl=NONE consumers=0   name=IDREF hostIdEl=span[role=slider] consumers=1
    signature  for=DANGLING hostIdEl=NONE consumers=0   name=IDREF hostIdEl=div[role=group]   consumers=1
    

    剩余五行未动: formula / summary / auto_number / vector / grid。本单继续持有它们,D 组那条方向裁决(方案 1 vs 2)没有被本次交付预判掉。

    ⚠️ 一条要更正的地方: 本单「方案 3」对 slider / signature 单独用是不够的

    本单正文写:

    widget 侧各自展开 pass-through —— grid / slider / signature 有真控件,这条对它们大概是对的(host id 落到可 label 元素上,for 自然解析)

    前半对、括号里那半不对,而这正是实施时踩到的一步。这两个 widget 确实有真控件,但控件本身不是 labelable 元素:

    • slider 的控件是 Radix 的 span[role=slider] thumb;
    • signature 的绘制面是 canvas。

    label for 只能寻址 HTML 的 labelable 元素(button / input / meter / output / progress / select / textarea)。所以只做方案 3,for 会从「悬空」升级成「指向一个不可 label 的元素」—— getElementById 找得到、无障碍树里依然没有名字。probe 里 for=RESOLVES 会变绿而名字仍然缺失,是个会骗人的读数(本单的 RESOLVES-LABELABLE 那个写法是对的,值得保留成唯一口径)。

    ⇒ 这两个的正解是方案 3 + labelling: 'group' 声明(#3961 的 IDREF 路子),且是 file 的形态而非复合体的形态: 一个不可 label 的控件,不是一组子输入。PR #4933 即按此做——slider 的名字与描述直接落在 thumb 上(它的 slider 角色本身就能承载名字,不加多余的 group 包裹层);signature 无可聚焦控件,落在 role="group" 容器上。

    对 grid 的推断请重新量: 它在同一 probe 里 focusable=1 [button],控件是个 button(labelable),所以方案 3 对它可能真的够——但这一条本次没有实测,别按上面两行外推。

    与 #3318 的边界

    #3318 只有 aria-invalid 一根轴,本单是 id / for / describedby 三根。两单在 slider / signature 上同因同治,所以 PR #4933 一次都关了;剩下五行的 aria-invalid 侧已在 #3318 的账本里按语义分流定案(四个只读展示型 + grid 各有归属),不代本单裁 id/describedby 侧的方向。


    Generated by Claude Code

  5. self-assigned this
    on Aug 17, 2026
  6. yinlianghui commented on Aug 17, 2026

    @yinlianghui
    ContributorAuthor

    认领(objectui 分片 PM 座席,批次 21)。Session session_01GTRjn8xBqp75dk7kFupVRt,分支 claude/issue-4857-labelling-display,os-dev 子代理实施。

    实施依据 = 05:03Z 维护者裁定 + 06:36Z 实测更正,范围收敛后:①ComponentMeta.labelling 扩为 'control' | 'group' | 'display' 三值闭枚举(公开契约键扩展 —— 按裁定指定,本切片由 claude-fable-5 执行;与用户「默认 opus」的一般指令相比这是维护者对具体切片的显式指定,从其具体);②display 四(formula/summary/auto_number/vector)声明 'display' 并接 #4788 已裁的 host 容器;③grid 先重测(06:36Z 更正明言别按 slider/signature 外推 —— 它的控件是 labelable button,方案 3 可能够,量了再改);④配套注册表级「registered ⇒ declared」门(裁定点名的漏声明静默退化陷阱);⑤⛔ 方案 1(disabled→readonly 重映射)维持否决,ObjectForm.tsx:687 一字不动;⑥slider/signature 已由 PR #4933 交付,不重做。

    文件面:packages/core(ComponentMeta)、packages/components(form renderer host 分支)、packages/fields(五 widget + 注册表)—— 与在飞 #4989(plugin-form redirect 面)/#4917(renderers/basic/span)/#4874(custom/filter-builder)不相交。姊妹单 #4871 持有,Blocked-by 本单(其 meta 层复用本单落地的声明类型)。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions