Repository navigation
Commit c8e6d38
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBAjDcpfVVoUY3KDhYsNEFWg)_
Fixes #30088
## 维护者速读
- **改了什么**:已安装(只读)应用的对象,在
Studio「操作」页现在可以「新建」操作:同一套预设和编辑器,保存为定制包(`BASE.custom`)里的独立 `action` 条目(带
`objectName`),列在厂商操作之后、带「新增」标记,可编辑、可删除,「仅看定制」会计入它。
- **为什么改**:运营想给已安装 CRM 的线索加一个「标记合格」按钮;以前只有可写包能加操作,最常见的已安装场景没有入口。
- **风险与回滚**:仅 `@object-ui/app-shell` 前端(外加两条 i18n 文案);无
spec、服务端、元数据类型改动;可写包的操作仍写在对象草稿里,行为不变。回滚即 revert 本
PR。删除已发布的新增操作是立即生效的(与删除流程一致),不经过发布。
- **先看**:`pairObjectActions.ts`(读写定制包里的操作,首写走 ensure 门)和
`ObjectActionsPanel.tsx` 里 `pair` 分支(厂商行只读、新增行可编辑)。
- **两分钟验证**:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/studio-design/DataPillar.pairActions-30088.test.tsx`(5
条全绿)。
- **需要你做的**:无,席位落地。
Clause-②: no
Metadata-protocol: none
## What changes
On a read-only base package (installed or code-shipped) that has, or can
get, its paired customization package (ADR-0146 D3), the Data pillar's
Actions view of a base object now offers New. Card Acceptance 1–4 all
ship here (diff about 940 changed lines, under the 1,500 split line).
1. **New on a read-only base.** The Actions view gets an optional `pair`
door from the Data pillar, passed only for a base object of a read-only
base (`extendsBase`, the same condition the object-extension fields
use). New offers the same presets (`actionPresets.ts`) and the same
editor. A preset that needs no input is written at once; one that waits
for a target is held in the panel as before and written by the first
edit that gives it one. The write is a standalone `action` item into the
pair, as a draft, `objectName` = the object, pinned `ifNoneMatch: '*'`.
The first write asks the ensure door (`customization.ensure`) and every
write names its answer, never a spelled `.custom` id. The object draft
is not touched.
2. **Vendor rows read-only, added rows editable.** The vendor's actions
are the served object's `actions` less any the pair holds (by name, and
by `_packageId` = the pair, which the served object carries on a folded
pair action: measured below). They open read-only with no Delete. The
pair's actions on this object (its published list and its drafts, each
draft read at the address its save writes so the version pins the save)
are listed after them with the compact "added" `CustomizationMark`. An
edit autosaves into the pair's draft through the shared
`useDraftAutoSave` and `useDraftSaveGuard`. Delete removes the pair's
draft and then its published row, as Delete flow does. An added action
keeps the name it was created with: a rename is held with a line saying
how to save (same rule and reason as the hooks panel). The read's
top-level `_` keys are never written back.
3. **Pending changes and "only customizations".** The draft is in the
pair, so it appears in the surface's pending changes and publishes with
the pair's batch (no change needed there). The Actions list gets the
"only customizations (n)" toggle (`RailCustomizationFilter`), counting
the added actions that carry a mark and narrowing the list to them.
4. **Walled caller.** When the ensure door refuses this caller, New is
not offered and the pair's actions open read-only.
The object rail's own "only customizations" count is unchanged: it
counts rows by the base's served customization read, which lists
overrides and object-extension members and no standalone action.
Counting a base object there for a pair action would need either a
change to that read (server and spec) or a client-side computation the
mark rules forbid, so the count lives on the Actions list instead. See
Acceptance notes.
New i18n key `engine.studio.actions.renameHeld` (en, zh).
## Tests
`DataPillar.pairActions-30088.test.tsx`, real `DataPillar`, real Actions
view and real action editor over a metadata client double, 5 tests:
- New on a read-only base offers the four presets; `change_choice`
writes one `action` save at the pair with `objectName`, `mode: 'draft'`,
`ifNoneMatch: '*'`, asks the ensure door once, and writes no object; the
vendor row has no Delete and a disabled editor; the added row carries
the `added` mark.
- A pair action (published and drafted) is listed once, as the pair's; a
label edit autosaves into its draft at the pair with no `_packageId`;
Delete resets its draft and then its row at the pair.
- "only customizations" reads 1 and narrows the list to the pair's
action.
- Walled: no New, the pair's action read-only, no ensure call.
- CONTROL: on a writable package the new action is written into the
object draft's `actions`, with no `action` save and no ensure call.
<details><summary>Ablations (each through scripts/ablation-replace.mjs,
restored to the HEAD blob, git diff HEAD empty)</summary>
- Drop the pair route (`extendsBase && customization` to `false &&
customization` in the Data pillar's Actions branch): 3 failed / 2 passed
(walled and CONTROL stay green).
- Drop the door gate (`canAdd = pair ? !pair.walled` to `pair ? true`):
1 failed (walled) / 4 passed.
- Write the read's `_` keys back (`ownBody` keeps every key): 1 failed
(the edit pin) / 4 passed.
</details>
## Acceptance notes
Browser check on a dev boot of `examples/app-crm` (`pnpm dev:crm --
--fresh`) with the console on Vite, on `f8bdead2` (before the merge of
`main`). `com.example.crm` 4.0.0 is read-only and had no pair at boot.
Screenshots are in the project's `ui-orders/30088-shots/` folder.
- **Before** (main's two files served, confirmed by fetching the served
module): the 操作 tab lists the vendor's Convert Lead with no 新建
(`01-before-no-new.png`).
- **Add.** 新建 shows the presets (`02-after-new-menu-presets.png`).
修改下拉选择字段 sent `POST /packages/com.example.crm/ensure-customization`
(201, pair created) and `PUT
/meta/action/crm_lead_action_2?mode=draft&package=com.example.crm.custom`
(200). The row shows the 新增 mark, 仅看定制 (1), 删除, and the top bar 变更 1
(`03-after-added-in-pair.png`). Label 标记合格 and placement 列表 · 行
autosaved into the pair's draft (`04-after-edited-saved-to-pair.png`).
- **Publish.** 待发布的变更 lists 动作 标记合格 新增 under "保存到 CRM (minimal example)
的定制" (`05-after-pending-changes.png`); 全部发布 sent `POST
/packages/com.example.crm.custom/publish-drafts`, `publishedCount: 1`.
Back in Studio the action is listed once, marked
(`06-after-published-listed-once-marked.png`). `GET
/meta/object/crm_lead/layers?package=com.example.crm` served it in
`effective.actions` with `_packageId: com.example.crm.custom`, beside
the vendor's with none.
- **Run.** The runtime row menu of a lead shows 标记合格
(`07-after-runtime-row-menu.png`); it asked for 状态
(`08-after-run-dialog.png`), and with 合格 sent `POST
/actions/crm_lead/crm_lead_action_2` (200, `operation: update`); the row
turned 合格 with "操作已成功完成" (`09-after-run-result.png`).
- **Remove.** 删除 sent `DELETE
/meta/action/crm_lead_action_2?package=com.example.crm.custom` (200);
the list shows only Convert Lead (`10-after-removed-in-studio.png`) and
the runtime row menu no longer has it
(`11-after-removed-runtime-menu.png`).
- A rename of an added action is held with the line, and no save is sent
until the name is put back (`12-after-rename-held.png`); a second run
confirmed the edit's PUT carries no `_packageId` and that 删除 of a
draft-only action sends `DELETE
...?state=draft&package=com.example.crm.custom`.
Observed, not changed:
- The object rail's 仅看定制 does not count `crm_lead` for an added action
(see What changes). If the maintainer wants the object row marked too,
the base's customization read would have to list standalone pair actions
on base objects: a spec and server change, out of this card (Acceptance
5).
- Deleting a published added action takes effect at once, as Delete flow
does; it does not wait for a publish.
## Verification
All under `scripts/pm/os-verify-lock.sh` unless noted. Head `238e15e3`
(main `474c030b` merged).
- Build: full `pnpm build`, then `turbo run build
--filter=@object-ui/app-shell...` after the merge: exit 0.
- `@object-ui/app-shell` and `@object-ui/i18n` `type-check` (both
tsconfigs each): exit 0.
- Tests: every app-shell test file that imports `DataPillar`,
`ObjectActionsPanel` or `pairObjectActions` (79 files) plus the whole
`@object-ui/i18n` suite (95 files): 174 files, 1977 passed, 13 skipped.
Narrowed from the 214 files importing `StudioDesignSurface`: this diff
changes only the Data pillar's Actions branch there; CI's Test UI runs
the rest.
- `npx eslint` on the 4 touched source/test files (unlocked): 0 errors;
the warnings are all pre-existing ones in `StudioDesignSurface.tsx`.
- `node scripts/pm/dispatch-gates.mjs --commands` on head `238e15e3`: 35
commands, all recorded `:: exit 0` (including `check-sdui-manifest.mjs`,
`vitest run scripts/` and `check:type-check-debt`); `--ran`: "35
derived, 35 run, 0 NOT-MEASURED, 0 UNRUN".
- Changed lines against `main`: about 940 (+908 / -30, 7 files), under
3,000.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01WGufzemmgi8p8uY913FXHx
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0c666c3 commit c8e6d38
7 files changed
Lines changed: 908 additions & 30 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 | + | |
| 17 | + | |
| 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 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
0 commit comments