Repository navigation
Commit 693e100
feat(studio): customize a read-only base's flow into its pair, with one ledger switch and Restore package original (#28824)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbMDxQQZtBknmCatggWjGfjm)_
Fixes #28570
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:在 Studio 的自动化栏位里,只读基础包(如 HotCRM)的流程头部多了「自定义」按钮。第一次保存编辑时,经 ensure
接口拿到配对包 `BASE.custom`,把这个流程以同名草稿写进去,并声明 `overrides: { package, basedOn
}`。覆盖流程的头部显示「覆盖 HotCRM」徽标,仍然只用启用台账的那一个开关,不画定义开关。流程的 ⋯
菜单多了「恢复软件包原版」,确认后删除配对包里的草稿和行,基础流程重新运行。
- 为什么改:ADR-0146 D9 第 1–3 项(#28236 的 N-18)。以前想改 HotCRM 的流程只能在 Setup
克隆,两份都会跑;现在用同名覆盖替换它,开关还是同一个。
- 风险与回滚:低。只改 `app-shell` 的 studio-design 目录和 studio 文案(en/zh
各加一条),不改协议、服务端或 spec。是否已覆盖,读的是声明覆盖那一行本身(配对包草稿,否则 layered 读取的
`overlay`),不读信封里随基础包封印走的 `deletable`(服务端席位 1
的发现)。可写包的流程照旧有删除和定义开关(对照测试)。回滚即还原本 PR。
- 先看:`StudioDesignSurface.tsx` 里 `AutomationsPillar` 的
`doSave`(写进配对包并声明覆盖)、`restoreFlow`(两次删除)和 `flowSwitchEl`(覆盖流程不画定义开关);然后
`FlowItemMoreMenu.tsx` 的 `restore`。
- 两分钟验证:`cd ui && pnpm exec vitest run
packages/app-shell/src/views/studio-design/AutomationsPillar.customizeFlow-28570.test.tsx`,预期
10 条全过。
- 需要你做的:无,席位落地。
## What changes
On a read-only base, the Automations pillar now customizes a flow the
way the Interfaces pillar customizes a view, a dashboard or a report
(the card's Wanted 1 to 6).
- **Customize this flow (Wanted 1).** The flow's header draws
`StudioCustomizeButton`, N-17's control, with the same block reasons:
`walled` from the surface's `customizeDoorWalled`, `noVersion` when the
listed manifest states no version. Pressing it only unlocks the flow's
editors (canvas `onPatch`, the node inspector, the autosave). Nothing is
written until an edit is saved.
- **The first saved edit (Wanted 2).** `doSave` asks the surface's
ensure door once per flow (`StudioBaseCustomization.ensure`, the same
path `InterfacesPillar` takes) and writes the flow's draft into the
package that answer names, under the flow's own name. The body is
`overrideDraftBody(draft, base, basedOn)`: the served flow without its
`_` keys, declaring `overrides: { package, basedOn }`. The first save
into the pair is pinned to "no draft there" (`ifNoneMatch`), the later
ones to the version they were answered. The flow's load reads its draft
at the pair once one is listed, so the designer then edits that draft.
The rest of the package stays read-only: Delete flow, New similar's
target, the item tools and the definition switch keep their read-only
gates.
- **One switch (Wanted 3).** The open override keeps `FlowLedgerSwitch`
on the flow's name (`_status` row, unchanged). When no ledger row is
served, a customized flow draws no switch at all, where the base's own
flow keeps its locked definition switch. `toggleEnabled` stays refused
on a read-only package's flow, so Studio never writes the override's
`status` to mean on or off.
- **The badge (Wanted 4).** The header draws `StudioOverrideChip` from
`useServedOverride`: the pair's draft through the draft door, else the
layered read's `overlay`, which N-11 serves as the pair's row.
- **Restore package original (Wanted 5).** `FlowItemMoreMenu` gains a
`restore` prop that draws N-17's `StudioRestoreOriginalItem`. The pillar
passes it when the served declaration exists. Per server seat 1's
finding on the card, Restore is decided from the declared override's own
row, never from the served envelope's `deletable`, which follows the
base's seal. The confirmation (`StudioRestoreOriginalDialog`, new `kind:
'flow'`) says the base's flow runs again under the same switch, and
names the draft that goes first. Confirming deletes the pair's draft,
then the pair's row, both at the pair (`client.reset`), reloads the
flow, re-reads the declaration and the engine's rows, and the base's
flow reads back.
- **Refusals (Wanted 6).** A refused ensure door or a refused save into
the pair is shown as the server sent it. A refused publish of the pair's
flow goes through the surface's existing per-draft path, which carries
the server's reason as sent (`publishRefusal`,
`engine.studio.refusal.publishPlain`).
- **One new string**, `engine.studio.customize.restoreFlowBody`, in the
studio namespace's `en` and `zh`.
## Tests
`AutomationsPillar.customizeFlow-28570.test.tsx`, 10 cases, through the
mounted pillar, its real autosave, the real header controls and the real
`ensureCustomizationPackage` over a fetch stub that also serves the
`/automation` doors:
- Customize is offered and writes nothing; until it is pressed the
canvas edits nothing.
- The first saved edit asks the door once (201) and writes the pair's
draft under the flow's name with the declaration and no `_` key; the
base is untouched; no `/toggle` request is sent; the badge appears; one
switch, the ledger's; the second edit asks the door no more and is
pinned to `v1`.
- A published override: badge, no Customize, the ledger switch alone,
Restore offered although the layered envelope says `deletable: false`,
no Delete flow, and an edit saves into the pair keeping the declaration.
- Restore: the confirmation's title, the flow sentence and the draft
sentence; two deletes at the pair (draft, then row); the base's flow
reads back with Customize offered again.
- No ledger row served: the base's flow keeps its locked definition
switch (control); once customized no switch is drawn, and the saved body
keeps the base's `status`.
- A walled tenant admin: Customize disabled with the reason; nothing
asked or sent.
- A refused save into the pair is shown verbatim.
- A refused publish of the pair's flow reads with the server's sentence.
- CONTROLS: the base's flow with no override offers no Restore; a
writable package's flow keeps Delete flow and its definition switch,
with no Customize.
## Acceptance notes
- `FlowItemMoreMenu` lives in its own file,
`studio-design/FlowItemMoreMenu.tsx`, not inside
`StudioDesignSurface.tsx`, so the edit to it is that file. The claim
names `FlowItemMoreMenu` in the file surface.
- `setup/packagedFlowLedger.ts` was not touched: the ledger row is keyed
by the flow's name, and N-11 keeps `packagedFlowOwner(name)` answering
the base, so the existing hook already binds the override.
- The flow's item tools stay read-only on a customized flow, as the
Interfaces pillar keeps them on a customized item.
## Verification
All local runs at head `146e0b60` unless stated.
<details><summary>Touched-package tests</summary>
- New file: `Test Files 1 passed (1)`, `Tests 10 passed (10)`.
- 155 ui test files naming `AutomationsPillar`, `FlowItemMoreMenu`,
`StudioCustomizeControls`, the automations route, or importing the
studio namespace, in four batches at `47ed4fb8`: 40/40 files 336 tests,
40/40 files 851 tests, 40/40 files 283 tests, 35/35 files 587 tests, all
passed. `packages/i18n/`: 94 files, 1438 passed, 13 skipped. The commits
after `47ed4fb8` are a one-line render change to Restore's `disabled`
(it no longer reads a ref while rendering), the new no-ledger-row case
and two renamed test parameters. At `146e0b60` the new file and five
neighbouring files (`AutomationsPillar.ledgerSwitch-28148`,
`.deleteFlow-27356`, `.newSimilar-28567`,
`StudioDesignSurface.oneSurface-28572`,
`InterfacesPillar.customize-28562`) ran again: `Test Files 6 passed
(6)`, `Tests 52 passed (52)`.
- The rest of app-shell's suite is CI's Test UI (narrowing declared: the
set above is a `git grep` of the touched components and strings, blind
to tests that reach them indirectly).
</details>
<details><summary>Ablations (each one-shot through
`scripts/ablation-replace.mjs`, restored to the HEAD blob, `git diff
HEAD` empty)</summary>
- Save names no pair: 2 red. Restore not read from the declaration: 2
red. Editors stay locked: 3 red. No badge: 2 red. Load ignores the pair:
1 red. Walled block dropped: 1 red. Definition switch drawn on a
customized flow: 1 red (the no-ledger-row case). Dialog says the item
sentence: 1 red. Row delete dropped: 1 red.
</details>
<details><summary>Type-check, lint, derived gates</summary>
- `pnpm exec turbo run type-check --filter=@object-ui/app-shell
--filter=@object-ui/i18n --concurrency=2` at `146e0b60`: `Tasks: 66
successful, 66 total` (the first run, at `92ba27eb`, was red on two
unused parameters in the new test's doubles, TS6133; fixed in
`146e0b60`).
- ESLint on the six touched source and test files: 0 errors. In
`StudioDesignSurface.tsx` the React Compiler's "memoization could not be
preserved" warnings move from 27 to 29, for `restoreFlow` (the same
shape `deleteOpenFlow` already warns on); no other warning class is
added.
- `node scripts/pm/dispatch-gates.mjs --commands`: 34 derived families,
each run once at `92ba27eb` and exit 0, including `node
scripts/check-sdui-manifest.mjs` and `pnpm --dir ui exec vitest run
scripts/`. `--ran` reconciliation: `34 derived, 34 run, 0 NOT-MEASURED,
0 UNRUN`. The only commit after it renames two test parameters.
- Every family CI runs is read from this PR's check-runs.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jRWZNXu1HAHuMowQXS)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01QsR2jRWZNXu1HAHuMowQXS
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 2587485 commit 693e100
7 files changed
Lines changed: 737 additions & 23 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 | + | |
| 18 | + | |
| 19 | + | |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
23 | 26 | | |
24 | 27 | | |
25 | 28 | | |
| |||
52 | 55 | | |
53 | 56 | | |
54 | 57 | | |
| 58 | + | |
55 | 59 | | |
56 | 60 | | |
57 | 61 | | |
| |||
124 | 128 | | |
125 | 129 | | |
126 | 130 | | |
127 | | - | |
| 131 | + | |
| 132 | + | |
128 | 133 | | |
129 | | - | |
| 134 | + | |
130 | 135 | | |
131 | 136 | | |
132 | 137 | | |
133 | 138 | | |
134 | 139 | | |
135 | 140 | | |
| 141 | + | |
136 | 142 | | |
137 | 143 | | |
138 | 144 | | |
| |||
209 | 215 | | |
210 | 216 | | |
211 | 217 | | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
212 | 221 | | |
213 | 222 | | |
214 | 223 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
309 | 309 | | |
310 | 310 | | |
311 | 311 | | |
312 | | - | |
| 312 | + | |
| 313 | + | |
313 | 314 | | |
314 | 315 | | |
315 | 316 | | |
| |||
319 | 320 | | |
320 | 321 | | |
321 | 322 | | |
| 323 | + | |
322 | 324 | | |
323 | 325 | | |
324 | 326 | | |
| |||
328 | 330 | | |
329 | 331 | | |
330 | 332 | | |
| 333 | + | |
| 334 | + | |
331 | 335 | | |
332 | 336 | | |
333 | | - | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
334 | 342 | | |
335 | 343 | | |
336 | 344 | | |
| |||
0 commit comments