feat(spec): give an inline type:'api' action its own payload key — params keeps one meaning (#5777) - #6819
Merged
Conversation
`InlineActionSchema.params` is an `ActionParam[]` definition array; the showcase's pure-SDUI submit button authored a request payload map under the same name, and objectui's ActionRunner accepted both, discriminating on `Array.isArray`. One key, two fact-contracts — and the generated reference could only describe the array, so an author following the docs could not write a working `api` submit button. Maintainer ruling 2026-08-06 took direction A (a separate payload key, no same-name union). The separate key is `bodyExtra`, already declared on `ActionSchema` for exactly this and now picked onto the inline shape: `body` is taken by the script hook body and `payload` is already an alias pointing at `bodyExtra` (#5013), so no new name was available or needed. - pick `bodyExtra` onto `InlineActionSchema` - `params` keeps one meaning; the object form is refused with a message that names `bodyExtra` instead of the bare "expected array, received object" - ADR-0087 D2 live-window conversion `inline-action-api-params-to-body-extra` (+ D3 chain step) rewrites the object form at load - migrate the showcase contact form to the new key - regenerate action.mdx / spec-changes.json / upgrade guide / api-surface Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011sGk4SKHqGRgmmqUok1P8M
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 112 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
August 8, 2026 23:40
This was referenced Aug 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5777
InlineActionSchema.paramsis picked fromActionSchemaand is anActionParam[]definition array — the fields a dialog collects from the user before the action runs. What the showcase's pure-SDUI contact form authored on its submit button is a request payload map, and objectui'sActionRunneraccepted both, discriminating onArray.isArray. One key, two fact-contracts — and because the generated reference could only describe the array, an author (or an AI author) following the docs could not write a workingapisubmit button at all.This is the spec half of the cross-repo split the ruling ordered. The objectui half is a separate card,
Blocked-by:this one.The ruling (2026-08-06)
Quoted verbatim and untranslated — rewriting a quoted ruling is rewriting the ruling.
The key name:
bodyExtra, and why neither suggested name was availableThe ruled point is "a separate key"; the name was delegated to implementation review. Measured against
origin/main, the separate key already exists — the inline shape simply never picked it:ActionSchematodaybodyscriptaction's L1/L2 hook body. #4352's refinement rejectsbodyalongside any non-scripttype, so it could never carry anapipayload.payloadpayload→bodyExtra, written by #5013 for exactly this reason ("bodyis DECLARED on this schema …payloadis the live spelling that still needs pointing atbodyExtra").bodyExtratype:'api'— "static body fragment merged into the outgoing request body". Live in four objectui consumers.So this PR adds no new vocabulary: it picks the key the contract already owns onto the inline shape. An author who writes
payload:is renamed onto it by the existing alias; one who writesbody:on anapiaction is rejected by the refinement that owns that name. That is the strongest available reading of "参数定义与请求载荷是两件事,不做同名 union".bodyExtraalso already does the job semantically, which a third name would have had to re-earn: objectui's consoleapiHandlerrunsresolvePageVarTokensover it, so the page-variable tokens the showcase form depends on resolve there exactly as they did under the old object-formparams.What changed
packages/spec/src/ui/action.zod.ts—bodyExtrapicked ontoInlineActionSchema.paramskeeps ONE meaning, and its object form is now refused with a message that namesbodyExtraand says whatparamsis, instead of the bareexpected array, received objectan author cannot act on. Implemented as a customerroron the array schema, so element-level issues are untouched and a non-object input still gets the default message.inline-action-api-params-to-body-extra(protocol 17, live window, retires at 18) plus its D3 chain step. Rewrites object-formparamstobodyExtraat load —defineStack,validate,lint, and storedsys_metadatarows viaapplyConversionsToStoredItem.examples/app-showcase/src/ui/pages/contact-form.page.tsmigrated to the new key.content/docs/references/ui/action.mdx(the inline-action table now carriesbodyExtra— the docs hole the issue named),spec-changes.json,docs/protocol-upgrade-guide.md,authorable-surface/ui.json, api-surface.Three boundaries the conversion draws deliberately
Array.isArrayis the whole discriminator. A realActionParam[]definition array is left alone.type:'api'. Object-formparamson atype:'url'inline action is a third meaning again —ActionRunner.interpolateTargetreads it as the${param.X}interpolation scope andexecuteUrlreadsparams.newTab. Rewriting those into an api request body would be lossy, so they are out of scope and stay refused by the array-only field. Nothing in the reachable corpus authors that shape.bodyExtrathat says something different keeps both keys for the author to reconcile (ADR-0087 别名转换会把「被遮蔽的旧拼法」留在存量元数据里 —— 节点 config 收紧后它从静默丢弃变成执行期硬拒 #4923 house rule).Not extended to registered actions.
ActionSchemais parsed atdefineAction, so its array-onlyparamshas always refused the object form at the authoring door. The defect existed only on the inline path, wherePageComponent.propertiesis an open bag and nothing parsed the props until #5068. Registered actions already reach the payload throughbodyExtra.After this merges and before the objectui follow-up lands, a page authored with
bodyExtraon an inline action validates and publishes, and is then dropped one hop before the runner.The missing hop is precise and small. objectui's
element:buttonrenderer (packages/components/src/renderers/basic/elements.tsx) builds an explicit forward whitelist of action keys it hands to theActionRunner, and that list mirrors the old pick list exactly — it has nobodyExtraentry. Everything below it is already ready:ActionRunner'sActionDefdeclaresbodyExtra, and the consoleapiHandler(packages/app-shell/src/hooks/useConsoleActionRuntime.tsx) merges it into the request body after runningresolvePageVarTokensover it.Direction of the window: spec accepts ahead of the renderer, never the reverse. It affects new-key authors and, through the conversion, old-key authors too — the load-time rewrite produces
bodyExtra, which the current forward list also drops. That cost is the ruled sequencing (spec 半边先行), and it is recorded here rather than papered over. Per the split, this PR does not edit objectui; that is the follow-up card.Verification
pnpm --filter @objectstack/spec test— 345 files / 8870 tests, all pass.pnpm --filter @objectstack/spec exec tsc --noEmit— pass.check:*enumerated from.github/workflows/lint.yml, run one by one — 55/55 pass, includingcheck:spec-changes,check:upgrade-guide,check:authorable-surface,check:docs,check:api-surface,check:liveness,check:adr-0087-registration,check:nul-bytesandpnpm lint.objectstack validateon the example corpus exits 0 with zerocomponent-props-*findings — the SDUI 组件 props 没有解析闸门:PageComponent.properties是开放 record,ComponentPropsMap的 29 个站点从不被 parse(#4001 批 17 的 no gate 判定) #5068 diagnostic the issue opened on is gone.New tests pin the contract rather than the absence of a crash: the refusal case asserts the issue
code, theexpectedtype, the path, and that the message namesbodyExtra— atoThrow()-shaped assertion would stay green if the message regressed to the unactionable default, which is the actual defect. The conversion gets its own block for the discriminator (definition array untouched,urlaction untouched, both-keys case kept, idempotence).Generated by Claude Code