Skip to content

feat(spec): RecordHighlightsField 声明 readonly —— 渲染器已强制的键不再被静默剥离 (#5176) - #5607

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-5176-highlights-readonly
Aug 5, 2026
Merged

feat(spec): RecordHighlightsField 声明 readonly —— 渲染器已强制的键不再被静默剥离 (#5176)#5607
os-zhuang merged 2 commits into
mainfrom
claude/issue-5176-highlights-readonly

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5176

按维护者 2026-08-05 裁定的 Option A 实施,仅 spec 侧。

改了什么

packages/spec/src/ui/component.zod.tsRecordHighlightsField对象成员新增一个已声明键:

readonly: z.boolean().optional().describe('Render this chip read-only — ...')

union 形状本身不动(bare-string 成员不变),不翻 .strict() —— 那属于 #5068 的闸门专项,明确不在本单范围。对象级的 highlightFields: string[] 同样未触碰(见 issue 的范围边界)。

同时把两处 .describe() 里已经过时的形状说明补齐({name,label?,icon?,type?}{name,label?,icon?,type?,readonly?}),否则生成的组件参考文档会描述一个与 schema 不符的形状。

前提复核(rule 6):issue 前提成立,已实测复现

origin/main(e900015cd)上按 issue 所述逐条核对:union 为 [string, object]、对象成员非 .strict()readonly 缺席且被静默剥离。剥离实测(改动前跑新增的保留测试):

AssertionError: expected undefined to be true
 ❯ src/ui/component.test.ts:229:28
     expect(entry.readonly).toBe(true);

{ name: 'supply_share', readonly: true, type: 'number' } 解析后 readonlyundefined —— 与 issue 正文给出的 input/parsed 对照完全一致。

先证红 → 后转绿

按"先写测试证红"的要求,写保留测试并在未修改的 schema 上跑红,再改 schema。事前预判的方向是常规红(删掉 readonly 这条 limb,新增的 pin 测试转红),实际与预判一致:

改动前(5 个新增用例中 3 红 / 2 绿):

FAIL should preserve an authored readonly on an object-form highlight field
FAIL should preserve readonly: false rather than dropping it
FAIL should reject a non-boolean readonly instead of silently stripping it
 Test Files  1 failed | 314 passed (315)
      Tests  3 failed | 8025 passed (8028)

另外 2 个用例(不物化默认值bare-string 与其它对象形式仍被接受)是回归护栏,改动前后都应绿,实测也确实都绿 —— 它们不承担证红职责,如实标注。

改动后(spec 全量):

 Test Files  315 passed (315)
      Tests  8028 passed (8028)

其中 should reject a non-boolean readonly 这一条值得单说:改动前 readonly: 'yes' 会被静默剥离从而通过解析,改动后 union 两个成员都失配、解析响亮失败 —— 这正是裁定里"拼写/类型错误在闸门接通后响亮失败"的那一半,现在在 schema 层就已兑现。

消费半径清扫

record:highlights 的下游读取方是 packages/lintvalidate-page-field-bindings.ts(COMPONENT_FIELD_SPECS['record:highlights'] = { props: ['fields'] })与 validate-react-page-props.ts,两者只从条目中取 name;本次是纯增量、未删除任何 limb,因此不存在需要重判的 fixture(fixture 三分类处置在此无适用项)。全仓 grep 也确认没有任何 fixture 拼写过 readonly

packages/lint 全量:59 passed (59) / 1284 passed | 4 skipped

生成物审查

  • content/docs/references/ui/component.mdx —— 本 PR 内唯一应当移动的生成物,三处改动逐行可由这一个键解释:union 描述行、对象成员表新增 readonly 行、fields 行内类型因生成器满 4 个属性后截断而显示省略号。
  • api-surface.json —— 未移动,已实测确认(public API surface + factory signatures unchanged ✓)。该文件只记录 "名字 (kind)" 字符串,不含类型文本,而本次未新增导出,因此结构上不可能移动。
  • authorable-surface.base.json —— git checkout 排除,不入本 PR(check:authorable-surface--check 模式下也会重写 authorable-surface.base.json —— 一次纯核验会改工作区,且任何无关 PR 都能因此静默推进删除门的锚点 #5358 纪律)。gen:schema 会把它刷新到新 baseRev 并顺带吸收 3 个与本单无关的键(system/EmailServiceConfig:appName / defaultTemplateContext / queueDelivery,来自其它已合入 issue)。注意:它不含本次新增的 readonly
  • 其余闸门(check:spec-changes / upgrade-guide / skill-refs / skill-docs / skill-examples / react-blocks / react-declaration-parity / variant-docs / empty-state / strictness-ledger / liveness / doc-authoring / docs-audit-scope / nul-bytes)全绿,未新增任何 check: / gen: 脚本,故无需登记 check-generated 台账。

一处对派发预设的更正

派发说明预期"component.zod.ts#5068 中实测 BFS 不可达,故 authorable-surface 不应移动"。结论(不移动)对,但理由需更正:ui/RecordHighlightsProps:fields 本身该 walk 里。真正的原因是这个 walk 是严格一层的 —— 全表 1080 个 ui/ 键中带嵌套路径的为 0(RecordRelatedListProps:add 同样只记容器、不下钻 add.picker.object)。readonly 挂在 fields 数组项 union 的对象成员上,属于第二层,因此不产生新的 authorable-surface 键。对 #5068 实施者而言这是个有意义的差别:该键不会自动进入 authorable 面,闸门接通时需按 props 解析路径单独覆盖。

验证命令

npx vitest run --maxWorkers=2                       # packages/spec: 315 files / 8028 passed
npx vitest run --maxWorkers=2                       # packages/lint: 59 files / 1284 passed
pnpm --filter @objectstack/spec --filter @objectstack/lint typecheck   # 均 Done
pnpm --filter @objectstack/spec check:generated     # 建包后 10/10 绿
node scripts/check-nul-bytes.mjs                    # OK,另做控制字节自扫,干净

check:api-surfacecheck:skill-examples 在新工作树里初次为红,原因是尚未 builddist/(闸门自身即提示 "this gate reads the BUILT dist ... the removals above are phantoms");建包后两者均转绿,与本改动无关。

Changeset

@objectstack/spec minor(新增已声明协议键,additive)。

后续项(不在本 PR)

  • objectui:在 record:highlights block 的 inputs 中声明 readonly,使其进入 sdui.manifest.json —— 裁定中明确可另 PR,由 objectui 车道承接。在此之前,AI 作者仍无法从 manifest 学到该键(docs/audits/2026-06-react-blocks-conformance.md 记为 "zero inputs")。
  • Option C(chips 读 form view 字段级 readonly)按裁定保留为后续便利层,未并入。

Generated by Claude Code

`record:highlights` 条目的对象形式新增可选 `readonly: boolean`,用于把高亮
chip 标记为不可行内编辑(hook / 自动化维护的列)。

此前该键未被声明,而对象成员不是 `.strict()`,因此被**静默剥离**:

    input   { fields: [ { name: 'supply_share', readonly: true, type: 'number' } ] }
    parsed  { fields: [ { name: 'supply_share', type: 'number' } ] }

今天之所以端到端可用,只是因为逐组件 props 尚未在装载路径上解析
(`PageComponentSchema.properties` 是 `z.record(z.string(), z.unknown())`)。
一旦该闸门接通,授权的 `readonly` 要么静默丢失(机器维护列重新可编辑、
零诊断),要么硬解析失败。声明它使"授权的声明"与"被强制的行为"成为
同一事实 —— 落地即满足 ADR-0049 enforce-or-remove,而非声明即惰性。

纯增量:`readonly` 可选且不物化默认值,未授权该键的条目解析结果与此前
完全一致;bare-string 形式不变。对象级 `highlightFields: string[]` 不在
本次范围内。

不翻 `.strict()` —— 那属于 #5068 的闸门专项。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 5, 2026 9:26pm

Request Review

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Copy link
Copy Markdown
Contributor Author

PM 预记(session_018fxLGQdatPbBUvCgiVxg6D):本 PR 的 ESLint job 将因 #5604(main 侧 check:engine-double-contract 断裂,#5584 遗留,与本 diff 无关)而红——验收时不计入本单质量账,#5604 修复落地后合 main 重跑。本 PR 带 @objectstack/spec minor changeset,无需 skip 标签。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 5, 2026 22:15
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit 4f4c3fb Aug 5, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5176-highlights-readonly branch August 5, 2026 22:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RecordHighlightsField does not declare readonly, so the spec silently strips the key the chip gate reads

2 participants