Repository navigation
Commit 6ff27b3
fix(app-shell): Studio editors edit a translated type's pending draft as stored, not as served (#30912)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoDFP3MiaZLZFMmxVoftra8p)_
Fixes #30859
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:Studio
里五处编辑器(数据页的表单布局、列表视图面板、界面页的应用导航和它打开的页面/仪表盘/报表/视图、数据集等打包项编辑器)在有待发布草稿时,编辑副本改取分层读取里作者原样存的草稿,不再取按控制台语言翻译过的草稿读取;走的是
#30858 落地、#30877 移到 `@object-ui/data-objectstack` 的同一处读法。
- 为什么改:中文控制台里,第一次保存正确,刷新后装入的是中文草稿,再保存一次就把中文当成作者原文存进去,英文用户随后看到中文;在 zh-CN
实测栈上五处全部复现。
-
风险与回滚:只改客户端读哪一层,不改服务端和协议;服务端不翻译的类型照旧用读到的草稿。草稿存在但分层读取没给出原文时,编辑器不装副本、说明读不到、不保存。回滚即
revert 本 PR。
- 先看:新文件 `studio-design/authoredDraft.ts`(只是调用 `authoredBuffer`
的一处入口),再看
`useFormViewDraft.ts`、`ObjectListViewInspector.tsx`、`StudioBundleItemEditor.tsx`、`StudioDesignSurface.tsx`
里各自加载草稿的那几行。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/studio-design/DataPillar.formViewLayout-30057.test.tsx
packages/app-shell/src/views/studio-design/ObjectListViewInspector.draftUnreadable-29148.test.tsx
packages/app-shell/src/views/studio-design/StudioDesignSurface.draftUnreadable-29148.test.tsx
packages/app-shell/src/views/studio-design/StudioBundleItemEditor.customize-28569.test.tsx`,应见
35 个测试全绿。
- 需要你做的:无,席位落地。
- **席位意见**:ui 席 2 对照 head `f24ebd58` 的 diff 复核:28 个文件、+629 /
−66,无受治理路径。Studio 五处在有待发布草稿时改从分层读取的 `draft` 层(在草稿读取的同一地址)取可编辑副本,经新的
`authoredDraft.ts` 调用 `@object-ui/data-objectstack`
的同一读法,不另写一份;层里没给存储副本就不装缓冲并给出既有的「草稿不可读」提示;无草稿时仍用 `effective ??
code`。`EmbeddedItemEditor` 与 `StudioNavItemInspector` 实测不复现、未改,读数在正文。CI
全绿,Clause-② no(无新导出),不欠契约复审。同意落地。
## What changes
The `?state=draft` read translates a translatable type's body for the
console's `Accept-Language`. Five Studio editors seeded their edit
buffer from that served body when a draft was pending, so a save stored
the translation as the author's text. Each now takes the stored draft
from the layered read's `draft` layer, through `authoredBuffer` from
`@object-ui/data-objectstack` (the reading #30858 landed and #30877
moved there), never a copy of it.
- `studio-design/authoredDraft.ts` (new): `readAuthoredDraft(client,
type, name, servedDraft, at)` is the one call the five sites make. For a
type `editsAuthoredLayers` names, it reads `client.layered(type, name, {
packageId, member })` at the address the draft read was asked at and
returns `authoredBuffer(type, layers, servedDraft)` with those layers.
Any other type keeps the served draft as its buffer, as before.
- `useFormViewDraft.ts` (Data pillar, Form tab): the pending form view
draft is the stored one. Unserved: no buffer, the tab's failure strip
says the pending draft cannot be read.
- `ObjectListViewInspector.tsx` (list view panel): the same; unserved is
the panel's existing `draftUnreadable` strip with no Details. The
copilot marks' published layer is taken from that same layered answer
when the draft was read at the rail's package, so no second `/layers`
read is sent.
- `StudioBundleItemEditor.tsx` (bundle items: dataset and the other
bundle types): the same, with the bundle `member` on the layered read;
unserved mirrors the refused-draft path.
- `StudioDesignSurface.tsx`, Interfaces pillar: the app's navigation and
the leaf it opens (page, dashboard, report, view). Unserved: no nav
buffer or no leaf buffer, the strip says why, and the autosave has
nothing to send. The dashboard's published pairing reuses the layered
answer when it was read at the same package.
Sites measured and left alone (readings below):
`metadata-admin/EmbeddedItemEditor.tsx` and
`StudioNavItemInspector.tsx`.
## Tests
Pins, each a zh-CN served draft beside its stored `draft` layer, plus
the unserved case:
| site | file | zh-CN pin | unserved pin |
|:---|:---|:---|:---|
| form view | `DataPillar.formViewLayout-30057.test.tsx` | canvas shows
`Basic info` / `Money details`, Add group saves them and the label `Deal
form`, no CJK in the body | failure strip, no view save |
| list view | `ObjectListViewInspector.draftUnreadable-29148.test.tsx` |
buffer and PUT carry `Open tasks`, pinned `If-Match: v1`; one `/layers`
read | strip, no PUT |
| app nav | `StudioDesignSurface.draftUnreadable-29148.test.tsx` | rail
shows `Home Page` / `Reports Page`; removing one saves `['Home']`, app
label `Acme` | strip, no Edit, no save |
| leaf (page) | same file | canvas gets the stored page draft; a patch
saves label `Home` and block title `Welcome` | strip, no save |
| bundle item (dataset, read-only base pair) |
`StudioBundleItemEditor.customize-28569.test.tsx` | designer gets the
stored measures, an edit saves source labels | no buffer, nothing sent |
Eighteen existing studio-design suites used client doubles whose
`/layers` answer had no `draft` key beside a pending draft, so the
editors now read it as unserved. Each double now serves the pending
draft as stored, as the server does for an admitted author. Three suites
count `/layers` reads and misses
(`StudioDesignSurface.expectedMisses-11799`,
`ObjectListViewInspector.layersUnsaved-11799`,
`ObjectListViewInspector.copilotMarks-12725`): their counts now include
the stored draft's one layered read, which answers 200 and is never a
miss; each change carries an `objectstack#30859` comment. No other
assertion changed.
<details><summary>Ablation (head f24ebd5)</summary>
`node scripts/ablation-replace.mjs` on `authoredDraft.ts`, anchor
`return { buffer: authoredBuffer(type, layers, servedDraft), layers };`
replaced by `return { buffer: { kind: "buffer", body: servedDraft,
fromDraft: true }, layers };` (the served draft as the buffer, the
pre-fix reading): anchor 1 to 0, blob 717b739 to df3a56625e48. Over
the four pin files: `Tests 10 failed | 25 passed (35)`; the ten red are
exactly the ten new pins (five zh-CN, five unserved), every existing
test in those files green. Restored: blob equals HEAD, `git diff HEAD`
empty.
</details>
## Acceptance notes
- `EmbeddedItemEditor.tsx` does not reproduce, so it is not changed. Its
parents are objects only (fields, indexes, validations), and the object
write door un-translates an echoed body and drops injected columns.
Measured on a probe object in the showcase pair: the drawer's draft read
served `探针` with Chinese labels for `title`, `note` and eight injected
columns, the drawer's save of an edited `note` answered 200, and the
stored draft held `Probe`, fields `title` / `note` only, with source
labels. The published path (no draft, base `effective` with injected
columns) stored an overlay with `title` / `note` only, as authored.
- `StudioNavItemInspector.tsx` reads the served draft only to classify a
nav item, and never seeds a buffer or a save from it, so it is not
changed.
- One more request: a pending draft of a translated type now costs one
`/layers` read beside the draft read. The list view marks and the
dashboard pairing reuse that answer rather than asking again.
- The reading is imported from `@object-ui/data-objectstack`, its home
since #30877 (merged into this branch from main).
`authoredObjectBuffer.ts` is not edited; `StudioDesignSurface.tsx` keeps
its existing import of the object spellings from it.
## Verification
<details><summary>Wanted 1: live measurement on a zh-CN stack
(origin/main cf478ea)</summary>
`pnpm dev -- --fresh` (app-showcase), signed in as the seeded admin,
`Accept-Language: zh-CN`, `com.example.showcase.custom` ensured. Each
site's own request sequence on the read-only base and its pair: load
(draft read at the pair, 404; `/layers` at the base), PUT 1 of
`effective ?? code` with `overrides`, re-load (today's buffer is the
served draft), PUT 2 of that buffer, then the stored `draft` layer.
| site | item | served draft after PUT 1 | `/layers` `draft` after PUT 1
| stored after PUT 2 |
|:---|:---|:---|:---|:---|
| list view panel | view `showcase_task.urgent` | label `Urgent` → `紧急`
(1 string) | source text | `紧急` (1 string translated) |
| form view (Form tab) | view `showcase_task.form` | `sections[0].label`
`Task` → `任务信息` (1) | source text | `任务信息` (1) |
| app navigation | app `showcase_app` | label `功能展示`, nav `能力地图`,
`页面搭建`, `工作区`, `我的工作`, … (58) | source text | 58 strings translated |
| leaf page | page `showcase_capability_map` | label `能力地图` (1) | source
text | 1 translated |
| leaf dashboard | dashboard `showcase_ops_dashboard` | label `交付运营`,
description, widget titles (17) | source text | 17 translated |
| leaf report | report `showcase_hours_by_status` | label and
description (2) | source text | 2 translated |
| bundle item | dataset `showcase_task_metrics` | `measures[4].label`
`已完成任务`, `measures[5].label` `完成率` (2) | source text | 2 translated |
Every site reproduces. At load `effective` equalled `code` and was
served untranslated, so the published half was already source text; only
the pending draft was the leak. The layered read served the `draft`
layer at the pair for each, draft-only items included (200).
</details>
<details><summary>Local gates and tests (head f24ebd5)</summary>
Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` against this diff (36 families, the same
before and after the merge of main), each run with stdin closed;
reconciled with `--ran`: 36 derived, 36 run, 0 NOT-MEASURED, 0 UNRUN.
```text
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/ci/snapshot-next.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
pnpm --filter @object-ui/app-shell run type-check :: exit 0
```
UI tests (vitest root `ui/`, under the verify lock): `pnpm --dir ui exec
vitest run packages/app-shell/src/views/studio-design
packages/app-shell/src/views/metadata-admin`: `Test Files 1061 passed
(1061)`, `Tests 10214 passed | 1 skipped (10215)`, on head f24ebd5.
</details>
<!-- signed: domain:ui#2 · os-tesla · session_01L9qibw7DekEe3wx5f3zphf
-->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent d8b2295 commit 6ff27b3
28 files changed
Lines changed: 629 additions & 66 deletions
File tree
- .changeset
- ui/packages/app-shell/src/views/studio-design
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | | - | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
28 | 31 | | |
29 | 32 | | |
30 | 33 | | |
| |||
45 | 48 | | |
46 | 49 | | |
47 | 50 | | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
48 | 57 | | |
49 | 58 | | |
50 | 59 | | |
| |||
109 | 118 | | |
110 | 119 | | |
111 | 120 | | |
112 | | - | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
113 | 127 | | |
114 | | - | |
115 | 128 | | |
116 | 129 | | |
117 | 130 | | |
| |||
168 | 181 | | |
169 | 182 | | |
170 | 183 | | |
| 184 | + | |
171 | 185 | | |
172 | 186 | | |
173 | 187 | | |
| |||
286 | 300 | | |
287 | 301 | | |
288 | 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 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
95 | 95 | | |
96 | 96 | | |
97 | 97 | | |
98 | | - | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
99 | 102 | | |
100 | 103 | | |
101 | 104 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
65 | 68 | | |
66 | 69 | | |
67 | 70 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
95 | 95 | | |
96 | 96 | | |
97 | 97 | | |
| 98 | + | |
98 | 99 | | |
99 | | - | |
100 | | - | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
101 | 103 | | |
102 | 104 | | |
103 | 105 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
| 17 | + | |
| 18 | + | |
18 | 19 | | |
19 | 20 | | |
20 | 21 | | |
21 | | - | |
22 | | - | |
| 22 | + | |
| 23 | + | |
23 | 24 | | |
24 | 25 | | |
25 | 26 | | |
| |||
82 | 83 | | |
83 | 84 | | |
84 | 85 | | |
| 86 | + | |
85 | 87 | | |
86 | | - | |
87 | | - | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
88 | 91 | | |
89 | 92 | | |
90 | 93 | | |
| |||
224 | 227 | | |
225 | 228 | | |
226 | 229 | | |
227 | | - | |
| 230 | + | |
228 | 231 | | |
229 | 232 | | |
230 | 233 | | |
| |||
234 | 237 | | |
235 | 238 | | |
236 | 239 | | |
237 | | - | |
| 240 | + | |
| 241 | + | |
238 | 242 | | |
239 | 243 | | |
240 | 244 | | |
| |||
245 | 249 | | |
246 | 250 | | |
247 | 251 | | |
248 | | - | |
| 252 | + | |
249 | 253 | | |
250 | 254 | | |
251 | 255 | | |
252 | 256 | | |
253 | 257 | | |
254 | | - | |
| 258 | + | |
| 259 | + | |
255 | 260 | | |
256 | 261 | | |
257 | | - | |
| 262 | + | |
258 | 263 | | |
259 | 264 | | |
260 | 265 | | |
| |||
266 | 271 | | |
267 | 272 | | |
268 | 273 | | |
269 | | - | |
| 274 | + | |
| 275 | + | |
270 | 276 | | |
271 | 277 | | |
272 | 278 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
81 | 81 | | |
82 | 82 | | |
83 | 83 | | |
| 84 | + | |
84 | 85 | | |
85 | | - | |
| 86 | + | |
| 87 | + | |
86 | 88 | | |
87 | 89 | | |
88 | 90 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
16 | 20 | | |
17 | 21 | | |
18 | 22 | | |
| |||
49 | 53 | | |
50 | 54 | | |
51 | 55 | | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
52 | 63 | | |
53 | 64 | | |
54 | 65 | | |
| |||
76 | 87 | | |
77 | 88 | | |
78 | 89 | | |
79 | | - | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
80 | 99 | | |
81 | 100 | | |
82 | 101 | | |
83 | | - | |
| 102 | + | |
84 | 103 | | |
85 | 104 | | |
86 | 105 | | |
| |||
149 | 168 | | |
150 | 169 | | |
151 | 170 | | |
| 171 | + | |
| 172 | + | |
152 | 173 | | |
153 | 174 | | |
154 | 175 | | |
| |||
195 | 216 | | |
196 | 217 | | |
197 | 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
| 72 | + | |
72 | 73 | | |
73 | | - | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
74 | 78 | | |
75 | 79 | | |
76 | 80 | | |
| |||
0 commit comments