Repository navigation
feat(studio): customize a read-only package's view, dashboard or report into its <base>.custom pair, with an override badge and "Restore package original" - #28711
Merged
Conversation
…nto its pair Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jQpUMdR7QmHKTFR8iB
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jQpUMdR7QmHKTFR8iB
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jQpUMdR7QmHKTFR8iB
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jQpUMdR7QmHKTFR8iB
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jQpUMdR7QmHKTFR8iB
The zh wording census reads a bare Latin "base" in a value as an untranslated
word, and {base} is one.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jQpUMdR7QmHKTFR8iB
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jQpUMdR7QmHKTFR8iB
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jQpUMdR7QmHKTFR8iB
Contributor
Author
|
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y 席位核对 · ACCEPT · 2026-11-01T15:00ZHead-sha:
Generated by Claude Code |
This was referenced Nov 1, 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 #28562
Clause-②: no
Metadata-protocol: none
维护者速读
POST /packages/:id/ensure-customization)。之后把草稿写进门回答的配对包<base>.custom,草稿声明overrides: { package, basedOn },basedOn取包列表里基础包的manifest.version。待发布数、变更面板和发布都改为作用于配对包。标题栏显示「覆盖 HotCRM」徽标,读自服务器返回的声明。⋯ 菜单新增「恢复软件包原版」:确认后先删草稿,再删配对包里的这一行,基础包的版本随即读回。只读标记不再把这三种类型链接到 Setup。StudioDesignSurface.tsx里InterfacesPillar的doSave(配对包 id 取自门的回答)、restoreOriginal,以及 Studio 外壳里的draftScope/publishTarget;新文件StudioCustomizeControls.tsx。cd ui && pnpm exec vitest run packages/app-shell/src/views/studio-design/InterfacesPillar.customize-28562.test.tsx packages/app-shell/src/views/studio-design/StudioDesignSurface.customizePublish-28562.test.tsx,预期 19 例全绿。What changes
StudioCustomizeControls.tsx, new) appears in the item header of a view, dashboard or report, and of anobjectleaf's list view, when the package is read-only. Pressing it only unlocks that item's editors (canvas, inspectors, list-view panel). Nothing is written until an edit is saved, so an unedited Customize leaves nothing behind.InterfacesPillardoSave, and the list-view hook's save via its newcustomizationoption). It callsensureCustomizationPackage(packages-io.ts, new) once per item, with no prompt and no body. It then saves the draft into the package the door's answer names. The body is the item as held, without its top-level_keys, withoverrides: { package: <base>, basedOn: <listed manifest.version> }. The answer is judged by the spec'sEnsureCustomizationPackageResponseSchema. A declaration the item already carries is kept as it is. The first save into the pair is pinnedIf-None-Match: *when the buffer was read at the base, so a draft someone else left there is asked about, never overwritten.grouporisolatedposture, an admin who is not the platform operator sees Customize disabled, with the reason. This is read fromfeatures.tenancyPostureanduser.isPlatformAdmin, the factsuseFlowLedgerWalledReadOnlyreads. Any refusal from the door is shown as the envelope reader writes it (<sentence> (<CODE>)), and nothing is saved.versiongets Customize disabled, with a sentence that says so.DraftChangesPanelandpublishPackageDraftsname the pair. A refusal at the publish door goes through the existing refusal path, verbatim. A read-only base with no pair keeps the read-only Publish and its reason.StudioOverrideChip). It reads the declaration the server serves: the pair's draft through the draft door, else the layered read'soverlay(N-10). Its title names the version. It is never computed.StudioRestoreOriginalItem+StudioRestoreOriginalDialog) is on the ⋯ menu of an item served as an override. On confirm it sendsDELETE /meta/:type/:name?state=draft&package=<pair>, thenDELETE /meta/:type/:name?package=<pair>, and reloads the item. The base reads back. The pair is the door's answer this item's saves named, else the surface's. The door is not asked again.StudioReadOnlyLinkno longer links a view, dashboard or report to Setup's overlay page. Other overlay-able types keep the link.engine.studio.customize.*keys in the studio namespace (en + zh). The placeholder is{package}, because the zh census refuses a bare Latinbase.Surface additions (approved by the seat)
ui/packages/app-shell/src/views/studio-design/ObjectListViewInspector.tsx.useObjectListViewDraftgains an optionalcustomization(readAt/writeTarget), andObjectListViewInspectorgainscanCreate(default!readOnly). Without them nothing changes.ui/packages/app-shell/src/views/studio-design/StudioCustomizeControls.tsx(new).Tests
StudioCustomizeControls.customize-28562.test.tsx(36): type gate, declaration parse, override body (_keys dropped, an existing declaration kept,nullwith no version), the wall matrix, the ensure client (201 and 200, no body, refusal text, a malformed answer refused,packages-changedonly on create), listed version and pair, and the button and badge.InterfacesPillar.customize-28562.test.tsx(13). The mounted pillar, real autosaves, and the real ensure client over a fetch stub that answers 201, then 200, are driven against a server double. Covered:If-None-Match: *, thenIf-Match.StudioDesignSurface.customizePublish-28562.test.tsx(6). The real surface and the real client over fetch. Covered:_drafts?packageId=<pair>, the panel reviews the pair, and Publish posts/packages/<pair>/publish-drafts.4.0.0, then Pending and Publish follow the pair.StudioReadOnlyLink.overlay-27356.test.tsxwas updated. View, dashboard and report are plain. An overlay-ableemail_templatestill links (control).Ablations
Nineteen, each through
scripts/ablation-replace.mjs. Each one landed, its filtered test went red, and the file was restored to HEAD:pairFornever reuses an answercustomizeDoorWalledalways falsewalledpublishTarget = packageIddraftScope = packageIdpublishReadOnly = readOnlynoVersionblockStudioReadOnlyLinkignores the three typescanCreatenot passedIf-None-Matchpin (leaf)If-None-Matchpin (list view)leafReadOnlyalways falsebasedOnnot read from the listed manifest4.0.0Live measure
This was measured on
pnpm dev:crm(basecom.example.crm, read-only, viewcrm_lead.all) through the server's doors with curl. The browser UI was not driven; the UI is covered by the tests above.POST /api/v1/packages/com.example.crm/ensure-customizationanswered 201 withcreated: trueon the first call and 200 withcreated: falseafter.data.packageIdwascom.example.crm.custom, and the UI takes the pair from that answer.GET /api/v1/packagesservedcom.example.crmwithmanifest.version4.0.0andwritable: false.PUT /meta/view/crm_lead.all?mode=draft&package=com.example.crm.custom(If-None-Match: *) answered 200. The draft read at the pair carriedoverrides: { package: "com.example.crm", basedOn: "4.0.0" }, and_drafts?packageId=com.example.crm.customlisted it.POST /packages/com.example.crm.custom/publish-draftsansweredpublished, 1. ThenGET …/layers?package=com.example.crmreturnedoverlay.overrides= the declaration andeffective.label"All leads (customized)".DELETE …?state=draft&package=com.example.crm.customanswered 200, thenDELETE …?package=com.example.crm.customanswered 200. The layered read then returnedoverlaynull andeffective.label"All Leads", the base read back.Acceptance notes
If-None-Match: *pin on the first pair save is an addition.Verification
pnpm --filter @object-ui/app-shell run type-check(src + tests): exit 0 at6674886524.views/studio-designtest files: green at6674886524(2108 tests). The 85 tests outside it that read the studio namespace: green at4bff2978d7.packages/i18n(94 files): green.dispatch-gates --commands) ran at0513534c9f: 34 of 34 exit 0, reconciled with--ran(0 NOT-MEASURED, 0 UNRUN).check:sdui-manifest,check:nul-bytes,check:i18n-keys,check:i18n-driftandcheck:i18n-dead-keyswere re-run at4bff2978d7: exit 0. ESLint on the touched files reported 0 errors.origin/mainthree times. The second merge brought feat(studio): New similar on a flow in the Automations pillar, opening the copy where the clone door landed it #28668 (the card Studio: "New similar" on a flow over the clone door — the copy lands inactive in <base>.custom or the open writable package; Setup's clone link follows it #28567) and the third brought feat(studio): New similar on a read-only base's permission set, opening the copy where the clone door landed it #28669 (the card Studio: "New similar" on a base permission set — the copy lands where the clone door puts it (<base>.custom) and opens in Studio; Setup's clone button reads the same #28573); both merged clean. At the head1ae8358eb7: app-shelltype-checkexit 0; this card's four test files, Studio: "New similar" on a base permission set — the copy lands where the clone door puts it (<base>.custom) and opens in Studio; Setup's clone button reads the same #28573's two Studio and catalog test files,packages/i18n(94 files) and the zh wording census green;check-sdui-manifest,check:nul-bytes,check:i18n-keys,check:i18n-driftandcheck:i18n-dead-keysexit 0. No parallel fix of this card is on main.🤖 Generated with Claude Code
https://claude.ai/code/session_01QsR2jQpUMdR7QmHKTFR8iB