refactor(fields)!: 文档字段例子改表单托管,删 registerFields + createFieldRenderer (#3910) - #3953
Merged
Merged
Conversation
…gisterFields + createFieldRenderer (#3910) Ruling B of #3798 (maintainer-confirmed), the remove side of #3308's "one registration path" intent. PR #3793 corrected the misleading `@deprecated` note first and is the safety net this builds on. The docs field demos rendered a BARE field node as a labelled, editable input, which no application can do: on the live path a bare field node has no host for its label or its value. That only worked because the docs site registered a demo-only adapter over the same `field:<type>` keys the live path owns. The field docs are a first-hand transcription source for AI authors, so they were teaching a shape that does not work. - catalog: 74 bare-node examples under `examples/schema-catalog/src/schemas/ fields-*` become `{ type: 'form', showSubmit: false, showCancel: false, fields: [...] }` (2 were already form-hosted). A node's `value` moves to the form's `defaultValues`: the form spreads react-hook-form's state after the schema props, so a field-level `value` is silently ignored (measured — field-level yields an empty input, form-level yields the value). All 74 round-trip to their original content with `value` relocated and nothing else added or dropped. - fields: delete `registerFields()` and `createFieldRenderer()`, plus the 37 static widget imports that existed only to feed the wrapper. Tombstones record why, and carry forward the `capability-multiselect` tombstone that lived inside the removed function. - site: `ObjectUIProvider` drops the `registerFields()` call for a side-effect import; the module's `registerAllFields()` is the one seam. - three widget comments credited the `field.field` nesting to the deleted wrapper, which never produced it — re-pointed at the form renderer's declared metadata slot (#3090), the real producer. The 26 `content/docs/fields/*.mdx` pages need no edits: each consumes only `<SchemaExample id>` (76 references, one per catalog example). New catalog pins: every `fields-*` example is form-rooted, carries no dead field-level `value`, and renders a real `form` with its label in the field's own chrome; three value-bearing examples assert the seeded value reaches the DOM. Reverse-verified — restoring either limb turns exactly these red.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Collaborator
Author
|
PM 验收(session_01GTRjn8xBqp75dk7kFupVRt):通过,转 ready 并挂 auto-merge。#3798 裁决 B 落地,#3910 独立批。 核验记录(head
out-of-scope findings #3951/#3952 由 PM 分诊(另评)。 Generated by Claude Code |
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 #3910
落地 #3798 裁决 B(维护者已确认),即 #3308「注册只剩一条路」意图的 remove 侧。PR #3793 先把误导性的
@deprecated注释改为如实说明,是本单的安全垫。基线 sha:
b14ab3afe(派发单写的cb5e32d73在开工时已被 main 推进,按当下显式 sha 建 worktree)。病根
文档的字段 demo 把裸字段节点渲染成带 label 的可编辑输入框,而真实应用做不到这件事:活路径上裸字段节点既没有 label 的宿主,也没有 value 的宿主。它之所以能渲染,是因为文档站另外注册了一个 demo 专用适配器,覆写了活路径拥有的同一批
field:类型键。字段文档是 AI 作者的一手抄写来源,于是它一直在教一种不成立的写法。实测(本单 probe,未入 commit):
四个部分
1. catalog 表单托管(74 个 JSON) —
examples/schema-catalog/src/schemas/fields-*下 74 个裸节点例子改为{ "type": "form", "showSubmit": false, "showCancel": false, "fields": [...] }(另有 2 个fields-select例子本来就是表单托管,未动,它们也是仓内既有先例)。关键:这不是无脑套壳。表单渲染器在 schema props 之后展开 react-hook-form 的状态,所以字段级
value会被静默覆盖(上面 B/C 两条实测)。因此 24 个带value的例子把值搬到表单的defaultValues,否则「只读展示值」「预勾选」「默认日期」这些例子的全部看点会在看起来正常的情况下丢失。改完做了机械等价性校验:74 个文件逐个与
HEAD版本对比,value归位后逐键完全一致,没有多加也没有漏掉任何一个键。脚本放 scratchpad(文件名带 3910),改后逐结构族人工复核 diff(标量 / 布尔 / 对象值 / grid 行数组 / vector 数组 / options / 带schema键的 object)。2. 删两个 demo 包装器 — 从
@object-ui/fields删除registerFields()与createFieldRenderer(),连带删掉 37 个「只为把组件引用喂给包装器」而存在的静态 widget import(活路径走fieldWidgetMap的 lazy loader,不需要静态引用;widget 仍由文件末尾的export * from './widgets/…'公开导出)。删除点留 tombstone,并把原本写在registerFields()函数体内的capability-multiselecttombstone(#3308)一并搬出保留。dist/index.d.ts已确认零残留,registerField/registerAllFields仍在。3. 消费者清零证明(带阳性反查) — 正查在同一路径集合上排除注释与散文后命中 0;阳性反查用邻近词证明扫描器与路径都是活的:
跨仓同样两组(一律
git -C,不用cd):git -C /home/user/objectstack grep对两个被删导出命中 0,而@object-ui/fields在该仓命中 1 个文件 —— 证明那次扫描也是活的。4. 文档站与逐页重验 —
ObjectUIProvider把registerFields()调用换成副作用 import(模块加载即跑registerAllFields(),唯一的注册缝)。InteractiveDemo无需改动:它本来就只渲染SchemaRenderer,节点换成 form 之后由真表单渲染器接管 label 与状态。26 个content/docs/fields/*.mdx页也无需改动 —— 每页只用SchemaExample id=…,全站共 76 处引用,与 76 个 catalog 例子一一对应。顺带修掉三处注释性误归因:
LookupField/UserField/CapabilityMultiSelectField里把field.field嵌套说成是被删包装器的产物,实际产出者是表单渲染器的 metadata 槽(#3090);complex-widgets.test.tsx的用例名同病,一并改指真实产出者(覆盖保留,不删)。浏览器实证(26 页逐页)
本地起站点(自选空闲端口 5321,收工按记下的 PID 收掉),Playwright + 预装 Chromium 逐页截查:
data-obj-type)按 widget 类别抽样(每类至少一页)结论:
text/textarea/email/url/phone/password— label + input/textarea,正常number/currency/percent— label + 带前缀的数值控件(currency页实测$与EUR前缀),正常date/datetime/time— label + 原生日期时间控件;time的18:00/09:00种子值正确显示select/lookup/user— label + 下拉或记录选择器按钮,正常boolean— label + switch,pre-checked的true生效auto_number/formula/summary/vector— label + 值展示(无 input 是只读字段的正确形态);ORD-0001/TKT-202403-0567/INV-1234/42/15750.50/4.52均正确显示rich-text(markdown+html)、object、file/image、location— label + 对应控件;object的只读例子以pre展示种子 JSON,location的经纬度正确显示grid— 见下方「已知遗留」已知遗留(不在本单范围,已另立单)
name, GridField readsfield— spec-compliant grid metadata renders empty cells #3951grid三个 demo 的表格行数正确但单元格全空,并伴随一条 React key 警告。根因是 grid 列键存在两种拼写:声明类型GridColumnDefinition(也是GridFieldMetadata.columns的声明类型)与文档散文都写name,而GridField自己的GridColumn接口和全部读点都用field。用同样的行数据直接渲染GridField、只改列键拼写即可复现(name拼写:全空 + 1 条 key 警告;field拼写:值正常、0 警告),与表单托管无关,改动前就是空的。本单故意没有把例子改成field—— 那会把声明类型里不存在的键固化进文档,正是裁决 B 要消灭的失效类;该选哪一侧收敛是契约决策,留给维护者。fordangles #3952 表单里的boolean字段,可见 label 的for指向一个不存在的 id(BooleanField用字段name覆写了FormControl给的控件 id),所以点 label 无法切换开关。同样是改动前既有、与本单无关的活路径缺陷。select页role-gated-options的current_user谓词告警:该例子改动前就是表单托管形态、本单未动,告警属既有现象(demo 上下文没有current_user)。auto-number页出现一条 404「Failed to load resource」,随后 4 次定向复现均为 0 —— dev server 首次编译的瞬态产物,不可复现,未归因到本改动。changeset 与 Bump
.changeset/fields-catalog-form-hosted-3910.md,@object-ui/fields: minor。依据仓内 Bump Policy 门:scripts/check-changeset-no-major.mjs机械禁止major(fixed 组 40 包会被一起推上去、脱离@objectstack节奏),AGENTS.md §版本号策略明写「objectui 自身的破坏性变更也标 minor,破坏语义写在正文」。破坏语义与迁移写法已在 changeset 正文写明。@object-ui/site与@object-ui/example-*在ignore列表,无需条目。两个门实跑:
⛔
content/docs/releases/**未触。验证
新增 pin:
examples/schema-catalog/test/fields-form-hosted.test.tsx—— 每个fields-*例子(1)根节点是 form 且fields非空、(2)字段条目不带死的value(Object.hasOwn,不是真值判断,否则false/0/''会被放过)、(3)经真SchemaRenderer+ 真表单渲染器渲染出 form 且 label 落在该字段自己的data-field容器里;另有 3 个带值例子直接断言种子值进 DOM。capability-multiselect-retired.test.ts按三种处置逐条重判(详见文件头注释):只钉活路径那条保留(第二条路没了之后它才真正钉住东西)、「跑完 registerFields 之后」那条随路径删除(测试不能调用已不存在的函数,对着删掉的路径断言会因「什么都没跑」而空绿)、38 键那条改指活路径;另加一条钉住两个导出确已消失、且活路径的两个导出仍在。反向自证(方向预先判定为「双向变红」,实测吻合):把两处肢体分别加回 —— (A) 还原一个裸节点 JSON、(B) 重新导出
registerFields—— 恰好 4 条断言变红且只有这 4 条(A 打中该例子的 shape / dead-value / render 三条,B 打中新增的导出 pin),撤销变异后 236 条全绿。变异未入 commit。Generated by Claude Code