Skip to content

feat(studio): New similar on a read-only base's permission set, opening the copy where the clone door landed it - #28669

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-q3n56w
Nov 1, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-q3n56w

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #28573

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:Studio「访问」支柱里,只读基线包(如 HotCRM)的权限集多了一个「新建类似」按钮。它打开 Setup 已有的复制对话框,只提交机器名和显示名给权限集复制接口;服务端答复副本落在哪个包(通常是 hotcrm.custom),Studio 就打开那个包的访问支柱并选中副本。Setup 目录页的按钮文字也从「克隆后自定义」改为「新建类似」。
  • 为什么改:ADR-0146 D6 规定客户自己的权限集是基线旁的新集合,放在 .custom 包里。服务端已由 runtime: the permission-set clone door lands "new similar" in a writable target package, by default the source package's <base>.custom, never package-less #28563(PR feat(runtime)!: the permission-set clone door lands "new similar" in a writable target package, by default the source package's .custom (#28563) #28636)把复制目标改到 .custom,这张卡(N-20b)补上 Studio 侧的入口。以前 Studio 从不提供复制,只有 Setup 目录页有。
  • 风险与回滚:没有新的写入路径,只调用已有的复制接口。答复没写包名或副本名时什么都不打开,只提示。墙隔离姿态或缺 manage_metadata 时按钮置灰并给出原因。访问支柱现在按包重新挂载(与其他支柱一致)。回滚即 revert 本 PR。
  • 先看:studio-design/StudioDesignSurface.tsx 的 AccessPillar(onNewSimilarSaved 和按钮的禁用判断)、metadata-admin/permission-set-clone.ts(读答复里的 package)、i18n/src/namespaces/studio/{en,zh}.ts(文案)。
  • 两分钟验证:cd ui && pnpm exec vitest run packages/app-shell/src/views/studio-design/StudioDesignSurface.accessNewSimilar-28573.test.tsx,预期 9 个用例全绿。
  • 需要你做的:无,席位落地。

What changes

  • New similar in Studio's Access pillar. On a read-only package, the open permission set offers "New similar" in its header. It opens the existing PermissionSetCloneDialog, which posts exactly { name, label } to POST /api/v1/security/permission-sets/:name/clone. The client sends no package and no facet.
  • The copy opens where the answer says it landed. clonePermissionSet now reads the answer's package (the shape feat(runtime)!: the permission-set clone door lands "new similar" in a writable target package, by default the source package's .custom (#28563) #28636 added: { permissionSet, package, notice }), as sent: a string, null, or absent. With a package, the pillar navigates to /studio/<package>/access?surface=permission:<copy>, and the server's notice rides the navigation state and shows on the copy (access-new-similar-strip). With no package (absent or null) or no copy name, the strip says so and nothing opens. The landing is never derived from the base.
  • Refusals and gates. Refusals are the dialog's own (shown verbatim, unchanged). Under a walled posture (useWalledCatalogReadOnly) the button is disabled with engine.catalog.readOnly.walled; without manage_metadata (useCanAuthorMetadata) with engine.edit.capabilityReadOnly. The sentence is the button's accessible description and its title.
  • Access pillar keyed by package. <AccessPillar key={packageId}>, as the Data, Automations and Interfaces pillars already are. Without it, the hop to the copy's package kept the base's open set, rail and ?surface= capture, because the route keeps the surface mounted.
  • One label. perm.clone.action now reads "New similar" (zh 新建类似), on Setup's catalog too. The guidance strings that named the old label (perm.readOnly.artifact.hint, perm.clone.hint, perm.clone.guidance, perm.save.locked, perm.clone.dialogTitle) now name the new one. They also stop calling the copy "an ordinary permission set authored in this system": since feat(runtime)!: the permission-set clone door lands "new similar" in a writable target package, by default the source package's .custom (#28563) #28636 the server saves a base's copy in the package that holds your changes. Setup's ../COPY hop is unchanged.
  • Strings. Three new keys, engine.studio.access.newSimilar.{noPackage,done,dismiss}, all in the studio namespace (ui/packages/i18n/src/namespaces/studio/{en,zh}.ts, typed Record<string, string>). No locales/* pack carries this family, so the change is Clause-②: no (the seat amended the claim, 6557419813).

Tests

  • New: studio-design/StudioDesignSurface.accessNewSimilar-28573.test.tsx, 9 cases. It covers the en label with no override gesture on the base, the exact { name, label } body, and the copy opening in hotcrm.custom's Access pillar, selected, with the done line and the notice. It also covers a package that is absent or null (opens nothing, says so), no copy name, a 409 verbatim, walled disabled, capability disabled, and a writable package's set offering no New similar. The real AccessPillar, PermissionMatrixEditPage and PermissionSetCloneDialog are routed under /studio/:packageId/access and keyed by package, as StudioDesignSurface mounts them.
  • Updated: PermissionMatrixEditor.cloneToCustomize.test.tsx and packagedItemWriteGate-12169.test.tsx read "New similar" / 新建类似 for Setup's button and guidance.
  • Ablation, run once: if (typeof copy.package !== 'string') { was replaced by if (false as boolean) { through scripts/ablation-replace.mjs. The anchor went 1 → 0, and the absent and null package cases turned red (2 failed | 7 passed). Restore showed the blob equal to HEAD and git diff HEAD empty.

Acceptance notes

  • The keyed remount is pinned through the test's own route wrapper, which mirrors StudioDesignSurface's mount. The surface itself is not rendered in that suite.
  • After a copy lands, the server's notice is shown on the copy's Access pillar until dismissed or until another set is selected. It rides location.state, read once at mount, and only when it names the pillar's own package.
  • Card item 3 says "all locale packs": the perm.clone.* family lives only in the studio namespace, which has en and zh. Other locales fall back to en, as for every studio string.

Verification

Builds and type-check (at d224186, after merging origin/main dd6349e)
  • turbo run build --concurrency=2 --filter='@object-ui/app-shell^...' --filter=@objectstack/formula --filter=@objectstack/sdui-parser --filter=@objectstack/lint --filter=@objectstack/client: 64/64 tasks, lock VERDICT command-exit 0.
  • pnpm --filter @object-ui/app-shell --filter @object-ui/i18n run type-check: both Done, VERDICT command-exit 0.
Tests
  • Touched suites at d224186: 4 files, 75 tests passed (the new suite, cloneToCustomize, packagedItemWriteGate-12169, walledCatalogReadOnly-12183).
  • Consumer sweep at the pre-merge head b5bb340: every app-shell test file naming AccessPillar, PermissionSetCloneDialog, permission-set-clone, perm.clone or StudioDesignSurface. That is 172 files in three locked batches (86 + 43 + 43): 451 + 227 + 447 = 1125 tests passed. The incoming main commits touched PackagedAutomationPage and packages/spec/src/api, neither of which these files read. CI's Test UI runs the rest of app-shell.
  • pnpm exec vitest run packages/i18n/: 94 files, 1438 passed, 13 skipped.
Derived gates (dispatch-gates --commands --repo objectstack-ai/objectstack, at d224186)

All 34 derived commands exit 0, reconciled with dispatch-gates --ran. Among them: node scripts/check-sdui-manifest.mjs (exit 0, no registry change), pnpm --dir ui exec vitest run scripts/ (169 files, 4900 tests), pnpm check:type-check-debt (0 errors above record), pnpm check:query-options-erasure, pnpm check:nul-bytes, and pnpm check:cross-package-test-inputs. The PR-body gates (check-adr-0087-registration --base origin/main, check-changeset-no-major --base origin/main, check-closing-keyword-parity) ran against this body through GITHUB_EVENT_PATH.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (No spec change.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (Left to CI's Lint UI.)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: n/a.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.

Generated by Claude Code

… Access pillar

The Access pillar of a read-only package offers "New similar" on the open
permission set. It calls the clone door with a name and a label only, and
opens the copy in the package the door's answer names, with the copy
selected. An answer naming no package or no copy opens nothing and says so.
Walled posture and a caller without manage_metadata draw it disabled.

The action reads "New similar" on Setup's catalog too, and AccessPillar is
keyed by package like its siblings so the hop to the copy's package remounts.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jUn8pWsnfJNsrCm68c
…-q3n56w

# Conflicts:
#	ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

席位核对 · ACCEPT · 2026-11-01T13:49Z

Head-sha: 863f6fad1eae6ab4debbc016dac383dea6363ac4

  • Shape: draft → main, first content line Fixes #28573, no other card number beside a closing keyword. 8 files, +552 / −33 (585 lines ≤ 3000), no governed path (check-governed-merges --pr 28669). Author objectstack-fleet[bot]. Clause-②: no / Metadata-protocol: none line-initial in body and changeset, matching the seat's amendment 6557419813. perm.clone.action is re-valued, not added. The three new keys engine.studio.access.newSimilar.{noPackage,done,dismiss} sit in @object-ui/i18n's studio namespace (Record<string, string>, en + zh), and no locales/* pack key moved. No contract review is owed.
  • Wanted, item by item, read in the diff:
    1. On a read-only base, the open permission set's header offers New similar (access-new-similar). It opens the existing PermissionSetCloneDialog, which posts exactly { name, label }: no package, no facet.
    2. clonePermissionSet reads the answer's package as sent (a string, null or absent). With a package, the pillar navigates to studioPillarItemPath(pkg, 'access', { type: 'permission', name }), which is /studio/<pkg>/access?surface=permission:<copy>, with the copy selected. The landing rides navigation state, read only for that package. No package or no copy name says so and opens nothing. Nothing is derived from the base.
    3. The label reads "New similar" / 新建类似 on both surfaces, through the one perm.clone.action key. Setup's ../COPY hop is unchanged. Other locales fall back to en, as the whole studio namespace does; the body's acceptance note says so.
    4. The notice and every refusal are shown verbatim, the dialog's own. Under a wall the button is disabled with engine.catalog.readOnly.walled; without manage_metadata, with engine.edit.capabilityReadOnly. That sentence is also the button's title and accessible description.
    5. No override gesture is drawn on a base set, and a writable package's set offers no New similar.
  • <AccessPillar key={packageId}>: keyed like its sibling pillars, so the hop to the copy's package remounts the rail and the ?surface= capture.
  • Head history: d224186d was green, and I read it in full. 863f6fad merges main at 49a6a5c3, which carries PR feat(studio): New similar on a flow in the Automations pillar, opening the copy where the clone door landed it #28668 (14049381). The only conflict was the StudioDesignSurface.tsx import block, where both PRs added useLocation and studioPillarItemPath. The dev kept one copy each and kept this PR's three imports; en/zh merged cleanly. At the new head the dev re-ran the app-shell type-check (exit 0), the accessNewSimilar-28573, AutomationsPillar.newSimilar-28567 and deleteFlow-27356 suites (3 files, 20 tests), and check-sdui-manifest (exit 0).
  • Gates (check-runs on this head): 44 success, 11 skipped, 0 failed or pending. All ten required contexts are included; Build Core is skipped by its path filter on a ui-only diff.
  • Tests: new StudioDesignSurface.accessNewSimilar-28573 (9 cases), plus cloneToCustomize and packagedItemWriteGate-12169 updated for the new label. One ablation (the package-type guard) turned 2 cases red, as reported.
  • Observations, not blocking:
    • perm.clone.noName and perm.clone.done still say "Cloned" / 克隆, while the action now reads New similar.
    • The landing strip rides history state, so it shows again if the admin goes back to that entry.
  • Out of scope / NOT MEASURED: no live probe against the moved door (runtime: the permission-set clone door lands "new similar" in a writable target package, by default the source package's <base>.custom, never package-less #28563 / PR feat(runtime)!: the permission-set clone door lands "new similar" in a writable target package, by default the source package's .custom (#28563) #28636); it is stubbed at fetch. Skills-docs: not owed.
  • Landing: ordinary queue landing, pr_ready then automerge_enable. Fixes #28573 closes the card on merge.

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tooling

Projects

None yet

1 participant