Uh oh!
There was an error while loading. Please reload this page.
fix(actions): forward bodyShape so a declared body wrap is honoured (objectstack#6938) - #3932
Conversation
`bodyShape` is the spec's body-wrapping declaration for a `type: 'api'`
action — `'flat'` (default) or `{ wrap: key }` to nest the collected params
under `key`. The console `apiHandler` read it unconditionally while ZERO
action renderers forwarded it, so an author declaring `bodyShape: { wrap:
'data' }` got a FLAT body on the wire: the endpoint received the params at
the top level and the declaration read as honoured because it parsed and
published. Same forwarding-whitelist shape as bodyExtra (objectstack#6837).
The four declared-action renderers (action:button / :group / :icon / :menu)
now forward the key, and `ActionSchema` declares it, typed by derivation
from the spec so the union cannot drift from the contract.
`ActionRunner.executeAPI` — the fallback path taken when no host registered
an `api` handler — now reads it too. Without that, fixing only the renderers
would have left a narrower version of the same defect: the console
apiHandler and RecordDetailView.apiHandler both wrapped and the runner's
fallback did not, so one action changed body shape with the host that ran
it. The wrap covers the collected payload only; bodyExtra and other
top-level keys stay flat — the spec's own wording for the key, and what both
console read-sites already implemented, so there was one reading to mirror
rather than a choice to make.
`element:button` deliberately does NOT forward it: its whitelist mirrors
spec's `InlineActionSchema` pick list field for field, and that pick list
(verified against packages/spec/src/ui/action.zod.ts on objectstack
origin/main) does not include `bodyShape`. It is not inline vocabulary, and
that boundary opens spec-first, never renderer-first.
Fixesobjectstack-ai/objectstack#6938The latest updates on your projects. Learn more about Vercel for GitHub. |
yinlianghui
commented
Aug 9, 2026
通用 pin test 为何不在本 PR 落地objectstack#6938 正文建议加一条 pin —— 把渲染器的转发键集与 runtime 实读键集 diff,让下一个新键响亮地失败而不是静默丢失。分诊已注记其为非绑定候选。评估后不在本 PR 落地,已单列为 objectstack-ai/objectstack#6975( 理由是它需要两份输入,而只有一份可机械推导:
而且遗漏本身是承重的,不是噪声: 于是机械 diff 旁边必须再挂一份手工维护的"已论证豁免"清单 —— 那恰恰是这整条线索要消灭的那种会漂移的清单,只是上移了一层。这是"每个渲染器面欠 runtime 哪些键"的契约该落在何处的设计决策,不是能顺手拧上的测试,应由维护者定而不是 dev 在修 bug 途中猜。#6975 列了三个方向(声明式生成 / 仅钉渲染器间一致 / spec 侧标注)及各自代价,本 PR 刻意不在其间做选择。 本 PR 内的替代覆盖:两个新测试文件把本次修复的键钉在两端(runner 收到什么 + 线上什么形状),并对两处反向验证记录了预判方向。这不能防住下一个键 —— 那正是 #6975 的题目。 Generated by Claude Code |
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
commented
Aug 9, 2026
📌 状态(objectui 分片 PM,session 诊断:该用例(objectui#3922 带入)在 处置:#6979 修入 main 后本 PR update branch 重跑即入队;两连红均非本 PR 缺陷,不改本 PR 内容。 Generated by Claude Code |
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
commented
Aug 9, 2026
✅ 验收通过(objectui 分片 PM,session 实物核验(前序评论已记录):单提交 9 文件,四个 declared-action 渲染器补 CI 亲读终态(update branch 携 #6979 修复后):20 检查全 completed、0 失败,原两连红的 Test shard 1/4 转绿 —— 前两次红已确证为 ContextSelectors persist 测试的 auto-select 竞态(objectstack#6979,PR #3933 已修入 main),与本 PR 文件面无关。 dev 另记的 rc 漂移观察(安装版 spec rc.5 的 InlineActionSchema pick list 尚无 Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixesobjectstack-ai/objectstack#6938
bodyExtra(objectstack#6837 / #3924)的姊妹单,同一个转发白名单形状,换一个键。缺陷
bodyShape是 spec 给type: 'api'动作的 body 包裹声明 ——'flat'(默认)或{ wrap: key }把采集到的参数嵌到该键下(better-authorganization/update就是它存在的理由)。consoleapiHandler无条件读它,而五个动作渲染器一个都不转发。于是声明了bodyShape: { wrap: 'data' }的 action:button 类动作实际发出扁平 body:端点在顶层收到参数,而声明看起来是"已兑现"的 —— 因为它能解析、能发布。改动
action-{button,group,icon,menu}.tsx)白名单补bodyShape转发,就在 #6837 刚补的bodyExtra旁边。ActionSchema(packages/types/src/ui-action.ts)声明该键,按 fix(actions): forward bodyExtra end-to-end through the action chain #3924 的先例从SpecAction['bodyShape']派生,联合类型无法与契约漂移。ActionRunner.executeAPI(无宿主注册apihandler 时走的回退路径)也开始读它。这一条超出 issue 正文点名的四个文件,理由写在下面。为什么连 ActionRunner 一起补
只修渲染器会留下同一缺陷的窄版:console
apiHandler与RecordDetailView.apiHandler都兑现 wrap,而 runner 回退不兑现 —— 同一个动作会因为承载它的宿主不同而改变 body 形状,这正是"声明 ≠ 兑现"再往下一跳。关键是这里没有需要猜的语义。spec 自己的措辞是:
{ wrap: 'data' }"nests the user-collected params under that key … whilerecordIdParamand other top-level keys stay flat"。两个 console 读点(useConsoleActionRuntime.tsx:277、RecordDetailView.tsx:670)实现的正是这个次序 —— 先构造 wrap,再把recordIdParam/organizationId/bodyExtra平铺到顶层。所以只有一个读法要镜像,不是一个要做的选择。runner 现在与之逐字一致:包裹只覆盖采集到的 payload,bodyExtra平铺在旁。element:button故意不补它的转发列表逐字段镜像 spec 的
InlineActionSchemapick list(该文件自己的注释就这么写),而那份 pick list 不含bodyShape—— 已用git show origin/main:packages/spec/src/ui/action.zod.ts亲验,13 个 pick 为type name label target openIn method params bodyExtra confirmText successMessage errorMessage refreshAfter opensInNewTab。bodyShape不是 inline 词汇,而 spec 对这个边界的规矩是明写的:"Widen this when a renderer widens, not before",且窗口只能 spec 先行,不能渲染器先行。从element:button转发它就是被禁的那个方向。spread 型宿主(
DeclaredActionsBar/RelatedRecordActionsBridge/ObjectGrid)转发整个 def({ params: rawParams, ...rest }),天然携带该键,免改 —— 已抽查DeclaredActionsBar.tsx:135,其注释本就点名bodyShape。测试
新增两个文件,在两端断言:runner 收到什么(转发本身),以及线上是什么形状(转发加 body 组装)。只查 props 的测试会在 payload 依然丢失时保持绿色。
packages/components/src/__tests__/action-bodyShape-forward.test.tsx—— 四个渲染器各一条转发;端到端两条(真实fetch,断言{ data: {...} },以及bodyExtra平铺在 wrap 旁);无bodyShape时扁平 body 的回归钉。packages/core/src/actions/__tests__/ActionRunner.bodyShape.test.ts—— runner 自身 7 条:wrap 生效、无 params 时包裹 context record、bodyExtra保持平铺、ApiConfig分支同样生效、'flat'与缺省两条扁平钉,以及一条 off-spec 值退化为文档默认值(而不是在线上造出一个字面量 undefined 键)。反向验证(方向先判后跑,两次都是标准 red):
expected undefined to deeply equal { wrap: 'data' }),扁平钉保持绿。expected { name: 'Acme Inc', slug: 'acme' } to deeply equal { data: { … } }),4 条渲染器转发断言与两处扁平钉保持绿。如实记一处:那条 off-spec 退化用例在 runner 改动前后都是绿的(改动前 runner 根本不读该键,行为也是扁平),所以它对本次改动不具判别力 —— 它是往后防止 wrap 分支在坏值上造键的钉子,不是本次修复的证据。
验证
pnpm exec vitest run packages/components/ packages/core/ --maxWorkers=2→ 174 files / 2358 tests passedpnpm exec vitest run packages/app-shell/ packages/plugin-grid/→ 361 files / 3285 passed, 1 skippedpnpm exec turbo run type-check --concurrency=2→ 78 successful, 78 totalpnpm check:control-bytes→ OK(3808 个文本文件);pnpm changeset:check→ OKpatch,覆盖@object-ui/components/@object-ui/core/@object-ui/typesBase:
5e524950d(含 #3924 的7e5bb5d4e)。正文点名的通用 pin test(渲染器转发键集 vs runtime 实读键集 diff)未在本 PR 落地,理由与后续单见评论。
Generated by Claude Code