Repository navigation
Commit d99cd3b
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBDQpmSg3rBeadS7SCnY2LPV)_
Fixes #30089
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:Studio
列表视图属性面板新增「批量操作」区:可从本对象已有的操作(含内置「删除」)中挑选放进选择栏(`bulkActions`),也可新增、修改、删除「字段更新」(`bulkActionDefs`:按钮文字、要填写的字段、确认提示)。
- **为什么改**:按裁决 A 执行。动作编辑器一直提示「到列表视图上声明批量操作」,视图面板却无处可填;现在实施和运营在 Studio
里就能配出「批量修改负责人」这类按钮,不用找开发,内核不写死任何业务字段。
- **风险与回滚**:只加界面,spec、服务端、元数据键都不动;不声明这两个键的视图保存结果与之前逐字节一致。回滚即还原本 PR。
-
**先看**:`ListViewBulkActionsSection.tsx`(写入逻辑与「删除是否保留」的判定)、`ObjectListViewInspector.tsx`(只加了一处挂载)、`ObjectListViewInspector.bulkActions-30089.test.tsx`。
- **两分钟验证**:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/studio-design/ObjectListViewInspector.bulkActions-30089.test.tsx`,预期
11 条全部通过;或看截图 `30089-4`(运行时选择栏出现「批量修改负责人」)与 `30089-7`(成功 2 / 2)。
- **需要你做的**:无,席位落地。
## What changes
A new section in the Studio list-view panel
(`ListViewBulkActionsSection.tsx`), mounted for grid views only, below
Sort. It edits exactly the two keys `ListViewSchema` already declares
(`packages/spec/src/ui/view.zod.ts`, "Row & Bulk Actions"); no spec,
server or new metadata key.
- **Selection-bar actions (`bulkActions`).** A picker lists the object's
actions by label (read from the draft-overlaid object, the same read the
panel's field pickers make) plus the built-in Delete under its reserved
name `delete`. Picked names are listed with a remove button; a name the
object does not declare is marked "Not an action of this object".
- **Field updates (`bulkActionDefs`, `operation: 'update'`).** "Add a
field update" opens a form held locally until it has a button label and
at least one field; Add then writes one def: `name` (`update_FIELDS`,
unique among the view's selection actions), `label`, one `required`
param per field (named for the field so the value is patched there; a
select's options and a lookup's object copied from the field; a `user`
field needs no object), and an optional `confirmText`. Before writing,
the def is checked with the spec's `BulkActionDefSchema`; a refusal is
shown and nothing is written. Fields offered: writable fields of a type
the bulk dialog can collect (text, number, date, select family,
lookup/master-detail with a reference, user), no read-only, formula or
platform-injected hidden field.
- **Editing in place.** An existing update def's label, fields (the last
field cannot be removed) and confirmation are edited in place; a served
`confirmText` arrives as the parsed `{ dialect, source }` envelope,
which is shown as its sentence and kept on edit. A def of another kind
(`delete`, aggregate `custom`) is listed with "Edited in metadata" and a
remove button.
- **Empty keys are removed**, never written as `[]`, so a view that
declares neither key saves byte-for-byte as it was read.
- **Customized view of an installed app.** The section writes through
the panel's existing `writeBody` buffer, so on a read-only base the
first edit goes through the existing view customization copy
(`ListViewCustomization.writeTarget`): saved as a draft in the `.custom`
pair with `overrides: { package, basedOn }`. Nothing about that path is
rebuilt.
- i18n: 21 keys each in the `studio` namespace's `en` and `zh`.
### The card's "Not measured" items, measured
- **Does the default Delete leave the selection bar when a view declares
`bulkActionDefs`?** Yes. Read from `ObjectGrid`'s `effectiveBulkActions`
and confirmed live: with neither key declared the grid shows its
built-in Delete; once the view declares either key, Delete stays only if
`bulkActions` names `delete`. The section states which of the three
holds (`data-state` = `default` / `kept` / `dropped`) and offers "Delete
(built-in)" in the action picker so the author can keep it. Live: shots
`30089-4` (declared with `delete`: bar shows 批量修改负责人 + 删除) and
`30089-10` (`delete` removed: bar shows 批量修改负责人 only). Edge: the grid
filters an update def out when the caller lacks the edit verdict, and
then its implicit Delete comes back; the section describes the author's
declaration, not a given user's bar.
- **How a customized view of an installed app merges these keys.** It
does not merge per key: the customization is the pair's whole copy of
the view (`overrides` declared), and the served effective view is that
copy. Live on `com.example.crm` 4.0.0: after the first edit, `GET
/meta/view/crm_lead.all?state=draft&package=com.example.crm.custom` held
`bulkActions`, `bulkActionDefs` and `overrides: { package:
com.example.crm, basedOn: 4.0.0 }`; after publish, `GET
/meta/view/crm_lead.all/layers?package=com.example.crm` answered the
effective view with both keys.
## Tests
- `ObjectListViewInspector.bulkActions-30089.test.tsx` (new, 11 cases;
the real hook and panel over a real `MetadataClient` whose transport
judges each view PUT with the installed spec's `view` schema): picking
writes `bulkActions`; adding a field update writes one def
`BulkActionDefSchema` accepts (user / select options / lookup object);
add waits for a label and a field; edit label, fields, confirmation and
remove round-trip, emptied key removed; served envelope confirmation
edited as its sentence; removing the last action removes `bulkActions`;
other-kind def and unknown name; read-only offers no edit; CONTROLS: a
view with neither key saves byte-for-byte as read, an edit elsewhere
adds neither key, a kanban has no section.
- `InterfacesPillar.customize-28562.test.tsx` (shared harness, new
top-level `describe`): on a read-only base, the first field update is
written into the pair's copy with the override declared, pinned to "no
draft there", the base untouched.
<details><summary>Runs (head d8c1f11)</summary>
All under `scripts/pm/os-verify-lock.sh`, from the `ui/` root as
`ui/AGENTS.md` prescribes.
- `pnpm --dir ui exec vitest run
packages/app-shell/src/views/studio-design/InterfacesPillar
packages/app-shell/src/views/studio-design/StudioDesignSurface
packages/app-shell/src/views/studio-design/ObjectListViewInspector` on
`d8c1f112`: Test Files 149 passed (149), Tests 1036 passed (1036), lock
VERDICT command-exit 0. Narrowed to the Studio pillar, surface and
list-view panel files; the rest of Test UI is CI's.
- The new file alone on `5baab8fe`: 11 passed (11).
- Census and namespace suites (`studio-namespace`,
`studio-namespace-lazy`, components `clickableNonControlCensus-13461`,
`iconOnlyButtonCensus-12143`, app-shell
`product-neutral-renderer.ratchet`, plugin-form
`affordanceGrantMap-12082`): 6 files, 115 passed.
- `pnpm --filter @object-ui/i18n run type-check && pnpm --filter
@object-ui/app-shell run type-check`: exit 0. `npx eslint` on the
touched files: 0 errors (warnings are the existing
`react-refresh/only-export-components` kind).
- `node scripts/pm/dispatch-gates.mjs --commands` on `d8c1f112`: 35
derived commands, each run, every one `exit 0`; `--ran` reconciliation:
35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. Includes `node
scripts/check-sdui-manifest.mjs` (exit 0).
- `pnpm --dir ui run check:new-line-citations`: 0 new citations, exit 0.
</details>
<details><summary>Ablation</summary>
Each mutation went through `scripts/ablation-replace.mjs` (anchor hit 1
→ 0, blob restored to HEAD, `git diff HEAD` empty). The test imports
resolve app-shell from `src` (no dist involved), so no rebuild was
needed between legs.
- `writeNames` (the `bulkActions` write) made a no-op, on `07a2728e`: 2
failed / 8 passed in the new file (the picking pin and the
remove-last-action pin).
- `writeDefs` (the `bulkActionDefs` write) made a no-op, on `07a2728e`:
4 failed / 32 passed across the new file and the customize harness (add,
edit/remove round-trip, other-kind remove, and the pair-copy pin).
- The served-envelope read (`envelope.source`) replaced with an empty
string, on `d8c1f112`: 1 failed / 10 passed (the envelope pin).
</details>
## Acceptance notes
- The section is shown for grid views only. Non-grid list views (kanban,
gallery, …) render their own bulk bar for `bulkActions` but not
`bulkActionDefs` (`ListView.tsx` reads only predicates from it), so the
section is withheld there rather than offering a field update that would
never render. Not filed: a reading, no defect.
- Each param is written `required: true`: the executor spreads params
straight into the patch, so an empty optional value would clear the
field on every selected record. An author who wants an optional field
edits the def in metadata.
- Labels in a def are plain strings (the spec's own note: an authored
def is not i18n-resolved); the section writes the label as typed.
- `rowActions` (also in the "Row & Bulk Actions" block) is not part of
this card's ruling and is untouched.
## Verification
Browser, `examples/app-crm` dev boot from this branch (merged with
`main`), console on Vite, zh-CN, admin. Shots in the project's
`ui-orders/30089-shots/` folder:
1. `30089-1-studio-new-field-update.png` — 定制 pressed on the read-only
线索 view; the new field-update form filled: 批量修改负责人, field 分配给,
confirmation.
2. `30089-2-studio-section-saved.png` — the def saved, 删除(内置) picked;
note reads that Delete is kept; header shows the pair override.
3. `30089-3-studio-reloaded.png` — after reload the confirmation reads
back as its sentence.
4. `30089-4-runtime-selection-bar.png` — published; two rows selected;
bar shows 批量修改负责人 and 删除.
5. `30089-5` / `30089-6` — the dialog asks 分配给 (person picker), then
confirms 「把 2 条线索改派给 Dev Admin?」 with the two records.
6. `30089-7-runtime-after-run.png` — 成功 2 / 2; `30089-8` the list shows
Dev Admin on both rows (API read agrees).
7. `30089-9` / `30089-10` — 删除 removed in Studio (note: not shown),
published; the bar shows 批量修改负责人 only.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01WGufzhfh9quLdZYWtWEMLK)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01WGufzhfh9quLdZYWtWEMLK
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0e42aef commit d99cd3b
7 files changed
Lines changed: 1201 additions & 0 deletions
File tree
- .changeset
- ui/packages
- app-shell/src/views/studio-design
- i18n/src/namespaces/studio
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
38 | 42 | | |
39 | 43 | | |
40 | 44 | | |
| |||
666 | 670 | | |
667 | 671 | | |
668 | 672 | | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
669 | 714 | | |
670 | 715 | | |
671 | 716 | | |
| |||
0 commit comments