Repository navigation
fix(app-shell): Studio's Automations, App and Data creates refuse an identifier a published or draft item already holds - #26987
Merged
Conversation
…identifier a published or draft item already holds The draft-mode first-write pin refuses only a DRAFT row, so "New automation", "Create app" and "New object" wrote their blank skeleton as the pending draft of a published flow, app or object of the same identifier, and a package publish would have replaced the live item. Each create now reads the published items and drafts of its type across every package first (one shared `findTakenIdentifier`, which the Access create now uses too) and refuses a taken identifier in the dialog, naming the holder and offering to open it when it is on the pillar's rail. A fresh identifier saves as before, under the pin. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jUEVZh2LeEfPDe25SW
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jUEVZh2LeEfPDe25SW
Since the New button asks before the dialog opens whether to drop a held edit, opening the flow or object that holds the identifier needs no second question. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jUEVZh2LeEfPDe25SW
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jUEVZh2LeEfPDe25SW
Contributor
Author
席位核对ACCEPT · seat
Landing: no contract review is owed. There is no published schema, no governed text and no exported type, and |
This was referenced Oct 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Steedos · project thread
Fixes #26917
Clause-②: no
Metadata-protocol: none
维护者速读
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
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, ornull.CreateTakenNoticerenders 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 (fix(app-shell): Studio Access refuses a new permission set over a taken identifier and can discard a set's draft #26916) now uses both instead of its inline copy; its behaviour, sentences and test ids are unchanged.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, fix(app-shell): creating an item or opening one from Pending changes asks before it drops a held Studio edit #26927, so Open asks nothing more); a flow another package holds gets no open path.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.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".mode: 'draft',packageId,ifNoneMatch: '*',surface: 'studio'. The pin still refuses a draft that lands between the check and the save.@object-ui/i18n,studionamespace,enandzh): five refusal rows (engine.studio.auto.createTaken,…auto.createTakenElsewhere,…app.createTaken,…data.createTaken,…data.createTakenElsewhere);engine.studio.access.openExistingbecomes the sharedengine.studio.create.openExisting;engine.studio.data.idExists, which the new refusal replaces, is removed.@object-ui/app-shellpatch,@object-ui/i18npatch. Nopackages/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 ofprotocol.served-content-hash.test.ts, both packages installed in the registry): publish an item, then save a draft of the same name withparentVersion: null(the REST door'sIf-None-Match: *) andmode: 'draft', as each create does.com.acme.appcom.acme.appcom.acme.appcom.acme.appcom.acme.appSo 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 realAutomationsPillar,DataPillarandStudioDesignSurface(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:savenever called, no draft row, the published body unchanged, the sentence names it, and "Open" opens it (its draft read follows);acme_ticket): refused, nothing written, body unchanged, no "Open";Acceptance notes
Verification
All at head
efc675b9(this branch mergedorigin/mainb1b153a3, which brought #26927's held-edit question on New), in a worktree, every build and test underscripts/pm/os-verify-lock.sh.New tests, ablation, type-check, lint and nearby suites
pnpm --dir ui exec vitest runon the new file, the Access file (Studio Access: creating a permission set whose derived id equals an already-published set puts a blank draft over the live set (publishing it would wipe its grants), and Studio offers no way to discard it #26836) and fix(app-shell): creating an item or opening one from Pending changes asks before it drops a held Studio edit #26927's threeheld*Create-26845files:Test Files 5 passed (5),Tests 25 passed (25).node scripts/ablation-replace.mjs, anchorif (!publishedRow && !draftRow) return null;replaced byif (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 throughfindTakenIdentifier(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 HEADempty.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).StudioDesignSurface.tsxcarries the same 14 warnings as onorigin/main.AutomationsPillar,DataPillar,AccessPillar,StudioDesignSurfaceor anengine.studio.(auto|data|app|access|create).key (178 files), plus all ofpackages/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(fromui/): "Every en row has a zh row, and every shared row carries the same placeholders."pnpm check:new-line-citations:0 new citation(s).Derived dispatch gates (34/34)
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 34 commands (the same list before and after the merge of main); every one exited 0 atefc675b9, 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 throughGITHUB_EVENT_PATH, andcheck-partof-closing-keywordwithPR_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.Dogfood battery: not run, no
!in the title and no(narrowing)arm.Human Minimum
packages/specholds no runtime logic. (not touched)pnpm lintpasses. (eslint on the touched files: 0 errors; the full lint runs in CI)main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed. (not touched)content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_01QsR2jUEVZh2LeEfPDe25SW