From 5c819ac2fac8e984e205d1a54c4700d8466f20ac Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Thu, 30 Jul 2026 23:05:57 +0800 Subject: [PATCH 1/2] feat(showcase): demonstrate bulk actions over a selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `bulkActions` — naming declared object actions so each SELECTED record is fanned out through the action runner — had no example in the showcase. It is also the only way to declare a bulk action: `action.bulkEnabled` was retired in spec 17 (#3896 close-out, #4054) precisely because nothing consumed it, and its tombstone prescribes this key. Adds a `bulk_actions` named view on showcase_task — the selection-bar twin of the existing `legacy_row_actions` fixture — naming both an action whose work is a sandboxed script (showcase_mark_done, via the platform action route) and one that is a custom endpoint (showcase_recalc_estimate, whose already-declared `recordIdParam` carries each record's id). Neither is a field patch, which is what distinguishes this from project.view.ts's `bulkActionDefs`: those mass-EDIT through the data API, this dispatches a real action per record. Verified in a browser against this app: selecting all 10 tasks and running each button issues 10 POSTs (one per record, all 200) to /api/v1/actions/showcase_task/showcase_mark_done and /api/v1/showcase/recalc respectively, and the records change server-side. No console errors. Co-Authored-By: Claude Opus 5 --- .../app-showcase/src/docs/showcase_tour_ui.md | 6 ++++ .../app-showcase/src/ui/views/task.view.ts | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/examples/app-showcase/src/docs/showcase_tour_ui.md b/examples/app-showcase/src/docs/showcase_tour_ui.md index 02af211b03..64bd411a54 100644 --- a/examples/app-showcase/src/docs/showcase_tour_ui.md +++ b/examples/app-showcase/src/docs/showcase_tour_ui.md @@ -57,6 +57,12 @@ canonical example of each linked from that page. surface. - `src/ui/actions/` — the ActionType × location matrix (script / url / modal / flow / api / form), visible as buttons across Task screens. +- Actions over a SELECTION come in two flavours, one view each: Task's + **Bulk Actions** names declared actions in `bulkActions`, and each selected + record is fanned out through the action runner (a script and a custom + endpoint, neither of them a field patch); Project's `bulkActionDefs` instead + mass-EDITS through the data API. `action.bulkEnabled` is not a third way — + it was retired in spec 17 and its tombstone points at `bulkActions`. ## Themes diff --git a/examples/app-showcase/src/ui/views/task.view.ts b/examples/app-showcase/src/ui/views/task.view.ts index 02ffe5cbba..66b813db30 100644 --- a/examples/app-showcase/src/ui/views/task.view.ts +++ b/examples/app-showcase/src/ui/views/task.view.ts @@ -107,6 +107,42 @@ export const TaskViews = defineView({ rowActions: ['showcase_recalc_estimate', 'showcase_quick_view'], }, + // ── Bulk actions — one declared action over the SELECTED records ────── + // The selection-bar twin of `legacy_row_actions` above, and — unlike that + // fixture — the CANONICAL authoring form, not a legacy one. `bulkActions` + // is the only way to declare a bulk action: `action.bulkEnabled` was + // retired in spec 17 (#3896 close-out) precisely because nothing ever + // consumed it, and its tombstone prescribes this key instead. + // + // Complements `project.view.ts`'s `bulkActionDefs`, which is the OTHER + // bulk vocabulary: inline defs that mass-EDIT records through the data + // API (`operation: 'update'` + a patch). Here the selected records are + // instead fanned out through the action runner, one dispatch each, so an + // action that is not a field patch at all — a script, a custom endpoint — + // works over a selection. Both names are already declared on the object: + // + // `showcase_mark_done` — type `script`; its sandboxed body flips + // `done`/`progress` per record via the platform action route. + // `showcase_recalc_estimate` — type `api`; POSTs the showcase's own + // `/api/v1/showcase/recalc`, with `recordIdParam: 'recordId'` (already + // declared for the row surface) carrying each record's id. + // + // Neither declares `list_toolbar`: a bulk action is not a toolbar action, + // it needs a selection. Naming it here is the whole declaration. + bulk_actions: { + label: 'Bulk Actions', + type: 'grid', + data, + columns: [ + { field: 'title' }, + { field: 'assignee' }, + { field: 'estimate_hours' }, + { field: 'progress' }, + { field: 'done' }, + ], + bulkActions: ['showcase_mark_done', 'showcase_recalc_estimate'], + }, + // 0 ── Tabular ─────────────────────────────────────────────────────── // ADR-0021 Phase 2: replaces the former `showcase_task_list` report // (a flat record list — a ListView concern, not analytics). From 0ef2c8529c8b82261420b538dea606f973bd96e0 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Thu, 30 Jul 2026 23:17:14 +0800 Subject: [PATCH 2/2] fix(showcase): translate the new view label, add the release-nothing changeset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The zh-CN bundle must carry every declared label — the i18n coverage ratchet counts untranslated declared strings per example and refused the 456 → 457 growth. And objectstack requires a changeset per PR; an empty one is the sanctioned declaration for a change that releases nothing (the showcase package is private). Co-Authored-By: Claude Opus 5 --- .changeset/showcase-bulk-actions-example.md | 7 +++++++ examples/app-showcase/src/system/translations/index.ts | 1 + 2 files changed, 8 insertions(+) create mode 100644 .changeset/showcase-bulk-actions-example.md diff --git a/.changeset/showcase-bulk-actions-example.md b/.changeset/showcase-bulk-actions-example.md new file mode 100644 index 0000000000..e8e139a92d --- /dev/null +++ b/.changeset/showcase-bulk-actions-example.md @@ -0,0 +1,7 @@ +--- +--- + +Showcase-only: a `bulk_actions` view on `showcase_task` demonstrating +`bulkActions` (declared object actions fanned out over the selected records), +plus the UI tour note distinguishing it from `bulkActionDefs`. Releases +nothing — `@objectstack/example-showcase` is private. diff --git a/examples/app-showcase/src/system/translations/index.ts b/examples/app-showcase/src/system/translations/index.ts index bf99c36a04..b27965f17c 100644 --- a/examples/app-showcase/src/system/translations/index.ts +++ b/examples/app-showcase/src/system/translations/index.ts @@ -231,6 +231,7 @@ export const ShowcaseTranslationBundle = { map: { label: '工作地点地图' }, chart: { label: '工时按状态分布' }, legacy_row_actions: { label: '旧式行操作' }, + bulk_actions: { label: '批量操作' }, }, }, showcase_account: {