fix(plugin-detail): record:highlights 的 fields 声明补上 readonly,让 manifest 能被作者读到 (#3407) - #3795
Conversation
…st 能被作者读到
`readonly` 早已被兑现:renderer 归一化时逐条拷贝它,`HeaderHighlight` 的可编辑门
据此拒绝 chip 上的内联编辑(objectstack#5077);`@objectstack/spec` 也已在
`RecordHighlightsField` 上声明(objectstack#5176 / PR #5607)。缺的只是本仓这一侧的
**发布面**:`fields` input 的 description 仍把条目形状写成
`{name,label?,icon?,type?}`,而 registry `inputs` 正是 `gen-manifest.ts` 序列化进
`sdui.manifest.json` 的东西 —— 读 manifest 的 AI 作者被告知这个键不存在。
`readonly` 写在 `fields` 的 description **里**,而不是新增一个自己的 input,因为
契约就是这么放的:spec 的 `RecordHighlightsProps` 顶层恰好三个键
(`fields`/`layout`/`aria`),`readonly` 挂在每个**条目**上。顶层加
`{ name: 'readonly', type: 'boolean' }` 会发布一个平台默默丢弃的键 ——
生成的 `sdui.manifest.json` 与 `sdui-intrinsics.d.ts` 会宣告 `readonly` prop,
manifest 门只校验顶层 prop、不会报任何诊断,`RecordHighlightsProps` 是普通
`z.object`、parse 时把未知键无声剥掉,而逐条读 `field.readonly` 的 renderer 永远
看不到它。信了这个面的作者,最后得到的是机器维护的列仍可手改、且任何地方都没有
诊断说明原因。`ComponentInput` 本就是扁平的,所以数组对象型 input 用散文发布成员
键,`record:path.stages` 与 `record:alert.action` 一直如此。
新增的 spec-parity 测试两个方向都在运行时从 `@objectstack/spec` 推导,而不是复述
今天的键表:`RecordHighlightsField` 对象分支的每个键都必须出现在 `fields` 的
description 里;本 block 也不得声明 `RecordHighlightsProps` 不接受的顶层 input。
此前没有任何东西交叉校验 registry `inputs` 与 spec,两个漂移方向都是静默的。
反向核验(方向事先预判,两个都应转红,各钉住改动的一半):
- 把 description 退回旧文本 → 「every spec entry key is discoverable」转红,
`undocumented = ['readonly']`;
- 按 issue 字面加顶层 `readonly` input → 「declares no top-level input the spec
does not accept」转红,`offSpec = ['readonly']` —— 新门恰好拦住字面方案。
无运行时行为变化。
Fixes #3407
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
|
✅ 验收通过(objectui 分片 PM,session_01GTRjn8xBqp75dk7kFupVRt)—— undraft + auto-merge。 对 issue 字面验收偏离的裁定:采纳 dev 的契约忠实落点。 三条实测把字面方案证死:(1) git 实物核验( 长期方向:dev 两轴分析 B(现在)→ C(ComponentInput 成员形状声明位,跨 types/core/sdui-parser)成立;C 的档案记在 #3797 判级评论,不进决策箱。衍生 #3797(4 个 block 发布 pin 版 spec 不接受的顶层 input)另行判级。dev 自查纠错一则入方法库:占位 GITHUB_TOKEN + Generated by Claude Code |
Fixes #3407
Part of objectstack-ai/objectstack#5176(spec 侧 PR objectstack-ai/objectstack#5607 已 MERGED)
摘要
把
record:highlights的发布面补齐:fieldsinput 的 description 现在写出完整条目形状{name,label?,icon?,type?,readonly?}并说明readonly的语义。registryinputs正是packages/sdui-parser/scripts/gen-manifest.ts序列化进sdui.manifest.json的东西,所以这一行就是 AI 作者能否从 manifest 得知该键存在的全部差别。
readonly早已两侧兑现:renderer 归一化逐条拷贝(renderers/record-highlights.tsx:70readonly: f?.readonly === true),HeaderHighlight可编辑门据此拒绝内联编辑(objectstack#5077);spec 已在
RecordHighlightsField上声明(#5607)。缺的只有本仓 description。按 AGENTS.md「issue 是线索不是规格」,动码前逐条核到
origin/main(objectui00b9451d8,objectstack
d42a92fc6):origin/main上record:highlights已有 2 个 inputs(fields、layout),自 #2113 / #3027 起就有docs/audits/2026-06-react-blocks-conformance.mdgit log --oneline --all -- 'docs/audits/2026-06-react-blocks-conformance.md'零命中,任何分支任何历史都没有过;docs/audits/现存只有 3 个文件,均无此名inputs中声明readonly键(boolean)readonly在 spec 里是fields[]的逐条键,不是顶层 prop第三条是本 PR 最关键的判断,证据(运行本仓 pin 的
@objectstack/spec@17.0.0-rc.5):若按 issue 字面加顶层
{ name: 'readonly', type: 'boolean' },后果是发布一个平台默默丢弃的键:sdui.manifest.json与sdui-intrinsics.d.ts会宣告 RecordHighlights 有readonlyprop;packages/sdui-parser/src/validate.ts的 manifest 门只遍历节点顶层 prop(Object.entries(node)对
comp.inputs),不下探fields[]条目,因此顶层readonly被判为「已知 prop」,零诊断;z.object,parse 时把未知顶层键无声剥掉(上方实测);field.readonly,永远看不到顶层的那个。净结果:作者信了平台自己的 manifest,写下的键被丢弃,他想保护的机器维护列仍然可手改,
而且任何地方都没有诊断说明原因 —— 这正是 spec 侧注释里声明
readonly的理由所反对的那件事(「an undeclared key is silently stripped here, which turns a machine-owned column editable
again with no diagnostic anywhere」),只是上移了一层。也正是 issue 自己引的
objectstack#5435「平台权威不得指向自己闸门会拒绝的键」的反向违例。
因此采用契约忠实的落点:
readonly写在fields的 description 里。ComponentInput本就是扁平的(
name= "must match schema property",无任何嵌套机制),所以数组对象型 input 用散文发布成员键 —— 这是本仓既有体例,非变通:
record:path.stages写'Explicit stage definitions [{ value, label }]',record:alert.action写'{ actionName, label?, variant? }',fields自己原本也写'bare names or {name,label?,icon?,type?}',只是漏了
readonly?。issue 的目的(「AI 作者无法从 manifest 得知该键存在」)由此达成,且不污染契约。
验收逐条证据
①
record:highlights的inputs含readonly(boolean,含语义描述) — ✅ 达成,落点为fields的 description(理由见上)。描述措辞抄自 spec 原文(
RecordHighlightsField.readonly.describe()与HeaderHighlight门实际行为),未自造语义:② 重新生成的
sdui.manifest.json携带该键 — ✅ 实测。sdui.manifest.json不是仓内提交产物(全仓零命中),而是
gen-manifest.ts的构建期产物,由manifestFromConfigs从 registry现场序列化(
packages/sdui-parser/src/index.ts:152description: i.description原样带出)。按gen-manifest.ts同样的调用形状(getPublicConfigs()→manifestFromConfigs)实跑,record:highlights条目:{ "type": "record:highlights", "namespace": "record", "inputs": [ { "name": "fields", "type": "array", "required": true, "description": "Key fields to highlight (1-7), bare names or {name,label?,icon?,type?,readonly?}. Set readonly: true on an entry to render that chip read-only — …" }, { "name": "layout", "type": "enum", "enum": ["horizontal","vertical"], "description": "Layout orientation for highlight fields" } ] }readonly可从 manifest 读到 =true;顶层 input 名单仍为['fields','layout'],与 spec 顶层可接受键一致(
aria按本文件既有注释的理由继续不声明)。③ conformance 审计中该 block 不再是 "zero inputs" —⚠️ 空条:该审计文件从不存在(证据见
上表),且该 block 在
origin/main上本就不是 zero inputs。故无条目可改,也未新建审计文档(超出本单范围)。若维护者确实想要这份 react-blocks conformance 审计,建议单开一单。
生成物 diff 审查
无生成物 diff 可审:
sdui.manifest.json/sdui-intrinsics.d.ts/sdui-blocks.md三者均不在仓内提交(
git ls-tree -r origin/main | grep -E 'sdui\.manifest|sdui-intrinsics|sdui-blocks'零命中),由构建期生成。因此未手改任何生成物;上面 ② 的 manifest 是用官方生成路径实跑打印
出来核对的,不落盘、不提交。源码 diff 仅本键增量:
packages/plugin-detail/src/index.tsx1 处 description 改写 + 说明注释,无其他移动。
反向核验(方向事先预判:两个都应转红,各钉住改动的一半)
undocumented = ['readonly']readonlyinputoffSpec = ['readonly']第二条尤其有意义:新增的门恰好拦住 issue 的字面方案。反向核验后已还原,
grep REVERSE-VERIFICATION零残留。
测试
新增
packages/plugin-detail/src/__tests__/recordHighlightsInputs.spec-parity.test.ts—— 两个方向都在运行时从 spec 推导,不复述今天的键表:
RecordHighlightsField对象分支的每个键都必须出现在
fieldsdescription 里;本 block 不得声明RecordHighlightsProps不接受的顶层 input。此前仓内没有任何东西交叉校验 registry
inputs与 spec(getPublicConfigs/manifestFromConfigs的使用点里没有这类门),两个漂移方向都是静默的。
全包:
pnpm --workspace-concurrency=2 --filter @object-ui/plugin-detail test→
Test Files 55 passed (55) / Tests 466 passed (466)(基线 54/461,+1 文件 +5 测试;vitest list已确认新文件被默认收集)。type-check→ 干净(tsc --noEmit && tsc -p tsconfig.typetests.json零输出)。lint→ 0 errors(707 warnings 全为未触及文件的既有基线;单独 lint 本次两文件亦 0 errors,27 warnings 均为 barrel 文件既有的
react-refresh/only-export-components)。控制字节:
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]'三个改动文件零命中。Changeset
.changeset/highlights-readonly-authoring-surface-3407.md,@object-ui/plugin-detail: patch。依据:改动落在
packages/plugin-detail/src/内(check-changeset-presence.mjs的欠账判据),且
sdui.manifest.json是发版包的对外发布面 —— 作者可读到的键集合变了,属用户可见;但无运行时行为变化、纯 additive 文档面,故取 patch(与
.changeset/bulk-action-param-options-open-3309.md这类「类型/声明面放宽、无运行时变化」的同类先例同档)。
未触碰
content/docs/releases/**未动。#3521 在packages/components/src/renderers/layout/containers.tsx,与本单(
packages/plugin-detail/)文件面不相交。Generated by Claude Code