Skip to content

docs(plugin-form): 文档站 Form Field 参考块与校验示例按 FieldValidationRules 真身重写 (#5118) - #5130

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-5118-form-mdx-validation-mirror
Aug 18, 2026
Merged

docs(plugin-form): 文档站 Form Field 参考块与校验示例按 FieldValidationRules 真身重写 (#5118)#5130
yinlianghui merged 1 commit into
mainfrom
claude/issue-5118-form-mdx-validation-mirror

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #5118

content/docs/plugins/plugin-form.mdx### Form Field 参考块与
### Form with Validation 示例,是 #5075(README 半,已闭,PR #5100)的文档站镜像:
README 侧修完,content/docs/** 的同内容一行未碰。半径严格限于这两块 + 被它们连坐的
门表项 + changeset。

前提验证:卡面三条判断逐条复测,全部成立

1. ValidationRule 这个类型名全仓不存在。 声明式 grep 零命中:

git grep -nE "(export )?(declare )?(const|type|interface|class|enum) ValidationRule($|[^A-Za-z])" -- packages content
# rc=1,无输出
# (写成不含反斜杠转义的等价式,避免正文里的 backslash-b 落成真控制字节 —— #5140/#5157 的教训)

零命中的邻近词反查只找到 ValidationRuleSchema(spec 的对象级 validations,
另一层)、DesignerValidationRuleObjectValidationRuleAdvancedValidationRule
ValidationRuleDraft —— 没有一个是 FormField.validation 的类型。裸名 ValidationRule
在改前全仓仅两处:本页 :58,以及 PR #5100 changeset 里记录这件事的那句话。
真身是 FieldValidationRules(packages/types/src/form.ts:744),按规则名开键的对象。

2. 数组拼法正是让校验静默失效的那个写法。 读点复核(行号未漂移):

packages/components/src/renderers/form/form.tsx:1651  // Build validation rules
packages/components/src/renderers/form/form.tsx:1652  const rules: any = {
packages/components/src/renderers/form/form.tsx:1653    ...validation,

数组展开进对象字面量得到数字键,而 react-hook-form 的字段校验器只解构固定集合:

spread keys = ["0","1"]
RHF destructure = {}            // required/maxLength/minLength/min/max/pattern/validate 全 undefined

真渲染器实测(ComponentRegistry.get('form') + testing-library,jsdom):旧片段原样
喂进去,minLength: 3 的字段拿到两字符值 ab —— 表单提交成功,payload
{"username":"ab"},页面上零提示。改后的对象形同一输入被拦住,显示 Min 3 characters

3. defaultValue / className 未声明,type / label 可选。dist 产物
(packages/types/dist/form.d.ts,pnpm --filter @object-ui/types build 后)复测:

members: 24                       // 23 个声明键 + 1 个 [key: string]: any
REQUIRED (non-optional, excl index sig): ["name"]
has index signature: true
defaultValue declared: false
className declared: false

一处对卡面措辞的实测修正,已写进页面而不是照抄:卡面说这两键「被索引签名吞掉,
永不被读」。渲染器把未解构的剩余键 ...fieldProps 一路转发给解析出的组件
(form.tsx:2031),实测一个字段级 className 确实落到内置 input 的 class 上
(探针:… md:text-sm PROBE-CLASS)。所以准确的说法不是「永不被读」,而是「不在契约里」:
页面写的是它不是声明键、能不能落到元素上取决于组件是否透传、契约不做承诺。
defaultValue 则实测确实不播种(字段级 defaultValue: 'Beijing' 下输入框值为 "",
表单级 defaultValues 下为 "Beijing")—— 控件由 react-hook-form 托管,初值来自表单。

改了什么

  • ### Form Field:不再现场声明本地类型。文档里手写的 interface 在任何地方都不
    参与编译,永远「编译通过」,这正是它漂移至此的成因(PR docs(plugin-form): README 的 Schema API 与 Examples 按 form 真读的键面重写 #5100 的教训)。改为对真身
    FormField 的键表引用(23 键,逐条对 dist 与读点亲测),外加一张「不是键」表
    (defaultValue / className 各自该写什么),以及 FieldValidationRules 的规则表。
  • ### Form with Validation:改成按规则名开键的对象,并带 FormSchema 类型标注 —— 标注
    本身是示例的一部分,因为 FormField / FormSchema 都有索引签名,不标注的
    const schema = { … } 写什么都过。另附一份 JSON 变体供元数据作者照抄,并写明
    pattern / validate 是 JSON 这条路表达不了的两条(JSON 没有正则字面量和函数)。
  • 三条此前只能靠试出来的事实进页面:validation.required 只供消息、是否必填由字段
    required / requiredWhen 决定(读点 delete rules.requiredform.tsx:1700);
    不存在 email 规则名,邮箱校验是 pattern;手写 schema 的 pattern.value 必须是
    RegExp。
  • scripts/check-doc-component-types.mjs:本页那两条 DOC_TYPE_EXEMPTIONS连坐
    删除。它们以「ValidationRule discriminant under a field's validation[]」为由豁免
    minLength / maxLength —— 这句理由的每个分句都是本次要清掉的虚构。改后页面不再在
    代码块里拼这两个 type 字面量,条目按门自身的规则变 stale-exemption(实测先红),
    删掉后转绿。
  • scripts/__tests__/check-doc-component-types.test.ts:加一个 content/docs/plugins/plugin-form.mdx 的 Form Field 参考块与「Form with Validation」示例是 #5075 的文档站镜像:validation 数组拼法让校验静默失效,ValidationRule 全仓不存在 #5118 的 pin(3 条断言),
    理由见下面的反向验证 (a)。

验证

结果
node scripts/check-doc-component-types.mjs rc=0,✅ Every documented component type is registered.
node scripts/check-doc-links.mjs rc=0
node scripts/check-control-bytes.mjs rc=0(4551 个跟踪文本文件)
改动文件自扫(grep -naP 控制字符类,覆盖门不扫的 0x01 一族) 零命中
pnpm exec turbo run type-check --concurrency=2(仓根,flock 串行) 81/81 successful
pnpm exec vitest run scripts/__tests__/check-doc-component-types.test.ts 27 passed(含新增 3)
node scripts/check-changeset-presence.mjs rc=0,no changeset is owed(见下)

changeset 自判:门只守 fixed 组包的 PKG/src/**,本 PR 四个文件都不在其中,故
不欠。仍然补了一份 —— 这是一页已发布文档的实质重写,与 PR #5109(同形状,补了)
同级;PR #5119 那种 17 行的 import 更正没补,是另一个量级。

改后示例 strict 编译(从 mdx 里按发布原文抽出,tsc --ignoreConfig --strict --noEmit,
解析真实的 @object-ui/types dist):

=== example-good.ts ===            rc=0
=== neg-missing-name.ts ===        rc=2
  error TS2741: Property 'name' is missing in type '{ type: string; label: string; … }'
  but required in type 'FormField'.
=== neg-array-validation.ts ===    rc=2
  error TS2559: Type '{ type: string; value: number; message: string; }[]' has no
  properties in common with type 'FieldValidationRules'.

两个负控都按事先书面预判落地(TS2741 / TS2559),页面里引用的正是 TS2559 这条原文。

名集合核对:页面新引入的每个类型名都对 dist 复核 —— FormFieldFormSchema
FieldValidationRulesFieldConditionSelectOptionRadioOptionDependsOnInput
七个全部 DECLARED,且都在 packages/types/dist/index.d.ts 的导出面上;
buildValidationRulespackages/fields/src/index.tsx:2246 导出;
@objectstack/formularesolveFieldRuleState 真正 import 的引擎。

探针分层(案头版):FormField / FormSchema 带索引签名 ⇒ 「未声明键」的探针绿
不作数,defaultValue / className 的证据是声明成员表 + 读点 + 上面那条 className
实测
,不是探针;有牙的是必填键(TS2741)与字面量键(门的 unregistered-doc-type)。

反向验证(先书面预判,再跑;commit 后变异、跑完还原)

(a) 旧块回填 —— 预判「按门分层」,实测与预判一致,含一条对本仓不利的实话。
两半分开灌:

  • a1 只回填 ### Form Field 旧参考块(带 ValidationRule[]):预判各门全绿,
    实测 check-doc-component-types / check-doc-links / check-control-bytes 全部
    rc=0。原因是门只扫代码块里带引号的 type: 字面量,而 type: string; 这种
    伪代码不带引号 —— 这一半在本仓没有任何既有门看得见,虚构可以原样回潮而 CI 全绿。
    这就是本 PR 补 pin 测试的唯一理由:a1 之下只有新加的 pin 红(3 条中 2 条)。
  • a2 只回填 ### Form with Validation 旧数组示例:预判,实测红 ——
    content/docs/plugins/plugin-form.mdx:282 / :283 [unregistered-doc-type] type 'minLength' / 'maxLength' (json)。诚实的一半:同一个变异拿 baseline 的门文件
    (0046d8f,豁免尚在)去跑是绿的。也就是说这一半的牙是本 PR 删豁免新造出来的,
    不是既有的。

(b) 塞假名自证:在 JSON 示例里把 type 改成 form-5118-fake-name,预判红 ——
实测 content/docs/plugins/plugin-form.mdx:333 [unregistered-doc-type] type 'form-5118-fake-name' (json),rc=1。证明本文件在门的判定面上是活的,前面的绿不是空绿。

(c) 自选方向:改后为真的可运行证据。发布后的 JSON 块从 mdx 里读出来、直接喂给
真 form 渲染器
(不是手抄的副本):

[F] published JSON block = {"type":"form","fields":[{"name":"username","type":"input",
    "label":"Username","required":true,"validation":{"minLength":{"value":3,
    "message":"Min 3 characters"},"maxLength":{"value":20,"message":"Max 20 characters"}}}],
    "submitLabel":"Sign Up"}
[F] the published snippet blocked a 2-char username, as documented

配套的 pattern 方向也实测:string 值 —— 不匹配的值照样提交、零提示;RegExp 值 ——
拦住。探针是 scratch(5118- 前缀),已随收尾删除,不在本 PR 里。

与相邻卡的关系


Generated by Claude Code

…#5118)

`content/docs/plugins/plugin-form.mdx` 带着 `packages/plugin-form/README.md`
在 #5075 修掉之前的同一批缺陷 —— README 半修完、文档站镜像一行未碰。

`### Form Field` 现场重声明了一个本地 `interface FormField`,其
`validation?: ValidationRule[]` 双重错:`ValidationRule` 这个类型名全仓不存在
(声明式 grep 零命中),`validation` 也不是数组,真身是
`FieldValidationRules`(packages/types/src/form.ts:744),按规则名开键的对象。
同一块还把未声明的 `defaultValue` / `className` 列成键(dist 产物复测:FormField
共 23 个声明键 + 一个 `[key: string]: any` 索引签名,必填只有 `name`),并把
`type` / `label` 标成必填 —— 与真身相反。本次不再现场声明本地类型:文档里手写的
`interface` 在任何地方都不参与编译,正是它漂移至此的成因;改为对真身键面的引用,
每一条都对 dist 产物与渲染器读点亲测。

`### Form with Validation` 按那个错的类型把校验写成了数组,而这个拼法是静默失败:
该键唯一读点把值展开进交给 react-hook-form 的规则对象
(`const rules: any = { ...validation }`,
packages/components/src/renderers/form/form.tsx:1652),数组展开得到数字键
(`{ '0': …, '1': … }`),RHF 一个都不认,规则全丢且不抛错。真渲染器实测:旧片段在
`minLength: 3` 下提交两字符用户名、无任何提示;改后片段拦住它。示例改带
`FormSchema` 标注,数组拼法从运行期惊喜变成编译错误(实测 TS2559),另附一份
JSON 变体供元数据作者照抄。

三条此前只能靠试出来的事实写进页面:`validation.required` 只供消息、是否必填由字段
的 `required` / `requiredWhen` 决定;不存在 `email` 规则名,邮箱校验是 `pattern`;
手写 schema 的 `pattern.value` 必须是 RegExp —— RHF 只在值 `instanceof RegExp` 时
才应用 pattern(dist 实测),把声明的字符串编译成 RegExp 的是对象元数据那条路
(`buildValidationRules`)。

`scripts/check-doc-component-types.mjs` 里本页的两条 DOC_TYPE_EXEMPTIONS 一并删除:
它们以「ValidationRule discriminant under a field's `validation[]`」为由豁免
`minLength` / `maxLength`,该理由的每一个分句都是本次要清掉的虚构;删掉后数组拼法
一旦回潮,这道门就会红。

Fixes #5118

Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

PM 验收:ACCEPT(session_01GTRjn8xBqp75dk7kFupVRt,objectui 分片;批次 22 收官单)

实物核验:merge-base 0046d8f8c;head bfe474992;4 files, +241/−31;标识 grep msg/diff 双零,trailer 唯一正确;releases/控制字节(含 ESC 位)零 ✅。门禁文件连坐亲读:删掉的两条 DOC_TYPE_EXEMPTIONS 恰是理由文本自身复述 ValidationRule 虚构的那两条,留下的解释注释把「豁免理由两半都是虚构」写透;与 PR #5103(不同 (file,value) 键)/PR #5128(不同文件)零冲突 ✅。CI 亲读:18/18 必需检查全 completed success(仅 Live E2E informational 在跑,非门)✅。

验收要点:

  1. 连坐删豁免是正确的深修:不删则门自己的 stale-exemption 报红(实测先红后绿)—— 门的自检机制在这单里如设计工作了一次;3 条 pin 的存在理由由 (a1) 直接证明(参考块伪代码不带引号 type,没有任何既有门看得见,虚构可原样回潮而 CI 全绿 —— 回灌 finding: 没有门禁核对包内 README 的自包导入名 vs 真实导出面 —— #5002 家族 7 个包的漂移全靠人工巡查发现(附已验证的检查器原型) #5043)。
  2. (a2) 的诚实分层是范本:「示例半的牙是本 PR 删豁免新造的,不是既有的」—— 用 baseline 门文件跑同一变异证明,不把新造的牙冒充既有覆盖。
  3. 改后为真的可运行证据:发布 JSON 块直接喂真渲染器(非手抄副本),minLength 拦截 + 提示可见 + onSubmit 未调;顺带实测 pattern string 值静默失效(手写 FieldValidationRules 在唯一读点既不校验也不归一:pattern.value 写成 string(类型明确允许)被 react-hook-form 静默忽略,未识别的规则名同样静默丢弃 #5099 决策箱的又一数据点)与 defaultValue 字段级不播种。
  4. 卡面措辞实测修正:className「永不被读」→「不在契约里」(经 ...fieldProps 落到内置控件 class 实测)—— 修正入页、README 侧另立 finding(plugin-form): README 说字段级 className「只在 section-divider 上被读」,实测它经 ...fieldProps 展开落到内置控件的 class 上 #5131(finding,量词错误)而不外推,premise 判定不受影响。
  5. changeset 自判不欠仍按 docs(plugin-view): 文档站页面整片示例按 ObjectView 真读的键面重写 (#5088, #5086 的 view 三分之一) #5109 先例补(实质重写量级与 17 行 import 更正不同)—— 量级判断采信;curl 假绿再次自纠(走 MCP 复核)。

三件套照常:本评论 → undraft → auto-merge(SQUASH)。#5075#5118#5131 链至此:README 半、文档站半均落,残余为观察类量词修正。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 18, 2026 03:59
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 18, 2026
Merged via the queue into main with commit 958d757 Aug 18, 2026
19 of 20 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-5118-form-mdx-validation-mirror branch August 18, 2026 03:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants