Repository navigation
Commit dc87dfb
fix(app-shell): a package-door permission save keeps the pending draft's values (#27080)
<!-- claude-projects-attribution: {"github_login":"os-warren"} -->
_Requested by **Warren** · [project
thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ8HDu4gcLm3Z4tJj8SMXXi7D)_
Fixes #26837
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:Studio 包内权限集的保存,合并基底从「已发布版本」改为「当前待发布草稿」;没有草稿时仍用已发布版本。
- 为什么改:经 API 或其他入口写进草稿的 description、packageId 等编辑器不管的键,在 Studio
里勾一个格子就被已发布值覆盖,静默丢数据。
- 风险与回滚:保存前多读一次草稿;草稿读取失败时拒绝保存,提示与已发布重读失败相同(作者可再次编辑重试)。回滚即还原本 PR 的修复提交。
- 先看:`PermissionMatrixEditor.tsx` 的 `doSave` 包门分支(`base` 一行与草稿读失败判断);新测试
`PermissionMatrixEditor.packageDoorDraftBase-26837.test.tsx`;同文件新增的
`pendingDraftBody`。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.packageDoorDraftBase-26837.test.tsx`,预期
3 passed。
- 需要你做的:无,席位落地。
## What changes
- `doSave`'s package-door arm now reads the pending draft through the
load's own door, `readDraftForSave`, at the address the save writes (in
parallel with the existing `layered` re-read), and merges this package's
slice onto the draft when one exists. With no pending draft the base is
the published `effective`, and the 404 shape keeps the `?? payload`
base, both as before.
- A REJECTED save-time draft read refuses the save with the same
`perm.save.rereadFailed` text as the layered refusal (objectui#9420); it
never falls back to the published body. `readDraftForSave` resolves a
rejection as no answer and no pin, while a read that found no draft pins
`If-None-Match: *`, so that pair is the failure signal.
- `mergePermissionSlice` and `EDITOR_AUTHORED_KEYS` are unchanged:
measured, the key list already takes only the authored facets from the
edited body. The defect was the base only.
- The unwrap of a served draft envelope (strip read decorations,
objectui#8181) moves into one module-level helper, `pendingDraftBody`,
used by the load and by the save base, so the two can't drift apart.
- The environment door (no `packageId`) keeps its whole-record live save
unchanged.
## Why
The package door's save writes the package DRAFT, but its merge base was
the PUBLISHED body. Keys the editor does not author (`description`,
`packageId`, anything another door wrote) came from that base, so one
Studio edit replaced a pending draft's values with published ones (or
with none). The response was 200 and nothing showed an error.
## Tests
New `PermissionMatrixEditor.packageDoorDraftBase-26837.test.tsx` drives
the real page, load and autosave:
1. A pending draft whose `description`, `packageId` and an out-of-scope
row differ from published: every non-authored key and the out-of-scope
rows reach the wire equal to the draft, and the edited row lands.
2. Control, no pending draft: the base is the published body (its
`description`, no `packageId`, its rows).
3. The save-time draft read rejects: `client.save` is never called, the
refusal is on the page's error strip, and `getDraftCalls === 2` proves
the read ran.
## Acceptance notes
- The load-time version pin is left alone: the save-time draft read does
not re-record it. A draft that moved after the load is still refused as
a `409 METADATA_CONFLICT` and opens the conflict dialog, instead of
being merged silently.
- When a pending draft exists, out-of-scope rows (other packages'
grants) now come from the draft as well, since the draft is the document
this save replaces and Publish promotes.
- `readDraftForSave` has no explicit failure flag. This PR reads failure
from its documented answer/pin pair and stays inside
`PermissionMatrixEditor.tsx`. An explicit flag would be a change to
`DraftConflictDialog.tsx`, which is outside this card's file surface.
- Serial work in the same file: PR #26999 and PR #27064 are both still
open. Neither has a hunk in the save-base region. PR #27064 edits the
load path near the `draftBody` line this PR changes, so whichever lands
second may need a small textual merge.
## Verification
Head `4dd4403f` (the fix commit `d87310b4` plus a merge of `origin/main`
at `f708cf22`).
<details><summary>Ablation (two legs, both restored to the HEAD
blob)</summary>
`node scripts/ablation-replace.mjs` under the verify lock, tests import
the editor by relative path (no dist leg):
- Base back on `fresh?.effective ?? payload`: anchor 1 → 0, blob
ae1632b → 55370e9d; the draft-survival test went red with `expected
'Published description' to deeply equal 'Set through the API'` (1
failed, 2 passed); restored, blob equals HEAD, `git diff HEAD` empty.
- The draft-read refusal line deleted: anchor 1 → 0; the refusal test
went red with one PUT where none was expected (1 failed, 2 passed);
restored, blob equals HEAD, `git diff HEAD` empty.
</details>
<details><summary>Tests and type-check</summary>
- All 45 test files that reference the permission editor: 45/45 files,
321/321 tests passed on head `4dd4403f` (and on `d87310b4` before the
merge).
- 24 further app-shell test files naming the `permission` type (Studio
Access pillar, list pages, previews, draft conflict): 24/24 files,
392/392 tests passed on `d87310b4`.
- `pnpm --filter @object-ui/app-shell run type-check` (both `tsc`
projects, tests included): exit 0.
- `pnpm --dir ui run check:new-line-citations`: 0 new citations.
</details>
<details><summary>Derived gates (24 families, all exit 0)</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `4dd4403f` derived 24 families; each was
run and its exit recorded before any pipe; `--ran` reconciliation: 24
derived, 24 run, 0 NOT-MEASURED, 0 UNRUN. `--tier`: no path-derived
mandate. Changed lines 281, under the 3000 threshold.
</details>
No changeset: `@object-ui/app-shell` is private (`skip-changeset`).
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (CI runs the repo lint)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset. (private
package, no changeset)
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (not touched)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English. (the 维护者速读 block is
the os-dev order's required Chinese summary)
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JQwxba8E9EE7oBM24GbdNZ)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 19a2b61 commit dc87dfb
2 files changed
Lines changed: 263 additions & 18 deletions
File tree
- ui/packages/app-shell/src/views/metadata-admin
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
297 | 297 | | |
298 | 298 | | |
299 | 299 | | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
300 | 313 | | |
301 | 314 | | |
302 | 315 | | |
| |||
910 | 923 | | |
911 | 924 | | |
912 | 925 | | |
913 | | - | |
914 | | - | |
915 | | - | |
916 | | - | |
917 | | - | |
918 | | - | |
919 | | - | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
920 | 930 | | |
921 | 931 | | |
922 | 932 | | |
| |||
1344 | 1354 | | |
1345 | 1355 | | |
1346 | 1356 | | |
| 1357 | + | |
| 1358 | + | |
| 1359 | + | |
| 1360 | + | |
| 1361 | + | |
| 1362 | + | |
| 1363 | + | |
| 1364 | + | |
| 1365 | + | |
| 1366 | + | |
| 1367 | + | |
| 1368 | + | |
| 1369 | + | |
1347 | 1370 | | |
1348 | | - | |
1349 | | - | |
1350 | | - | |
1351 | | - | |
1352 | | - | |
1353 | | - | |
1354 | | - | |
1355 | | - | |
1356 | | - | |
1357 | | - | |
| 1371 | + | |
| 1372 | + | |
| 1373 | + | |
| 1374 | + | |
| 1375 | + | |
| 1376 | + | |
| 1377 | + | |
| 1378 | + | |
| 1379 | + | |
| 1380 | + | |
| 1381 | + | |
| 1382 | + | |
| 1383 | + | |
| 1384 | + | |
1358 | 1385 | | |
1359 | | - | |
| 1386 | + | |
1360 | 1387 | | |
1361 | 1388 | | |
1362 | 1389 | | |
| |||
0 commit comments