Skip to content

objectui: bodyShape is read by the console apiHandler but forwarded by ZERO action renderers — a declared wrap is silently dropped #6938

Description

@yinlianghui

Found while implementing objectstack#6837 (bodyExtra forwarding, objectui PR objectstack-ai/objectui#3924). Not fixed there — different key, and #6837's scope was bodyExtra.

The defect

bodyShape is the spec's body-wrapping declaration for a type: 'api' action — 'flat' (default) or { wrap: key } to nest the collected params under a key. The console apiHandler reads it, unconditionally, on its absolute-HTTP branch:

packages/app-shell/src/hooks/useConsoleActionRuntime.tsx:277

const wrap = action.bodyShape && typeof action.bodyShape === 'object' && action.bodyShape.wrap
  ? action.bodyShape.wrap
  : undefined;
const body = wrap ? { [wrap]: resolvedParams } : { ...resolvedParams };

But no action renderer forwards it. Measured on objectui origin/main @ 99ba5fbd (counter-probe: confirmText is present in all five whitelists, so the greps are live):

renderer forwards bodyShape?
packages/components/src/renderers/basic/elements.tsx (element:button) no
packages/components/src/renderers/action/action-button.tsx no
packages/components/src/renderers/action/action-group.tsx no
packages/components/src/renderers/action/action-icon.tsx no
packages/components/src/renderers/action/action-menu.tsx no

So an author who declares bodyShape: { wrap: 'data' } on an action rendered by any of these gets a flat body instead of a wrapped one — the endpoint receives the params at the top level and the declaration reads as honoured because it parses and publishes.

Reachability

Reachable on the declared-action renderers (action:button and friends), which is where bodyShape is authorable. It is NOT reachable through DeclaredActionsBar / RelatedRecordActionsBridge / ObjectGrid: those spread the whole ActionDef ({ params: rawParams, ...rest }) and carry the key through — which is exactly why the gap survived, since the record-header path works.

Note element:button is arguably correct to omit it: spec's InlineActionSchema pick list does not include bodyShape, so it is not inline vocabulary. The defect is the four declared-action renderers.

Sibling key, checked and NOT a defect

recordIdParam is also forwarded by none of the five, but the apiHandler only reads it under if (rowRecord && action.recordIdParam), and rowRecord arrives only from the spread-based hosts above — which also carry recordIdParam. So that one is unreachable, not broken. Recorded here so the next reader does not re-derive it.

Why this class keeps recurring

The explicit forward whitelist is a deliberate design (a key no renderer honours must not look wired), but it makes every NEW spec key invisible until five separate lists are edited, with nothing failing when they are not. bodyExtra was the same shape (#6837) and openIn / resultDialog / undoable / locations each have an in-comment note recording their own instance of it. Worth considering a pin test that diffs the renderers' forwarded key sets against the spec keys the runtime actually reads, so the next key fails loudly instead of silently.

Filed unassigned for triage.

Activity

  1. claude commented on Aug 9, 2026

    @claude
    Contributor

    Triage: pm:queue + target:v17 (routing repo:objectui kept).

    • Concrete defect, decision-free: spec's bodyShape (wrap declaration for type: 'api' actions) is read by the console apiHandler but forwarded by zero of the five action renderers, so a declared wrap silently degrades to a flat body. Fix face is the four declared-action renderer whitelists the card names.
    • Premise verified live on objectui origin/main @ 99ba5fb (the exact ref the card measured): apiHandler reads action.bodyShape in packages/app-shell/src/hooks/useConsoleActionRuntime.tsx (wrap branch present); git grep bodyShape over packages/components/src/renderers/ returns 0 hits; counter-probe confirmText hits all five whitelists, so the zero is live, not a broken scan.
    • target:v17 basis (criterion ②, declared ≠ enforced on a public contract): the key parses, publishes, and reads as honoured while the endpoint receives a different body shape than declared — and the sibling card objectui: element:button forwards bodyExtra to the ActionRunner; the object-form params arm becomes a deprecation window (#5777 objectui half) #6837 (bodyExtra, the identical forwarding-whitelist shape, PR objectui#3924 in flight) already carries target:v17. Same face, same grade.
    • Dedup: objectui: element:button forwards bodyExtra to the ActionRunner; the object-form params arm becomes a deprecation window (#5777 objectui half) #6837 is the sibling, not a duplicate (different key, cross-referenced both ways). No other open issue or PR in the three repos mentions bodyShape (title sweep over all 417 open issues + open objectui PR list). The card's own pin-test suggestion (diff forwarded key sets against runtime-read keys) is noted as a candidate follow-up for the implementing dev, non-binding.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. self-assigned this
    on Aug 9, 2026
  3. yinlianghui commented on Aug 9, 2026

    @yinlianghui
    CollaboratorAuthor

    🔒 认领(objectui 分片 PM,session session_01GTRjn8xBqp75dk7kFupVRt,v17 批次)

    • 分支:claude/issue-os6938-bodyshape-forward(objectui 仓)
    • 范围(正文 + 分诊一致):四个 declared-action 渲染器(action-button/group/icon/menu)白名单补 bodyExtra 同款的 bodyShape 转发;element:button 按正文论证不补(spec InlineActionSchema pick list 不含该键,不是 inline 词汇);spread 型宿主(DeclaredActionsBar 等)天然携带,免改。
    • 前置已除:姊妹单 objectui: element:button forwards bodyExtra to the ActionRunner; the object-form params arm becomes a deprecation window (#5777 objectui half) #6837 的 PR objectui#3924 已合入 main(7e5bb5d4e),文件面无在飞冲突;本单以合入后形态为基。
    • 正文的 pin test 建议(渲染器转发键集 vs runtime 实读键集 diff)按分诊注记为非绑定候选,dev 可评估顺带落地或单列 finding。
    • 文件面:packages/components/src/renderers/action/action-{button,group,icon,menu}.tsx + 类型(如需)+ 测试。

    Generated by Claude Code

  4. added a commit that references this issue on Aug 10, 2026
    c2fd122
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions