Skip to content

fix(spec,lint): 把 record:* 从 react 档契约里撤回——它 publish 的 props 没有任何渲染器读 (#4413) - #4450

Merged
os-zhuang merged 3 commits into
mainfrom
claude/react-record-blocks-broken-wrk00g
Aug 1, 2026
Merged

fix(spec,lint): 把 record:* 从 react 档契约里撤回——它 publish 的 props 没有任何渲染器读 (#4413)#4450
os-zhuang merged 3 commits into
mainfrom
claude/react-record-blocks-broken-wrk00g

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes#4413.

react 档契约在 <RecordDetails> / <RecordHighlights> / <RecordRelatedList> / <RecordPath> 上 publish 了 objectName / recordId没有任何渲染器读这两个 prop

对着 objectui a8ad6c0 核实过:十个 record:* 渲染器全部从 useRecordContext() 取记录,而这个 context 只有记录路由(RecordDetailView.tsx:2033)和元数据编辑器预览(PagePreview.tsx:192)挂载;react-page.tsx 只把页面包进 SchemaRendererProvider。于是这些 block 在 react 页上渲染出 "bind a record to preview" 占位符——或者对 record:related_list(唯一读 schema.objectName 的那个)来说,因为父记录 id 永远拿不到而拒绝 fetch

完全照契约写出来的页面渲染成空,而且全链路没有一处报错——包括 os validate:它还在认真地把这些 prop 的字段名解析到它们命名的对象上,等于 lint 在为一个从不执行的绑定站岗。

为什么是撤回,不是去实现它

这份契约不只是"没实现",它的形状就是错的:每个 block 各带绑定,描述的是"四个 block 各取一次同一条记录",而这恰好是共享 record context 存在的目的所要防止的耦合方式——record:details 会把已挂载的 record:highlights 注册过的字段从明细网格里去重,记录级内联编辑保存栏用一个ifMatch 版本号提交整份草稿。照着 props 去修渲染器,等于把这个错误形状固化进契约(Prime Directive #12)。

这个原语该不该有公开名字(一个作者包在外层的 record SCOPE,一次取数、共享 context),是一个独立的产品判断,已开 #4444 记录,含两个风险(失效总线一致性、别蚕食记录页)和框架侧的连带改动清单。

改了什么

  • spec —— 四个 block 出 REACT_BLOCKS,留下写明原因的 ledger 和每个类型的可用替代。族成员从 ComponentPropsMap 派生而非手写,所以以后新增的 record 组件当天就被门禁覆盖,包括从来没进过契约、但同样能通过注册表构建的 react scope 写出来的另外六个。
  • lint —— 新规则 react-block-needs-record-context(error),按 tag 和 <Block type="record:…"> 两条路都拦,报错里直接给出能用的写法。作者在页面里自己声明的同名组件会 shadow 注入的 scope,这种情况不拦——发布级 error 不该被一个重名撞死。顺带删掉了已经走不到的 record:related_list 分支;COMPONENT_FIELD_SPECS 重新变回元数据面独有。
  • showcase —— Renewals Pipeline 改用这一档真能跑的绑定:<ListView filters={['account','=',sel]}> 出发票,<ObjectForm mode="view"> 出账户摘要。两个都真的渲染。
  • 契约 / skill / 文档 —— 重新生成或订正,包括 SKILL.md 里那句"curated set 之外的注册表 block 在这儿照样能用"——那正是这次的漏洞本身。

验证

  • os validate 在 app-showcase 上通过;把一个 record block 放回 react 页面后确实被拦(走完整 CLI,不是只调函数):

    ✗ Reference integrity check failed (1 issue)
    • page "showcase_task_desk" › <RecordHighlights>: … renders empty no matter how it is bound
    rule: react-block-needs-record-context at pages[24].source
    
  • spec 7234 / lint 727 / cli 639 / showcase 60 测试通过;两个包 typecheck 干净;check:api-surfacecheck:react-blocks 均已同步。

  • 已 merge 最新 main(5293114)后重跑上述全部。

一处需要知晓的取舍

<ObjectForm mode="view">record:details代餐,不是等价物——只读表单不是明细面板(FLS、分区、内联编辑、OCC)。这次撤回没有损失任何能跑的东西(它本来就是空的),但四个 block 里真正不可替代的就是这一个,#4444 的决策压力也集中在它身上。

🤖 Generated with Claude Code

https://claude.ai/code/session_015hz1t4rGsHvFTZYWRnRNPD


Generated by Claude Code

The react-tier contract published `objectName` / `recordId` on
`<RecordDetails>`, `<RecordHighlights>`, `<RecordRelatedList>` and
`<RecordPath>`, and no renderer read either prop. Confirmed against
objectui a8ad6c0: all ten `record:*` renderers take their record from
`useRecordContext()`, which only `RecordDetailView` (app-shell) and
`PagePreview` (metadata-admin) ever mount; `react-page.tsx` wraps the
page in `SchemaRendererProvider` alone. So the blocks rendered their
"bind a record to preview" placeholder — or, for `record:related_list`
(the one that does read `schema.objectName`), refused to fetch because
`ctx?.recordId` never arrives. A page authored exactly to contract came
back EMPTY with nothing reported anywhere, including by `os validate`,
which resolved those props' field names against the object they named:
lint standing guard over a binding that never ran.
Withdrawn rather than implemented. The contract was not merely
unimplemented, it was the wrong SHAPE: per-block bindings describe four
independent fetches of one record, which is exactly the coupling the
shared context exists to prevent (`record:details` drops the fields a
mounted `record:highlights` registered; one inline-edit save bar commits
them all under a single `ifMatch`). Honoring the props would have
fossilized that (Prime Directive #12). If the family is ever wanted on
this surface, the shape is a record SCOPE provider block an author wraps
around them — one fetch, shared context — not per-block props.
- spec: the four blocks leave REACT_BLOCKS, with the ledger for why and
the working replacement per type. The family is derived from
`ComponentPropsMap`, so a record component added later is gated the day
it lands — including the six that were never in the contract but are
just as reachable through the registry-built scope.
- lint: `react-block-needs-record-context` (error) rejects them on a
react page, by tag and through `<Block type="record:…">` alike, quoting
the block that does work. A locally-declared component of the same name
shadows the injected scope and is left alone. The dead
`record:related_list` branch goes with it; `COMPONENT_FIELD_SPECS` is
the metadata surface's alone again.
- showcase: Renewals Pipeline binds the selected account by React state
the way this tier can — `<ListView filters={['account','=',sel]}>` for
the invoices, `<ObjectForm mode="view">` for the summary. Both render.
- contract, skill, and validating-metadata/pages docs regenerated or
corrected to match, including the SKILL.md line that told authors every
registry block outside the curated set still works here.
Verified: `os validate` passes on app-showcase and fails with this rule
when a record block is put back on a react page; spec 7231, lint 727,
cli 639 and showcase 60 tests pass; both packages typecheck.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015hz1t4rGsHvFTZYWRnRNPD
@vercel

vercelBot commented Aug 1, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 1, 2026 8:02am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/lint, @objectstack/spec.

107 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 @objectstack/lint, 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 packages/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/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/lint, @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/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/kernel/runtime-capabilities.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/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.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:uisize/xlteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

kind:'react' 页面上 record:* 四个 block 全部失效——契约 publish 的 objectName/recordId 渲染器根本不读(#4340 后续)

2 participants

@os-zhuang@claude