Skip to content

docs(plugin-form): README 的 Schema API 与 Examples 按 form 真读的键面重写 - #5100

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-5075-form-readme-keys
Aug 18, 2026
Merged

docs(plugin-form): README 的 Schema API 与 Examples 按 form 真读的键面重写#5100
yinlianghui merged 1 commit into
mainfrom
claude/issue-5075-form-readme-keys

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes#5075

packages/plugin-form/README.md## Schema API## Examples 两节整段重写。三组是连体的:只改参考块、留着两个示例继续教 validation: [ … ],同一个 README 会自相矛盾。纯文档改动 —— 不改渲染器行为,不动 form.tsx 的读点,未新增任何能力、导出或类型。

基线 origin/main = 9fbb9b52ffa92432019a9a5ebfdd6a671d891f77

前提门:卡面三组读数逐条复测

卡面读数复测
README 三处 validation: [ … ]成立(改前 :387 / :456 / :468)
FieldValidationRules 是按规则名开键的对象(types/src/form.ts:744)成立
唯一读点 components/src/renderers/form/form.tsx:1652{ ...validation }成立
multi-step-form 全仓仅该 README 命中成立
本包六个 register 调用成立(plugin-form/src/index.tsx :100 / :159 / :211 / :232 / :276 / :356)
steps 不在 FormSchema成立,且更强:packages/types/src零声明,全仓 .steps 读点全是 flow 执行日志(app-shell / console),与表单无关
FormField 真身 23 键(form.ts:898)成立(逐键点数 = 23)
参考块「只列了 23 个键里的 9 个、缺席 15 个」修正为 7 / 16 —— 被数进「列了」的 defaultValueclassName 本就不是真键;卡面的缺席清单漏了 inputType,而 README 自己的示例一直在用它
「五处漂移」成立,但证据分层:type/label 可选性与 validation 形由编译探针证明;defaultValue/className不能由探针证明(见「探针有牙 / 致盲」)

三组判定表

1 · validation 数组拼法 → 真身对象形

README 写的真身处置
validation: [{ type: 'email', … }](Basic Form)FieldValidationRulesemail 规则名pattern,正则取 buildValidationRulestype === 'email' 字段发出的同一条
validation: [{ type: 'minLength', value, message }, …](Form with Validation ×2)minLength?: { value: number; message: string } 等按名开键逐条改成对象成员;message 手写时不是可选的
validation?: ValidationRule[](参考块)validation?: FieldValidationRules见第 3 组
{ type: 'pattern', value: '^…$' } 的 string 正则手写面必须给 RegExp示例改 RegExp 字面量,并写明原因;类型侧的二义留给另立的 #5099
——validation.required 只提供消息新增说明:是否必填由字段自身的 required / requiredWhen 决定(form.tsx:1704 只把它当 message,presence 来自 resolveFieldRuleState)

2 · multi-step-form / steps → 真实入口

README 写的真身处置
type: 'multi-step-form'全仓无任何注册认领该名object-form + formType: 'wizard'(ObjectForm.tsx:255 的路由,条件是 formType === 'wizard' && sections?.length)
steps: [{ title, fields }]任何 form schema 都无 steps步骤即 sections,每 section 一步;ObjectFormSection.fields(string | FormField)[]
无 dataSource 的内联字段section 内联 runtime FormField 直通(sectionFields.ts:164)第二个示例用导出的 WizardForm + 内联字段,dataSource 省略
onSubmitWizardFormSchema 上没有这个键;无 dataSource 时回调是 onSuccess(WizardForm.tsx:485)onSuccess
——另一条真实路线补一句 mobile: { stepper } 的扁平表单自动分步(ObjectForm.tsx:1279),避免读者重新发明

⛔ 未注册任何新 type、未新增任何键去把旧拼法变真。

3 · FormField 参考块 → 真身 23 键

参考块真身判定
type: string(必填)type?: string错;缺省 'input'
label: string(必填)label?: string错;缺省时不渲染 label 元素(校验消息才回落到 name)—— 卡面未涉及,按读点如实写
validation?: ValidationRule[]validation?: FieldValidationRules错;ValidationRule 这个名全仓不存在
defaultValue?: any未声明错;表单级是 FormSchema.defaultValues
className?: string未声明错;宽度是 span / colSpan,栅格是 FormSchema.fieldContainerClass口径:字段级 className 确有一个读点,但只在 type: 'section-divider' 这个伪字段上(form.tsx:1646),README 如实标注
缺席 16 键iddescriptioninputTypewidgetoptionsconditionvisibleOnvisibleWhenreadonlyWhenrequiredWhendependsOnhiddenreadonlyfieldcolSpanspan全部补进键表

参考块不再现场声明本地 interface FormField(裸声明永远编译通过,正是本次漂移的成因),改为对真身声明的键表 + FormSchema 键表,示例逐块标注真类型。

静默失效:前提复现

第 1 组最要紧的一半是运行时静默,而不是类型红。机制复现(node,零 React):

const validation = [
{ type: 'minLength', value: 3, message: '…' },
{ type: 'pattern', value: '^[a-zA-Z0-9_]+$', message: '…' },
];
const rules = { ...validation }; // form.tsx:1652 逐字
spread result keys : [ '0', '1' ]
recognised by RHF : []

react-hook-form 7.85.0 的字段校验器只解构固定集合(dist/index.cjs.js):

const{ref:c,refs:f,required:m,maxLength:y,minLength:p,min:g,max:b,pattern:V,validate:A,name:x,valueAsNumber:S,mount:k}=e._f

数字键不在其中 —— README 教出来的 email / minLength / maxLength / pattern 一条不跑,且没有任何报错。照抄的表单看上去有校验,实际没有。

顺带同一读点的另一面(已另立 #5099,本 PR 不动代码):pattern.value 写 string 也是静默失效 —— RHF 只应用 value instanceof RegExp 的 pattern(Z=e=>e instanceof RegExp),把声明的 string 编译成正则的只有元数据路线的 buildValidationRules(fields/src/index.tsx:2303)。README 因此教 RegExp 字面量。

探针有牙 / 致盲(先实测,再声称)

探针一律对构建产物dist/index.d.ts,strict,逐块单文件编译。

有牙(声明键的类型压过索引签名):

探针读数
三处 validation: [ … ] 标注 FormSchemaTS2559: Type '{ type: string; message: string; }[]' has no properties in common with type 'FieldValidationRules' ×3
type: 'multi-step-form' 标注 FormSchemaTS2322: Type '"multi-step-form"' is not assignable to type '"form"'
real → doc(参考块)TS2322 … Types of property 'type' are incompatible … Type 'string | undefined' is not assignable to type 'string'
real → { name; label }TS2322 … 'label' … 'string | undefined' is not assignable to 'string'
const f: FormField = {}TS2741: Property 'name' is missing —— name 是唯一必填键
doc → real(参考块)TS2322 … 'validation' … 'DocValidationRule[]' has no properties in common with type 'FieldValidationRules'

致盲(实测确认,不冒充键面证据):

探针预判实测
steps: [ … ] 挂在 FormSchema不报错 —— FormSchema extends BaseSchema,base.ts:318[key: string]: any不报错 ✓
{ name: 'x', defaultValue: 'draft' } 标注 FormField不报错 —— FormField 自带 [key: string]: any(form.ts:1034)不报错 ✓
{ name: 'x', className: 'col-span-2' } 标注 FormField同上不报错 ✓

所以 steps / defaultValue / className 三条由「声明缺席 + 读点 grep」证明,PR 与 README 都按这个口径写。反面对照:WizardFormSchema 没有索引签名,标注它的示例里发明一个键就是真编译错误 —— README 也把这条差别写下来了。

改后:两个 after 探针(重写后的 Basic / Validation / 两条 wizard 示例,含 WizardFormProps 省略 dataSource)rc=0

反向验证(预判先写,实测在后)

  • (a) 旧数组回填 → 预判红。 实测红:TS2559 ×3(见上)。
  • (b) 双向 pin 各砍一向 → 预判各漏一半。 实测部分不符,如实记录:
    • 只留 doc → real:报 validation 形,对 type/label 被写成必填沉默 —— 更严格的源赋给更宽松的目标本就合法,这一向结构上看不见「文档过度必填」。与预判一致(plugin-grid README 教 gridComponents 手动注册与 GridSchema / GridColumn 类型 —— 前两者不存在,GridSchema 这个名字在 types 里是 CSS Grid #5013 家族形)。
    • 只留 real → doc:报 type 可选性。
    • 追加的第三次砍(real → doc,但把 doc 的 validation 改回数组、optionality 修好):validation(FieldValidationRules is missing the following properties from type 'DocValidationRule[]': length, pop, push …)。所以两向并不是「各自独占一个事实」:TS 每个赋值只报第一个不相容属性,覆盖面取决于属性顺序。双向仍然必要(方向 2 看不见过度必填),但理由是这个,不是模板预设的「一向一事实」。
  • (c) 名集合核对无新假名。 见下。

名集合核对(#5043 规格)

对 README 的全部代码块做 AST 解析,收集 import 绑定与类型引用,逐个对构建产物的真实导出集合核验:

改前(origin/main 的 README):
local FormField (declared in the block) ← 本地假声明,正是本次漂移
UNKNOWN ValidationRule — no module exports this name
verdict: 1 problem(s)
改后:
README code blocks parsed: 16
imported bindings: 53 个全部 OK(plugin-form / types / core / data-objectstack)
type references : FormField, FormSchema, ObjectFormSchema, WizardFormSchema —— 全部 imported 且真实
verdict: clean

门禁

命令结果
pnpm exec turbo run type-check --concurrency=2(仓根,经 flock)Tasks: 81 successful, 81 total,rc=0
node scripts/check-doc-links.mjsLinks are valid across 13 scan roots.
node scripts/check-control-bytes.mjsOK (scanned 4525 tracked text file(s))
改动文件自扫 grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]'零命中

范围

只动 packages/plugin-form/README.md + 一个 changeset(@object-ui/plugin-form patch,docs 级)。与在飞 #5066 / #5062 / #5063 零交集。

实施中扫出、未夹带进本 PR 的两条,已另立卡:


Generated by Claude Code

三组连体键面漂移,一次改对(#5075 的三组读数逐条复测通过,另修正两处计数)。
判据一律取构建产物 `dist/index.d.ts` 在 strict 下的真编译。
1. `validation` 写成 `[{ type, value, message }, …]` 三处 —— 真身是
`FormField.validation?: FieldValidationRules`,按规则名开键的**对象**
(`types/src/form.ts:744`)。类型侧 TS2559 ×3;运行时侧是**静默**的,这是本
组最要紧的一半:唯一读点 `components/src/renderers/form/form.tsx:1652` 写的是
`const rules: any = { ...validation }`,把数组展开进对象字面量得到
`{ '0': …, '1': … }`,而 react-hook-form 的字段校验器只解构
required/maxLength/minLength/min/max/pattern/validate/valueAsNumber ——
README 教出来的规则一条不跑且零报错。顺带写进两条读者猜不到的事实:
`validation.required` 只提供**消息**(是否必填由字段自身的 required /
requiredWhen 决定),手写的 `pattern.value` 必须给 RegExp(RHF 只应用
`value instanceof RegExp` 的 pattern;把声明的 string 编译成正则的是元数据
路线的 `buildValidationRules`)。
2. `type: 'multi-step-form'` 全仓未注册,`steps` 也不是任何 form schema 的键
(`types/src` 零声明,`.steps` 的读点全是 flow 执行日志)—— 整个示例渲染出
未知组件占位,`steps` 里的字段从不被读。改教两个真实入口:`object-form` +
`formType: 'wizard'`(步骤即 `sections`,`ObjectForm.tsx:255` 的路由),以及
导出的 `WizardForm` 配 section 内联字段、无 dataSource(`onSuccess` 而非
`onSubmit`)。⛔ 未注册任何新 type 去把旧拼法变真。
3. `FormField` 参考块现场声明了一个本地 `interface FormField` —— 裸声明永远编译
通过。五行是错的(`type`/`label` 是**可选**;`validation` 见上;
`defaultValue`/`className` 不是声明键),`ValidationRule` 这个类型名全仓不
存在,且 23 个真键只列了 7 个。改为对真身声明的键表 + `FormSchema` 键表,示例
逐块标注真类型 —— 标注本身就是要点:`FormField` 与 `BaseSchema` 都带
`[key: string]: any`,不带标注的 `const schema = { … }` 写什么都能过编译,
发明的键也永远不是编译错误。
卡面两处计数如实修正:缺席的真键是 **16** 个(卡面列了 15,漏了 `inputType`
—— README 自己的示例在用它);「五处漂移」里的 `defaultValue`/`className` 两条
无法由探针证明,只能由声明缺席 + 读点 grep 证明(见 PR 正文的分层)。
纯文档改动:不改渲染器行为,不动 form.tsx 的读点,未新增任何能力、导出或类型。
Fixes#5075
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)25.3 KB350 KB
Entry fileindex-D86TVFgC.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)9.56KB3.59KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)8.92KB3.41KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)25.13KB5.40KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.79KB
auth (createAuthenticatedFetch.js)6.34KB2.43KB
auth (index.js)2.71KB1.22KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.02KB0.88KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)26.07KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.65KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)502.20KB112.21KB
core (index.js)4.11KB1.62KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)159.03KB44.08KB
fields (index.js)234.25KB58.48KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.35KB1.38KB
i18n (pickLocalized.js)3.69KB1.73KB
i18n (provider.js)23.12KB7.62KB
i18n (useDisplayLocale.js)2.84KB1.45KB
i18n (useObjectLabel.js)27.59KB6.63KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)39.16KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.35KB3.31KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.42KB1.42KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.81KB0.83KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.75KB18.37KB
plugin-chatbot (index.js)181.21KB43.14KB
plugin-dashboard (index.js)127.85KB32.73KB
plugin-designer (index.js)212.39KB42.83KB
plugin-detail (index.js)241.12KB60.43KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)123.21KB29.85KB
plugin-gantt (index.js)164.10KB39.87KB
plugin-grid (index.js)197.61KB53.03KB
plugin-kanban (index.js)52.72KB14.54KB
plugin-list (index.js)111.17KB26.99KB
plugin-map (index.js)18.72KB6.09KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)41.97KB11.33KB
plugin-timeline (index.js)26.68KB7.66KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)83.81KB20.49KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)27.53KB9.41KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.28KB0.68KB
react (schema-input.js)1.45KB0.83KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (index.js)4.77KB2.16KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)10.76KB3.17KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)6.92KB2.40KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.05KB1.52KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@yinlianghuiClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM 验收:ACCEPT(session_01GTRjn8xBqp75dk7kFupVRt,objectui 分片)

实物核验:merge-base 9fbb9b52f = 派发基线;head 4b63c89c6;2 files, +255/−52,文件面仅 packages/plugin-form/README.md + changeset,content/docs/releases/** 零触碰 ✅;标识 grep msg/diff 双零,trailer 唯一正确 ✅。CI 亲读:19/19 check 全 completed,17 success + 2 skipped(dependabot、coverage),零失败 ✅。

验收要点:

  1. 三组连体漂移一次改对,且修正了卡面本身:缺席真键 16 个而非 15(卡面漏了 README 自己示例在用的 inputType)、「列了 9 个」实为 7 —— 派发词要求逐字抄卡面,你复测后如实修正计数并写进三处记录,这是正确的优先级(卡面是输入,不是圣旨)。
  2. 反向验证 (b) 的诚实记录是本单亮点:双向 pin 各砍一向的预判「一向一事实」被第三次砍推翻 —— TS 每个赋值只报第一个不相容属性,覆盖面取决于属性顺序;双向仍必要,但理由修正为「方向 2 看不见文档过度必填」。预判不符如实记录勿套模板,正是这个形。
  3. 静默失效机制的零 React 复现({ ...validation } 展开数组得 ['0','1'] 键 + RHF 7.85.0 校验器解构清单)把「为什么数组形不报错也不生效」钉死在机制层,README 的散文有据可查。
  4. label 缺省「不渲染 label 元素」的读点修正(起草时写反、核读点后改正)—— dev 自纠不返工的又一正面样本。
  5. 范围纪律正确:## Integration with Data Sources 同文件漂移不夹带,另立 plugin-form README 的「Integration with Data Sources」示例把 dataSource / resource 写在 form schema 上 —— 两个键全仓零读取点 #5098;手写 FieldValidationRules 在唯一读点既不校验也不归一:pattern.value 写成 string(类型明确允许)被 react-hook-form 静默忽略,未识别的规则名同样静默丢弃 #5099(读点静默丢弃,A/B 契约题)带双轴分析交决策箱 —— PM 即将分诊。

三件套照常:本评论 → undraft → auto-merge(SQUASH)。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 18, 2026 00:07
@yinlianghui
yinlianghui added this pull request to the merge queueAug 18, 2026
Merged via the queue into main with commit e09f9e8Aug 18, 2026
20 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-5075-form-readme-keys branch August 18, 2026 00:08
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationplugin

Projects

None yet

2 participants

@yinlianghui@claude