Skip to content

feat(spec,showcase): aggregate bulk dispatch — allow _selectedIds through the param gate, document execution: 'aggregate' (objectui#3139) - #4461

Merged
os-zhuang merged 5 commits into
mainfrom
claude/bulk-action-aggregated-call-j95epo
Aug 1, 2026
Merged

feat(spec,showcase): aggregate bulk dispatch — allow _selectedIds through the param gate, document execution: 'aggregate' (objectui#3139)#4461
os-zhuang merged 5 commits into
mainfrom
claude/bulk-action-aggregated-call-j95epo

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

objectui 侧:objectstack-ai/objectui#3141(实现)/ objectstack-ai/objectui#3139(诉求)。

背景

列表勾选 N 行后,一类「聚合批量动作」无法表达:动作被调用一次、拿到全部选中记录 id、服务端产出单一聚合产物(勾选多台设备 → 逐台生成二维码 → 一个 zip 一次下载;同类还有批量打印、合并导出 PDF)。objectui 的批量 runner 对 custom 只有逐行分支。

objectui#3141 用视图侧的 bulkActionDefs[].execution: 'aggregate' 表达它(ActionSchema —— spec 17 的 action.bulkEnabled 墓碑明示多选工具栏只由视图 bulkActions/bulkActionDefs 驱动),渲染器一次分发并注入 params._selectedIds: string[]不需要新的服务端路由:聚合动作的目标(api/url/script)本就能承接 id 数组。本 PR 是这套契约的服务端一半。

改动

1. ACTION_PARAM_BUILTIN_KEYS += '_selectedIds'(packages/spec/src/ui/action-params.zod.ts)

没有这一条,任何声明了 params 的聚合动作都会被 ADR-0104 严格参数门以 unknown_field 400 掉 —— 而 _selectedIds 是分发器注入的,作者永远无法把它声明为 param。与 recordId / objectName 同性质。纯放宽:不声明 params 的动作本就不走校验(所以 zip 场景对旧服务端也可用,这是加固而非硬依赖),也没有哪个作者书写的参数包会合法地带这个 key。附 pin 测试。

2. bulkActionDefs.describe() 扩写为作者侧契约(view.zod.ts)

这个 key 在 content/docs 里没有任何文档,describe 是事实上的作者文档。现在写明:两种执行模式、服务端应读 params._selectedIds recordId、结果 all-or-nothing(无法覆盖整批的处理器必须拒绝)、batchSize 在聚合模式不适用而 maxRecords 仍生效、以及 ${ctx.selection.ids} / ${ctx.selection.count} 的工具栏插值。content/docs/references/ui/view.mdxgen:docs 再生成。

3. liveness ledger(packages/spec/liveness/view.json)

bulkActionDefs 行的证据原本只指到 ListView.tsx 的转发点(一个 forwarding seam)。按 ledger README 的规矩,证据要指分发路径(authoring/preview 渲染器不算 runtime consumer —— 2026-06 那次扫描 13 条里错了 10 条正是这个原因),现在补上 useBulkExecutor 的聚合分支与 ObjectGrid.runBulkActionAggregate,并 pin 到 objectui 的实现 commit。

4. showcase specimen

showcase_recalc_selection(api 动作)+ task.view.tsbulk_actions 视图用 execution: 'aggregate' 声明它,与同视图里 showcase_mark_done(script,逐行)/ showcase_recalc_estimate(api,逐行)并排 —— 一个视图里同时看到两种执行模式。recalc 端点新增 _selectedIds 批量分支(一次调用重算整批并报计数;all-or-nothing)。showcase_tour_ui.md 同步。

验证

  • pnpm --filter @objectstack/spec test7230 通过
  • pnpm --filter @objectstack/spec check:generated — 8 项生成物全绿(gen:docs 已按 --fix 只再生成确认过期的那一项)
  • check:liveness / check:empty-state — 通过
  • packages/runtime 全量 1023 通过;examples/app-showcase typecheck 通过

已知遗留(已立案,不在本 PR 范围)

bulkActionDefs 在 spec 里仍是 z.array(z.record(z.string(), z.any())) —— 形状契约实际住在 objectui 的 @object-ui/types,作者拼错 key 静默通过(ADR-0018 第二词汇表 + ADR-0078 silently-inert)。类型化 + validate-action-name-refs 覆盖已按 Prime Directive #10 开 issue:#4457

🤖 Generated with Claude Code

https://claude.ai/code/session_01S9aiswZBzoVYsyLKRuGByE


Generated by Claude Code

…ough the param gate, document execution: 'aggregate' (objectui#3139)

objectui 17.1 adds an aggregate single-call mode for bulkActionDefs: an
execution: 'aggregate' entry dispatches its named object action ONCE for the
whole selection with every selected id injected as params._selectedIds. The
capability lives on the VIEW def (per the spec-17 action.bulkEnabled
tombstone); this is the server-side contract half:

- ACTION_PARAM_BUILTIN_KEYS += '_selectedIds' so the ADR-0104 strict gate
  does not 400 an aggregate dispatch against a param-declaring action —
  dispatcher-injected, never authorable, pure widening (+ pin test)
- bulkActionDefs describe documents the aggregate contract: read
  params._selectedIds (not recordId), all-or-nothing results, batchSize
  N/A, maxRecords advised, ${ctx.selection.ids} toolbar interpolation
- liveness view.json: bulkActionDefs evidence now cites the dispatch path
  (useBulkExecutor aggregate branch), pinned to objectui@636ac2f
- showcase: showcase_recalc_selection specimen — the recalc endpoint gains a
  _selectedIds batch branch, task.view's bulk_actions declares the aggregate
  def next to the per-record fixtures, tour doc updated
- content/docs/references regenerated (check:generated green)

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

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 1, 2026 8:58am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests protocol:ui tooling size/m labels Aug 1, 2026
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

107 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 @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/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/kernel/services.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/v17.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.

claude added 2 commits August 1, 2026 08:28
…(objectui#3139)

`bulkActionDefs` was absent from the List View properties table entirely — the
Zod `.describe()` was its only author-facing documentation, so the aggregate
mode had nowhere an author would look. Adds the table row plus a "Bulk Actions
Over a Selection" section contrasting the two keys and both execution modes,
and states the aggregate contract authors need: read `params._selectedIds`
(not `recordId`), all-or-nothing results, `batchSize` N/A, `maxRecords` for
expensive work, and the `${ctx.selection.ids}` toolbar interpolation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S9aiswZBzoVYsyLKRuGByE
…bjectui commit

objectui#3141 squash-merged as 4bf612c; the branch sha the note carried is not
in objectui's main history.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S9aiswZBzoVYsyLKRuGByE
claude added 2 commits August 1, 2026 08:44
Browser-verifying the aggregate specimen surfaced a dead affordance: the
action:bar renderer treats a missing/empty `locations` as "every location"
(objectui action-bar.tsx), so a locations-less action also lands on the LIST
TOOLBAR — where there is no selection, so the dispatch carries no
`_selectedIds` and the endpoint rejects it (HTTP 400). Declaring
`record_more` + `recordIdParam` puts the single-record entry where the
endpoint's per-record branch handles it and keeps it off the toolbar.

The renderer's lenient rule is filed as objectui#3142 — it contradicts
DeclaredActionsBar's strict one, and an aggregate-only action is the shape
that makes the difference visible.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S9aiswZBzoVYsyLKRuGByE
The aggregate specimen declared a label + successMessage with no zh-CN
translation, which the coverage ratchet caught (456 → 460). Translating both
recalc surfaces — the per-record one was already untranslated — brings the
showcase to 452; baseline updated per the ratchet-down prompt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S9aiswZBzoVYsyLKRuGByE
@os-zhuang
os-zhuang merged commit e6b1b69 into main Aug 1, 2026
18 checks passed
@os-zhuang
os-zhuang deleted the claude/bulk-action-aggregated-call-j95epo branch August 1, 2026 09:13
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/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants