Repository navigation
Commit 40a35de
fix(app-shell): a dashboard draft the server refuses to read stays unopened in Studio Interfaces, whatever order the designers load in (#29922)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9Ubb53xYKskegHeLV9gUxkwMmb)_
Fixes #29884
Clause-②: no
Metadata-protocol: none
The two lines are copied from the claim (comment 6600806534).
## 维护者速读
- 改了什么:Studio Interfaces
打开一个服务器拒绝读取(403)的仪表盘草稿时,不论设计器先加载还是后加载,都只显示拒绝说明,不再打开空白的属性表单、画布和项目工具(Source),自动保存也没有任何可发送的内容。
- 为什么改:#29015
的修复只在设计器先注册时成立。内置设计器是动态加载的,多数新打开的页面(以及"所有元数据"里的项目链接)首屏没有设计器,于是空草稿被当成已加载;之后在
Source 里改一下,就会无版本前置条件地覆盖草稿(实测 200),草稿的权限门和所有仅草稿的改动丢失,发布后仪表盘对所有人可见。18.3.0
挡版项的修复不完整。
-
风险与回滚:只改拒绝读取(403)那一个分支,让它像对象、流程、应用三处已有的拒绝分支一样收回草稿归属。可读草稿、无草稿、其他读取失败的路径不变。回滚即撤销本
PR。
- 先看:`StudioDesignSurface.tsx` 叶子加载里 `'refused' in draftRead`
分支新增的五行;新测试
describe(`StudioDesignSurface.draftUnreadable-29015.test.tsx` 末尾)。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/studio-design/StudioDesignSurface.draftUnreadable-29015.test.tsx`,预期
6 个用例全部通过。
- 需要你做的:无,席位落地。
## What changes
- **Where the hole was.** The built-in designers register through a
dynamic import, so the Interfaces pillar's first render can find no
dashboard preview (`isEditable = !!Preview && !StudioCanvas` is false).
That render takes the no-designer arm of the leaf load, which installs
the leaf's clean `{}` buffer and stamps it as the leaf's
(`setDraftFor(leafKey)`, objectui#11272). When the preview registers,
the load runs again and reads the draft. On a 403 its refused branch
(added by #29132) only set the strip's error and returned, so the stamp
stayed. To `useDraftAutoSave` a stamped buffer is a loaded one, so the
canvas, the Properties inspector and the item tools (Source) opened on
`{}`, and the autosave was live with no version pin. A reload of a leaf
whose earlier read was answered (a publish, a rollback) kept its loaded
buffer the same way.
- **The fix.** The refused branch now takes the leaf's buffer back:
`setDraft({})`, `setDraftFor('')`, `forgetLeafVersion()`, the read
address cleared, `setIfDirty(false)`. The object (`setObjDraftFor('')`),
flow (`setDraftFor('')`) and app nav (`setAppDraftFor('')`) refused
branches already do this.
- **The no-designer arm is kept as it is.** objectui#11272 needs that
leaf's clean `{}` buffer. While `!isEditable`, the autosave is blocked
(`blocked: !current || !isEditable || …`) and the inspector needs
`isEditable`, so the arm sends nothing by itself. The hole was only the
refusal keeping its stamp.
- **Census of the other editors this surface hosts.** The object and
flow loads have no pre-registration arm that stamps ownership (both
effects start at `if (!current) return`), and their refused branches,
like the app nav's, already clear. Only the Interfaces leaf had this
ordering.
- **The "All metadata" deep link is the same load.**
`/studio/PKG/metadata/dashboard/NAME` navigates a dashboard to the
Interfaces pillar (`StudioMetadataArea` → `areaItemPillarPath`), so it
is covered by the same fix, not a second editor.
## Tests
A new top-level describe in the #29015 shared harness,
`StudioDesignSurface.draftUnreadable-29015.test.tsx`. The designer
registries are module state with no unregister, and the file's
`beforeEach` registers the dashboard preview, so each late-registration
case loads the pillar and its registries afresh (`vi.resetModules`),
with nothing registered. Each case first asserts the stamped state (the
Source tool is shown and the draft has not been read), then registers
the preview and a default inspector inside `act`.
- **Interfaces route, late registration:** the strip with the server
sentence shows; no canvas, no inspector edit, no Source tool; no save
after the autosave window; the stored draft still carries
`requiredPermissions`.
- **The area's deep link:** the same, mounted at the URL
`areaItemPillarPath` gives for `/studio/PKG/metadata/dashboard/NAME`.
This mounts the landing the area navigates to, not the area route
itself.
- **Same-leaf reload:** a readable draft opens, then a `publishNonce`
reload is refused; the loaded buffer is taken back and nothing is sent.
- **CONTROL, readable draft after late registration:** it opens
editable, and an inspector edit saves the served draft (its
`requiredPermissions` included) pinned `ifMatch: 'v1'`.
- **CONTROL, early registration:** the existing #29015 case (preview
registered in `beforeEach`) still refuses, unchanged.
<details><summary>Red at main, green with the fix, ablation</summary>
- At main `00f72f94` with only the test commit: `Tests 3 failed | 3
passed (6)`. All three new cases red on `expected <pre
data-testid="surface-draft"></pre> to be null`; both controls and the
#29015 case green.
- With the fix: `Test Files 1 passed (1)`, `Tests 6 passed (6)`.
- Ablation through `scripts/ablation-replace.mjs`: the refused branch's
`setDraftFor('')` replaced by `setDraftFor((prev) => prev)` (anchor 1 →
0, blob `a60c33a4f242` → `ec0372a0a886`). Result: `Tests 3 failed | 3
passed (6)`, the same three red. Restored: blob == HEAD
(`a60c33a4f242`), `git diff HEAD` empty. No build is involved: ui vitest
aliases app-shell to `src`.
</details>
<details><summary>Neighbour sweep and type-check (head 1a9786f, after
merging main)</summary>
- Every ui test file naming `InterfacesPillar` (57 files, the harness
included), two locked batches at `--maxWorkers=2`: 29 files / 155 tests
passed; 28 files / 283 tests passed.
- Narrowing, declared: the 223 files naming `StudioDesignSurface` are
not all run locally; the touched code is the Interfaces pillar's leaf
load, and CI's Test UI runs the rest.
- `pnpm exec turbo run type-check --filter=@object-ui/app-shell` (65
tasks, test files included): exit 0.
- `eslint` on the two touched ui files: 0 errors; the 36 warnings all
predate this PR (none is on a line it adds).
</details>
## Acceptance notes
- **Live, card steps 1–7** on a fresh showcase backend and the Console
Vite dev server: a writable package, a published dashboard with one
metric widget, an app whose nav names it, and a draft with
`requiredPermissions: ['showcase.export_data']` (admin's draft read:
403).
- Before (main's `StudioDesignSurface.tsx` served): 7 of 7 fresh loads
(5 on Interfaces, 2 by the deep link) show the strip AND the empty
Properties form (5 form controls) and the item tools. A Source edit to
the published body sent `PUT …?mode=draft&package=…&surface=studio` with
no If-Match, answered **200**, and admin's draft read flipped 403 → 200
(the gate was gone). That happened on both routes.
- After (this branch): 9 of 9 fresh loads (5 + 2 loads, plus the two
edit attempts) show only the strip, with 0 form controls, no item tools
and no PUT. The draft read stays 403.
- The swap was restored (blob == HEAD), and the after leg was re-run
once Vite served the restored file again.
- Probe script, JSON readings and screenshots:
`/mnt/project-files/issue-29884/shots/`.
- `Fixes #29884` is on the third line, after the project attribution
block, which the session requires as the body's first two lines.
## Verification
Local gate union on the final head `58c3a3f7` (main merged twice; the
second merge brought 5 backend-only commits, none touching `ui/`):
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derives 35 families for the 3 changed paths
(182 changed lines).
- The 34 non-vitest families: all `exit 0` on `58c3a3f7`, among them
`check-sdui-manifest.mjs` (exit 0, the record does not move for a
logic-only edit), `check-changeset-no-major`, `check-empty-changeset`,
`check-adr-0087-registration`, `check:query-options-erasure`,
`check:type-check-debt`, `check:cross-package-test-inputs`,
`check:test-source-alias`.
- `pnpm --dir ui exec vitest run scripts/`: on `1a9786f9` (the first
merge), `Test Files 170 passed | 2 skipped (172)`, `Tests 4979 passed |
2 skipped (4981)`, exit 0. The re-run on `58c3a3f7` finished green after
this PR opened (4979 passed; card report 6604287278). The second merge
touched no `ui/` file.
- `dispatch-gates --ran` on `1a9786f9`: 35 derived, 35 run, 0
NOT-MEASURED, 0 UNRUN.
- CI runs the rest: Type Check UI, Test UI, Lint UI, Build & E2E UI and
the required set.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jQx7EwgLR5PQRL3zQp)_
<!-- signed: domain:ui#3 · zhuangjianguo ·
session_01QsR2jV4dWohojjbTJTQq5m -->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent a04e9d7 commit 40a35de
3 files changed
Lines changed: 182 additions & 0 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 | |
|---|---|---|---|
| |||
106 | 106 | | |
107 | 107 | | |
108 | 108 | | |
| 109 | + | |
109 | 110 | | |
110 | 111 | | |
111 | 112 | | |
| |||
136 | 137 | | |
137 | 138 | | |
138 | 139 | | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
139 | 149 | | |
140 | 150 | | |
141 | 151 | | |
| |||
209 | 219 | | |
210 | 220 | | |
211 | 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 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5280 | 5280 | | |
5281 | 5281 | | |
5282 | 5282 | | |
| 5283 | + | |
| 5284 | + | |
| 5285 | + | |
| 5286 | + | |
| 5287 | + | |
5283 | 5288 | | |
| 5289 | + | |
| 5290 | + | |
| 5291 | + | |
| 5292 | + | |
| 5293 | + | |
5284 | 5294 | | |
5285 | 5295 | | |
5286 | 5296 | | |
| |||
0 commit comments