Repository navigation
Commit 272fec0
feat(studio): edit an email template or a translation in the All metadata area, and customize a read-only package's one per locale (#28743)
Fixes #28569
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:Studio「All metadata」区里,`email_template` 与 `translation`
不再落到通用条目页,而是打开一个简化表单编辑器:Setup 表单的字段(served schema,缺省用内置 schema)+
该类型已注册的预览(`EmailTemplatePreview` /
`TranslationPreview`,挂载复用,非拷贝)。可写包写自己的草稿;只读基座走 N-17 的 Customize
路径(ensure → 草稿写入 `<base>.custom` 并声明 `overrides: {package, basedOn}` →
头部 Publish 发布 pair → ⋯ 菜单「Restore package original」)。
- **为什么改**:此前改一个已安装包的邮件模板或翻译只能去 Setup,写出的是不属于任何包的行;本卡让它成为 pair
里的声明式覆盖(ADR-0146 D2),且邮件模板按 (name, locale) 逐语言覆盖,翻译整包替换并在首次保存前明说。
- **风险与回滚**:改动只在 ui 的 app-shell(新增
`StudioBundleItemEditor.tsx`,`StudioMetadataArea`
的条目路由多一个分支,`useServedOverride` 多一个可选 `member`)与 studio
命名空间三条字符串;服务端零改动。Setup 页面、跨包 `~all` 腿、`?scope=environment`、`?review=1`
仍走通用条目页。回滚 = revert 本 PR。
-
**先看**:`ui/packages/app-shell/src/views/studio-design/StudioBundleItemEditor.tsx`
的 `doSave`(只读基座分支:ensure 一次、`overrideDraftBody`、`recordRead(...,
{ifNoneMatch:'*'})`)和 `restoreOriginal`(先删 pair 草稿再删行,均带 `member`);再看
`metadataArea.ts#areaItemOpensFormEditor`。
- **两分钟验证**:`cd ui && pnpm exec vitest run
packages/app-shell/src/views/studio-design/StudioBundleItemEditor.customize-28569.test.tsx
packages/app-shell/src/views/studio-design/StudioMetadataArea.routes-28561.test.tsx
packages/app-shell/src/views/studio-design/StudioDesignSurface.metadataArea-28561.test.tsx`,应全部通过。
- **需要你做的**:无需决策;正常评审即可。Acceptance notes 里有一条与 Interfaces
柱一致的已知行为(Restore 后头部待发布计数不即时刷新)。
## What changes
- **Routing.** `metadataArea.ts` gains `AREA_FORM_EDITOR_TYPES`
(`email_template`, `translation`) and
`areaItemOpensFormEditor({packageId, type, search})`: true inside one
package, outside `?scope=environment` and `?review=1`.
`StudioMetadataArea`'s item route (`AreaItemOpen`) decides once: the
form editor, else the pillar, else `MetadataResourceEditPage` as before.
The item is keyed by `type/name/member`, so another locale is another
editor (the draft-save guard's version pin is keyed by type, name and
package, not member).
- **The editor (`StudioBundleItemEditor.tsx`, new).** `SchemaForm` over
the served schema, else the default schema (as the generic item page
resolves it), with `name` and `overrides` hidden, and `locale` too for
an email template. Beside it, the type's registered preview. Autosave
through `useDraftAutoSave` and the shared `useDraftSaveGuard` (same
version pin and conflict dialog as the pillars). Header: type, label,
locale chip, override badge, autosave status, Customize, ⋯ (History,
Restore package original).
- **A read-only base.** Customize (`StudioCustomizeButton`) only unlocks
the form. The first saved edit calls the surface's `ensure()` once,
saves the draft into the pair with `overrides: {package, basedOn}`
(`overrideDraftBody`), and bumps the badge. Restore
(`StudioRestoreOriginalDialog`) deletes the pair's draft, then its row,
at the item's member, then re-reads the base. A walled caller sees
Customize disabled with the reason.
- **Per member.** Every read, save, badge read and restore of an email
template carries `?member=<locale>` from the list row's link
(`BUNDLE_MEMBER_PARAM`). `useServedOverride` takes an optional `member`
and passes it to `getDraft` / `layered`. A translation has no member;
before the first save on a read-only base the editor says the whole
bundle is replaced. For an email template it says only this locale is
customized.
- **Surface.** `StudioDesignSurface` passes its existing
`customization`, `publishNonce` and `onDraftSaved` to
`StudioMetadataArea`; nothing else in the surface moves.
- **Strings.** Three `engine.studio.area.formEditor.*` keys in the
studio namespace (`en`, `zh`). That table is `Record<string, string>`
outside the locales packs, so `Clause-②: no` holds as claimed.
## Tests
- `StudioBundleItemEditor.customize-28569.test.tsx` (new, 6): writable
draft of one member (no door, no declaration, no `_` keys, locale field
hidden, preview live); read-only Customize of email template zh-CN
(ensure once, pair draft body exact with the declaration, badge, base
untouched, pair read at member zh-CN); the other locale (en-US)
untouched; Restore deletes the pair's draft and row of zh-CN alone and
the base reads back; walled admin sees Customize disabled; translation
whole-bundle note then the whole bundle saved into the pair, no member
in any read.
- `StudioMetadataArea.routes-28561.test.tsx` (+4): writable translation
and read-only email template at member zh-CN open the editor;
`?review=1` and `~all` open the generic page.
- `StudioDesignSurface.metadataArea-28561.test.tsx` (+2): the surface
hands the area a read-only base's customization (`base`, `baseLabel`,
`basedOn`, `walled`, `pairId`, `ensure`), and null for a writable
package, whose saved draft moves the changes count.
<details><summary>Ablations (6, through
<code>scripts/ablation-replace.mjs</code>; each landed on disk, went
red, and was restored to HEAD by the tool)</summary>
| # | Mutation | Result |
|---|---|---|
| A1 | the editor's layered read drops `member` | editor file: 4 failed,
2 passed |
| A2 | the badge read (`useServedOverride`) drops `member` | 3 failed, 3
passed |
| A3 | `locale` shown in the email template form | 1 failed (writable
case), 5 passed |
| A4 | the pair draft saved without the `overrides` declaration | 2
failed (email Customize, translation), 4 passed |
| A5 | Restore's draft delete drops `member` | 1 failed (Restore), 5
passed |
| A6 | `areaItemOpensFormEditor` always false | routes file: 2 failed,
18 passed |
</details>
## Acceptance notes
- Restore package original does not refresh the header's pending count
at once; it emits the metadata-refresh pulse, exactly as the Interfaces
pillar's Restore (#28711) does.
- An email template's draft PUT takes no `?member=` (400
`VALIDATION_ERROR`, supported: force, mode, package, surface); the
member is the body's `locale`. The editor sends the body it read, locale
included and hidden from the form, so a save stays on its locale.
- Server serving was checked before any code: the layered read and the
served-override lookup are per member (`overlayMemberWhere`,
`memberOption(member)`), so the card's stop rule ("server serves or
gates a per-(name, locale) override wrongly") did not fire on reads. The
write gate's shipper lookup is per (type, name); see the report's
finding.
## Verification
- The three card test files: editor 6/6, routes 20/20, surface
metadata-area cases green.
- Wider set under `scripts/pm/os-verify-lock.sh`: every test file naming
`StudioCustomizeControls`, `useServedOverride`, `StudioMetadataArea`,
`metadataArea`, `StudioBundleItemEditor` or `InterfacesPillar`, plus
every test importing the studio namespace (`zh-base-term.i18n-13131`,
the count-family census among them): 159 files, 2064 tests passed, at
`37edd173` (main's merge after it touches no ui file).
- `turbo run type-check --filter=@object-ui/app-shell
--filter=@object-ui/i18n`: exit 0 (at `1f6a135c`; the later commits add
one lint comment and main's non-ui files). ESLint on the 10 changed
ts/tsx: 0 errors (warnings are the files' existing ones plus
`react-hooks` advisories).
- `node scripts/check-sdui-manifest.mjs`: exit 0.
- Derived gates (`node scripts/pm/dispatch-gates.mjs --commands`) at
`ca3cc565`: 34 of 34 run, each exit 0 (`check:type-check-debt` under the
lock); `--ran` reconciles 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN.
`pnpm --dir ui exec vitest run scripts/`: 169 files passed, 2 skipped.
- Live, on `objectstack dev --fresh` for `examples/app-showcase` (base
`com.example.showcase` 0.1.0, which ships `showcase_task_done_email` in
`en-US` only): ensure answered 201 then 200 with
`com.example.showcase.custom`; a declared draft of `en-US` in the pair
saved 200. The write side of the per-locale gate is the report's
finding, not this PR's.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01QsR2jTJcKuqRSJyv696zZ9
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 92a1638 commit 272fec0
11 files changed
Lines changed: 1174 additions & 25 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 | + | |
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | | - | |
60 | | - | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
61 | 62 | | |
62 | 63 | | |
63 | 64 | | |
| |||
144 | 145 | | |
145 | 146 | | |
146 | 147 | | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
147 | 151 | | |
148 | 152 | | |
149 | 153 | | |
150 | 154 | | |
151 | 155 | | |
152 | | - | |
| 156 | + | |
153 | 157 | | |
154 | 158 | | |
155 | 159 | | |
156 | 160 | | |
157 | 161 | | |
158 | 162 | | |
159 | | - | |
| 163 | + | |
| 164 | + | |
160 | 165 | | |
161 | 166 | | |
162 | 167 | | |
163 | 168 | | |
| 169 | + | |
| 170 | + | |
164 | 171 | | |
165 | 172 | | |
166 | | - | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
167 | 176 | | |
168 | 177 | | |
169 | 178 | | |
170 | | - | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
171 | 182 | | |
172 | 183 | | |
173 | 184 | | |
| |||
176 | 187 | | |
177 | 188 | | |
178 | 189 | | |
179 | | - | |
| 190 | + | |
180 | 191 | | |
181 | 192 | | |
182 | 193 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
| 17 | + | |
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| |||
67 | 67 | | |
68 | 68 | | |
69 | 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 | + | |
70 | 98 | | |
71 | 99 | | |
72 | 100 | | |
| |||
90 | 118 | | |
91 | 119 | | |
92 | 120 | | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
93 | 140 | | |
94 | 141 | | |
95 | 142 | | |
| |||
170 | 217 | | |
171 | 218 | | |
172 | 219 | | |
173 | | - | |
| 220 | + | |
174 | 221 | | |
175 | 222 | | |
176 | 223 | | |
| |||
185 | 232 | | |
186 | 233 | | |
187 | 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2682 | 2682 | | |
2683 | 2683 | | |
2684 | 2684 | | |
2685 | | - | |
| 2685 | + | |
| 2686 | + | |
| 2687 | + | |
| 2688 | + | |
| 2689 | + | |
| 2690 | + | |
| 2691 | + | |
| 2692 | + | |
| 2693 | + | |
| 2694 | + | |
| 2695 | + | |
2686 | 2696 | | |
2687 | 2697 | | |
2688 | 2698 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
51 | 64 | | |
52 | 65 | | |
53 | 66 | | |
| |||
127 | 140 | | |
128 | 141 | | |
129 | 142 | | |
130 | | - | |
| 143 | + | |
131 | 144 | | |
132 | 145 | | |
133 | 146 | | |
| |||
191 | 204 | | |
192 | 205 | | |
193 | 206 | | |
194 | | - | |
195 | | - | |
| 207 | + | |
| 208 | + | |
196 | 209 | | |
197 | 210 | | |
198 | 211 | | |
| |||
211 | 224 | | |
212 | 225 | | |
213 | 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 | + | |
0 commit comments