Repository navigation
Commit d7df639
fix(app-shell): Studio's Automations, App and Data creates refuse an identifier a published or draft item already holds (#26987)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbQ45wNg97BSRszX9MTov2am)_
Fixes #26917
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:Studio
的「新建自动化」「创建应用」「新建对象」在写入前,先查同类型已发布和草稿中的同名项(跨所有软件包);标识已被占用就拒绝、不写入,提示里点名占用者;占用者在本支柱左栏里(本软件包的流程或对象)时给出「打开它」。三处与「访问」页共用一个检查函数。
- **为什么改**:首写保护(`If-None-Match: *` +
草稿模式)只拦已有草稿,不拦已发布的同名项。实测(见下)三种类型都会在已发布项上叠一份空白草稿,发布软件包就会用空白项替换线上的流程、应用或对象。
- **风险与回滚**:低。只改 `StudioDesignSurface.tsx`
的三个新建回调与对话框,沿用现有接口(列表、草稿列表);服务端、spec 不动。回滚即还原本 PR。
- **先看**:`findTakenIdentifier` 与
`CreateTakenNotice`(文件上部),`doCreateFlow` / `doCreateApp` /
`doCreateObject`
开头的占用检查;`StudioDesignSurface.createTaken-26917.test.tsx`。
- **两分钟验证**:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/studio-design/StudioDesignSurface.createTaken-26917.test.tsx`,预期
8 个测试全绿。
- **需要你做的**:无,席位落地。
## What changes
- **One shared check.** `findTakenIdentifier(client, type, name,
locale)` reads the published items and the draft headers of a type
across every package (`client.list(type)`, `client.listDrafts({ type
})`, the unscoped pair the Interfaces and Access creates read) and names
the item holding the identifier, or `null`. `CreateTakenNotice` renders
the refusal in the create dialog: a sentence naming the holder and, when
the holder is on the pillar's own rail, an "Open “LABEL”" button. The
Access create (#26916) now uses both instead of its inline copy; its
behaviour, sentences and test ids are unchanged.
- **New automation** (`doCreateFlow`): an identifier a flow holds is
refused before anything is written. A flow on this package's rail gets
"Open", which closes the dialog and opens it (the New button already
asked about a held step, #26927, so Open asks nothing more); a flow
another package holds gets no open path.
- **New object** (`doCreateObject`): the rail check it had (same package
only) becomes the "here" arm of the same refusal, with "Open"; an object
another package holds (the namespace-prefixed identifier) is now refused
too.
- **Create app** (`doCreateApp`): an identifier an app holds is refused.
Create app is offered only to a package with no app, so the holder is
always another package's and there is no "Open".
- A fresh identifier is created exactly as before: the draft save with
`mode: 'draft'`, `packageId`, `ifNoneMatch: '*'`, `surface: 'studio'`.
The pin still refuses a draft that lands between the check and the save.
- **Strings** (`@object-ui/i18n`, `studio` namespace, `en` and `zh`):
five refusal rows (`engine.studio.auto.createTaken`,
`…auto.createTakenElsewhere`, `…app.createTaken`, `…data.createTaken`,
`…data.createTakenElsewhere`); `engine.studio.access.openExisting`
becomes the shared `engine.studio.create.openExisting`;
`engine.studio.data.idExists`, which the new refusal replaces, is
removed.
- One root changeset: `@object-ui/app-shell` patch, `@object-ui/i18n`
patch. No `packages/spec/**`, no server change, no metadata key.
## Why
The card's order was to measure each create first. Measured at the
metadata door (`ObjectStackProtocolImplementation.saveMetaItem`, the
in-memory engine double of `protocol.served-content-hash.test.ts`, both
packages installed in the registry): publish an item, then save a draft
of the same name with `parentVersion: null` (the REST door's
`If-None-Match: *`) and `mode: 'draft'`, as each create does.
| type | published in | draft saved from | result |
| --- | --- | --- | --- |
| flow | same package | `com.acme.app` | written: active row "Live" +
draft row "Blank" |
| flow | other package | `com.acme.app` | written: active row (other) +
draft row (acme) |
| app | same / other package | `com.acme.app` | written over it, both
cases |
| object | same / other package | `com.acme.app` | written over it, both
cases |
| permission (control, #26836) | same / other package | `com.acme.app` |
written over it, both cases |
So no create was already safe at the door. The Data create was safe only
for an object on its own rail (same package), which it already refused;
the other two checked nothing. The probe was a scratch test, not
committed.
## Tests
`StudioDesignSurface.createTaken-26917.test.tsx`, eight cases on the
real `AutomationsPillar`, `DataPillar` and `StudioDesignSurface` (for
Create app), over a client double that keeps published and draft rows
per package, writes a draft over a published row the way the door does,
and refuses the pin over a draft row:
- flow on this package's rail: refused, `save` never called, no draft
row, the published body unchanged, the sentence names it, and "Open"
opens it (its draft read follows);
- flow another package holds: refused, nothing written, body unchanged,
no "Open";
- flow, fresh identifier: saved with the four options above;
- object another package holds (`acme_ticket`): refused, nothing
written, body unchanged, no "Open";
- object on this package's rail: refused, nothing written, "Open" opens
it;
- object, fresh identifier: saved with the four options;
- app another package holds: refused, nothing written, body unchanged,
no "Open";
- app, fresh identifier: saved with the four options.
## Acceptance notes
- **Race window, as on Access:** an item published (or created in
another package) between the create's two reads and its save is not
refused by the pin; a draft that lands there still is. Closing it for
every client would need the door's draft-mode pin to judge every
lifecycle, a spec and server change the seat ruled out on #26836 (answer
A).
- **Create app's sentence names no package.** Create app is shown only
when the package has no app, so "this package already has one" cannot
occur; the one sentence says an app holds the identifier.
- The object create's rail arm keeps its no-read fast path: an
identifier on the rail is refused before the two reads.
## Verification
All at head `efc675b9` (this branch merged `origin/main` `b1b153a3`,
which brought #26927's held-edit question on New), in a worktree, every
build and test under `scripts/pm/os-verify-lock.sh`.
<details><summary>New tests, ablation, type-check, lint and nearby
suites</summary>
- `pnpm --dir ui exec vitest run` on the new file, the Access file
(#26836) and #26927's three `held*Create-26845` files: `Test Files 5
passed (5)`, `Tests 25 passed (25)`.
- Ablation (`node scripts/ablation-replace.mjs`, anchor ` if
(!publishedRow && !draftRow) return null;` replaced by ` if (true ||
(!publishedRow && !draftRow)) return null;`, anchor x1 to x0) over the
new file and the Access file: `Tests 7 failed | 10 passed (17)`, exactly
the seven refusals that go through `findTakenIdentifier` (three Access,
two flow, object-elsewhere, app); the object-on-rail case stays green
because it is refused by the rail arm before the read. Restored: blob
equal to HEAD, `git diff HEAD` empty.
- `pnpm --filter @object-ui/app-shell run type-check && pnpm --filter
@object-ui/i18n run type-check`: `VERDICT command-exit 0` (app-shell's
covers the test file).
- eslint on the two app-shell files and the two catalogues: 0 errors;
`StudioDesignSurface.tsx` carries the same 14 warnings as on
`origin/main`.
- Narrowed suites: every app-shell test file naming `AutomationsPillar`,
`DataPillar`, `AccessPillar`, `StudioDesignSurface` or an
`engine.studio.(auto|data|app|access|create).` key (178 files), plus all
of `packages/i18n/`: `Test Files 267 passed (267)`, `Tests 2780 passed |
13 skipped (2793)`. CI's Test UI runs the rest of app-shell.
- `pnpm run check:i18n-designer-parity` (from `ui/`): "Every en row has
a zh row, and every shared row carries the same placeholders." `pnpm
check:new-line-citations`: `0 new citation(s)`.
</details>
<details><summary>Derived dispatch gates (34/34)</summary>
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 34 commands (the same list before
and after the merge of main); every one exited 0 at `efc675b9`, the
PR-body gates (`check-closing-keyword-parity`,
`check-adr-0087-registration --base origin/main`,
`check-changeset-no-major --base origin/main`) against this body through
`GITHUB_EVENT_PATH`, and `check-partof-closing-keyword` with `PR_BODY`:
exit 0.
- `pnpm --dir ui exec vitest run scripts/`: `Test Files 178 passed | 2
skipped (180)`, `Tests 5065 passed | 2 skipped (5067)`.
- `pnpm check:type-check-debt`: `0 raw tsc error(s) total, none above
its recorded number`.
- `dispatch-gates --ran`: `34 derived famil(ies) accounted for — 34 run,
0 NOT-MEASURED`.
</details>
Dogfood battery: not run, no `!` in the title and no `(narrowing)` arm.
## 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. (eslint on the touched
files: 0 errors; the full lint runs in CI)
- [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.
- [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 维护者速读 section is
the maintainer's Chinese summary)
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01QsR2jUEVZh2LeEfPDe25SW
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent e79be7d commit d7df639
5 files changed
Lines changed: 719 additions & 165 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 | + | |
| 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 | + | |
| 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 | + | |
| 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 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
0 commit comments