docs(protocol): widget-contract 收口「谁渲染什么」——校验文案归宿主,required 只反映为 aria-required (#4866) - #5914
Merged
Merged
Conversation
…-required (#4866) objectui#3222 按方向 1(objectui 跟随 spec)落地后(objectui PR #3289), 本页两处教法会让照抄它的第三方 widget 出现双份显示,按裁定收口。契约本身 是对的,`packages/spec/src/ui/widget.zod.ts` 一个字未改。 1. `CustomRatingField` 示例删掉 `{error && <span className="error">{error}</span>}`。 在 #3222 之前 widget 拿不到 `error`,这行永远不执行,问题是隐性的; PR #3289 让表单渲染器真的把 `fieldState.error?.message` 传下来之后, 宿主的 FormMessage 与 widget 会把同一句话画两遍。示例改为把 `error` 只用于 `aria-invalid`,并顺手用上一直被解构但从未使用的 `required` (`aria-required={required || undefined}`,与 objectui 渲染器同一写法)。 2. `required` 的「Indicate the required state visually」改为限定表述: 必填标记 `*` 由宿主的 FormLabel 拥有,widget 只把它反映成控件上的 `aria-required`。这正是 #3222 决定不把 `required` 布尔下沉到 widget props 的理由(objectui#3290:真正需要的是 aria-required,不需要新增契约键)。 同一声明块里 `error` 的注释原文是「Validation error message to display」, 与第 1 点是同一处失实的两半,一并改成「信号而非待渲染文案」。 新增「Who Renders What」一节固定三项归属(aria-invalid 归 widget、文案归宿主 FormMessage、必填标记归宿主 FormLabel),并记下 PR #3289 实测到的 spread 顺序陷阱:宿主的 FormControl(Radix Slot)本来就递正确的 aria-invalid, widget 在 spread 之后写一个来源不同的 aria-invalid 会把它覆盖成 false。 `:48` 的「the source of truth is FieldWidgetPropsSchema」保持不动。 Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
os-zhuang
marked this pull request as ready for review
August 6, 2026 11:41
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4866
纯 docs,单文件:
content/docs/protocol/objectui/widget-contract.mdx。⛔
packages/spec/src/ui/widget.zod.ts一个字未改 —— 契约本来就是对的,收口的是教法。真值核实(objectui origin/main 实测,不是照抄 issue)
objectui#3222 已按方向 1 落地(PR #3289,已合并),
packages/spec一个字未改。逐条对代码:error是字符串(消息本体),由表单渲染器从fieldState.error?.message产出,字段有效时为undefinedpackages/components/src/renderers/form/form.tsx:1574error只用来驱动aria-invalid;文案由< FormMessage / >渲染packages/fields/src/widgets/types.ts:140-155的 doc comment +form.tsx:1614required不在 objectui 的 widget props 里,读props.required是编译错误packages/fields/src/__tests__/spec-symbol-batch7.test.ts的_RequiredIsAbsentaria-required,由渲染器直接注入,48 个 widget 一个没改form.tsx:1608'aria-required': required || undefined*只有一个作者(宿主< FormLabel >),且已移出可访问性树form.tsx:1484起与 issue 表述的一处出入,已按实测写:issue 说「
error是布尔信号」。实测它是string(spec 声明error?: string,渲染器传的是消息文本)。所以文档写的是「当信号用,不是待渲染文案」——presence 驱动aria-invalid,文案归宿主。写成「布尔」会与FieldWidgetPropsSchema的类型直接矛盾。改了什么
{error && …})。在 fix(identity): close generic-write apiMethods hole on sys_presence & sys_metadata (#3220) #3222 之前 widget 拿不到error,这行永远不执行,问题是隐性的;PR chore(lint): org-identifier authoring guard for deprecated session.tenantId (#3280 follow-up) #3289 让渲染器真的把它传下来之后,照这份文档写出来的第三方 widget 会把同一句话画两遍。顺带用上了一直被解构却从未使用的
required:aria-required={required || undefined}—— 与渲染器里完全同一个写法(|| undefined而非String(required),可选字段就不带这个属性)。删行不会造成未使用参数:error仍被aria-invalid={!!error}读。required一节措辞限定:「Indicate the required state visually and validate accordingly」→ 反映为控件上的aria-required,不要自己画必填标记(宿主的 label 拥有*)。error的注释原文是「Validation error message to display」,与第 1 点是同一处失实的两半 —— 只删示例那行、留着这句「to display」,这份 PR 自己就自相矛盾了,所以一并改。这是第 1 点的完成范围内,不是顺手扩面。< FormControl >是 RadixSlot,本来就递正确的aria-invalid;widget 在 spread 之后写一个来源不同的aria-invalid会把它覆盖成false—— 七个内置 widget 就是这样从未播报过失效字段)。:48的「the source of truth isFieldWidgetPropsSchema」保持不动 —— 它是排除方向 3 的关键证据。验证
docs-only,没有可跑的单测,所以验的是「这页仍然构建得出来、且教的是实测到的事」:
pnpm check:doc-authoring→362 files clean(含 self-test)pnpm check:nul-bytes→scanned 5704 tracked text file(s); no raw ASCII control bytes(含 self-test);另对本文件单独自扫[\x00-\x08\x0b\x0c\x0e-\x1f\x7f],0 命中pnpm check:docs-audit-scope→scope is in sync with content/docs/: 178 hand-written doc(s)pnpm --filter @objectstack/spec run check:skill-examples→✅ 205 prose examples type-check(本页改动的两个块没有{/* os:check */}标记,也没新增标记;跑它是为了确认没有 orphan marker)@mdx-js/mdx+remark-gfm编译本页,42246 bytes JS,通过。< FormMessage / >语法完全合法,要到渲染期才炸。所以另写探针断言编译产物里的_missingMdxReference列表:本页只有Callout/Card/Cards(改动前就在用),表格与正文里的组件名都留在行内代码里。探针自身先被反向验证过:第一版探针匹配错了模式(_components.X),对故意加了裸标签的副本报绿 —— 按预判方向应当转红,于是改为匹配_missingMdxReference("X",副本转红(FAIL: FormMessage parsed as COMPONENT(s))、真文件保持绿。先有假绿被抓出来,这条绿才算数。Changeset
无 —— 纯 docs,不发布任何包,按仓库纪律加
skip-changeset标签。🤖 Generated with Claude Code
https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE
Generated by Claude Code