Skip to content

feat(spec): reject a body on a non-script action — it would never run (#3530) - #3548

Merged
os-zhuang merged 2 commits into
mainfrom
claude/modal-actions-object-resolution-su0iwz
Jul 27, 2026
Merged

feat(spec): reject a body on a non-script action — it would never run (#3530)#3548
os-zhuang merged 2 commits into
mainfrom
claude/modal-actions-object-resolution-su0iwz

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Part of #3530. The console-side half is objectstack-ai/objectui#2826.

The authoring trap

The action in #3530 is declared like this:

export const LogCallAction: Action = {
  name: 'log_call',
  type: 'modal',
  target: 'log_call',
  body: { language: 'js', source: `/* insert a record */`, capabilities: ['api.write'] },
  params: [ /* subject, duration, notes */ ],
};

The author's expectation — stated in the issue's Expected section — is that the modal collects the params and then "the action body executes with the collected input, and the record is written".

It never does. Action.body is documented as "Only meaningful when type === 'script'", and every non-script type dispatches on target instead (the page to open, the URL, the flow, the endpoint). There is no point at which a renderer would invoke the body. Nothing enforced that documented constraint, so the action passed validation, passed shape tests, and shipped a button that opens a modal and silently writes nothing.

Change

Added a parse-time refine rejecting body on any non-script action.

This is deliberately the same shape as the rule directly above it, which rejects a script action carrying neither body nor target (#2169 — "Mark Done does nothing"). Both catch the same class of defect: an action that validates, builds, and then no-ops at runtime, where the failure is invisible to every automated check and only surfaces as a user reporting that a button does nothing. The message names the fix rather than just the violation:

body only runs for type: 'script' — a non-script action dispatches on target and silently ignores its body. To collect params and then run the body, use type: 'script'; to open a page/modal, drop the body and keep type: 'modal' with target naming the page.

type: 'script' is the working shape for the reporter's intent: it collects the same params through the same dialog, then POSTs to /api/v1/actions/{object}/{name}, where the registered body runs with the collected input.

Compatibility

No action in this repo pairs a body with a non-script type — I scanned both repos before adding the rule, and the app-showcase and app-todo bundles both build clean against it.

Testing

  • New spec tests: a body is rejected across all five non-script types (modal, url, flow, api, form), and the corrected authoring from the issue (a modal action with params and no body) still parses.
  • packages/spec full suite: 258 files / 6842 tests pass.
  • packages/runtime (635 tests) and packages/objectql (1081 tests) pass.
  • @objectstack/example-showcase and @objectstack/example-todo build clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KYuc9N6YRbvoDMbCvJiX9f


Generated by Claude Code

…un (#3530)

`Action.body` is documented as "only meaningful when `type === 'script'`", but
nothing enforced it. A `type: 'modal'` action authored with `params` and a
`body` — expecting the modal to collect the input and the body to write the
record on submit — passed validation, passed shape tests, and shipped a button
that opened a modal and silently wrote nothing. Non-script types all dispatch on
`target` (the page to open, the URL, the flow, the endpoint); there is no point
at which a renderer would invoke the body.

This is the same invisible-failure shape as the existing rule that rejects a
`script` action with neither `body` nor `target` (#2169), so it is enforced the
same way: a parse-time error that names the fix — `type: 'script'` collects the
same `params` and does run the body, and a modal that only opens a page should
drop the `body` and keep `target` naming the page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KYuc9N6YRbvoDMbCvJiX9f
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
objectstack Ready Ready Preview, Comment Jul 27, 2026 5:33am
spec Building Building Preview, Comment Jul 27, 2026 5:33am

Request Review

@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tests protocol:ui tooling labels Jul 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

104 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 packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via 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/permissions/authorization.mdx (via @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/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.

…arget` resolves

Keeps the two action docs in sync with the parse-time rule added in this PR
(and with the console-side target resolution in objectui#2826):

- ui/actions: a second "trap" callout next to the existing dead-button one —
  a `body` on a non-script action is rejected, with `type: 'script'` + `params`
  named as the shape for "collect input, then run logic".
- protocol/objectui/actions: `body` on a non-script type is now a parse-time
  error, not just "not meaningful"; the Modal Actions section states the
  page → object → server-handler resolution order and that `params` are
  collected before the target opens.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KYuc9N6YRbvoDMbCvJiX9f
@os-zhuang
os-zhuang marked this pull request as ready for review July 27, 2026 04:05
@os-zhuang
os-zhuang merged commit f07808c into main Jul 27, 2026
16 of 17 checks passed
@os-zhuang
os-zhuang deleted the claude/modal-actions-object-resolution-su0iwz branch July 27, 2026 04:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants