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 apiHandlerreads 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.
Found while implementing objectstack#6837 (
bodyExtraforwarding, objectui PR objectstack-ai/objectui#3924). Not fixed there — different key, and #6837's scope wasbodyExtra.The defect
bodyShapeis the spec's body-wrapping declaration for atype: 'api'action —'flat'(default) or{ wrap: key }to nest the collected params under a key. The consoleapiHandlerreads it, unconditionally, on its absolute-HTTP branch:packages/app-shell/src/hooks/useConsoleActionRuntime.tsx:277But no action renderer forwards it. Measured on objectui
origin/main@99ba5fbd(counter-probe:confirmTextis present in all five whitelists, so the greps are live):bodyShape?packages/components/src/renderers/basic/elements.tsx(element:button)packages/components/src/renderers/action/action-button.tsxpackages/components/src/renderers/action/action-group.tsxpackages/components/src/renderers/action/action-icon.tsxpackages/components/src/renderers/action/action-menu.tsxSo 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:buttonand friends), which is wherebodyShapeis authorable. It is NOT reachable throughDeclaredActionsBar/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:buttonis arguably correct to omit it: spec'sInlineActionSchemapick list does not includebodyShape, so it is not inline vocabulary. The defect is the four declared-action renderers.Sibling key, checked and NOT a defect
recordIdParamis also forwarded by none of the five, but theapiHandleronly reads it underif (rowRecord && action.recordIdParam), androwRecordarrives only from the spread-based hosts above — which also carryrecordIdParam. 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.
bodyExtrawas the same shape (#6837) andopenIn/resultDialog/undoable/locationseach 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.