Repository navigation
feat(app-shell): Studio customizes an installed app's navigation into its customization package (#30059) - #30153
Merged
Conversation
…30059) On a read-only base with a customization pair, the Interfaces rail's navigation editor now edits the app: the first saved edit copies the whole app into the pair, declaring the override at the base's version, the same route a view's Customize takes. The target pickers list the pair's items, the rail marks the served override and counts it under "only customizations", and Restore package original deletes it after a confirmation. Claude-Session: https://claude.ai/code/session_01WGufzerTiGyoUPgrA6K1SY Co-authored-by: Claude <noreply@anthropic.com>
…touched nav pins (#30059) Measured on a dev boot of examples/app-crm: with an object made in com.example.crm.custom on the customized navigation, removing the object left the runtime menu linking to "object not found". An entry the inspector binds to an object only the pair holds now carries `requiresObject: <name>`, so the runtime hides it once the object is gone (measured: hidden after removal, shown while it exists). A gate the binding wrote is removed when the entry is rebound to an object the base ships or unbound; an author's own gate is kept. Then, from the full sweep of the app-shell test files that import the edited components: - sharedSelect-11865 pins the keys an object binding writes; the gate added `requiresObject: undefined` to every binding. The binding now writes the key only when it sets a gate or removes one it wrote. - navEditClosesOnReadOnly-11167 pins that a read-only answer closes nav editing. Its base declares no manifest version, so no override can be declared and every save of the copy would be refused: the nav editor is now offered on a read-only base only when the base has a version. - oneSurface-28572 pinned that the "+" on a read-only base offers no nav entry, which #30059 changes on purpose; the pin now says it does, and that creating an item still links none. Claude-Session: https://claude.ai/code/session_01WGufzerTiGyoUPgrA6K1SY Co-authored-by: Claude <noreply@anthropic.com>
Contributor
Author
|
domain:ui#1 · os-support-ai · session_01WGufzjDyeFwq6AuFu3vwTd 维护者速读(席位终稿)
席位核对 · ACCEPT · head
|
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 Yibo Wang · project thread
Fixes #30059
维护者速读
.custom配对包时,Studio「界面」导轨可以编辑应用导航;首次保存把整个应用复制进配对包并声明overrides: { package, basedOn },选择器列出配对包里的对象/页面/仪表板等,导轨显示「已定制」标记、计入「仅看定制」,并可「恢复原版」。@object-ui/app-shell前端;无 spec、服务端、元数据类型改动;可写包的导航编辑不变。回滚即 revert 本 PR。已定制的应用是整份副本,厂商后续版本新增的菜单项要恢复原版后重做才会出现。StudioDesignSurface.tsx的doNavSave(首存复制进配对包)和StudioNavItemInspector.tsx的gatePatch(配对包对象的菜单项自动带requiresObject)。pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/InterfacesPillar.customize-28562.test.tsx -t 30059(6 条全绿)。Clause-②: no
Metadata-protocol: none
What changes
On a read-only base package (installed or code-shipped) that has, or can get, its paired customization package
<base>.custom(ADR-0146 D3), the Interfaces rail now edits the app's navigation through the same route a view's Customize takes. Card Scope 1–5 all ship in this PR (diff ≈ 670 lines, under the 1,500 split line).addNavEntry,StudioNavItemInspector, reused). Opening Edit writes nothing. The first saved edit asks the ensure door for the pair, then writes the whole app there as a draft withoverrides: { package: <base>, basedOn: <base manifest.version> }(overrideDraftBody,_keys stripped), pinnedifNoneMatch: '*'. Later edits update that copy; the base package is never written.usePackageNavTargets) read the pair after the base, the base winning on a name clash.requiresObject: <object>, so the runtime hides it if that object is removed instead of linking to "object not found". A gate this binding wrote is removed when the entry is rebound to a base object or unbound; an author's own gate is kept. (requiresObjectis an existing spec key the console already evaluates; no spec change.)publish-drafts(measured below).CustomizationMark(no text label), "only customizations" counts it and keeps the navigation listed, and the mark's popover names the base and version and offers "Restore package original", which asks for confirmation and then deletes the pair's draft and copy of the app.A walled caller (no pair, no right to create one) is offered neither Edit nor the "+", and neither is a caller on a base whose manifest declares no version (no override can be declared then, so every save of the copy would be refused).
StudioCustomizeControls.tsxchanges only its types list (appadded) and docblock. No spec, server, metadata-type,app_extensionor upgrade-merge change.Tests
InterfacesPillar.customize-28562.test.tsx, new describe "Customize a read-only base’s navigation into its pair (objectstack#30059)", 6 tests:toStrictEqual),ifNoneMatch: '*'; the base row is untouched; the mark's facts read back.requiresObject; rebound to a base object it carries no gate.StudioCustomizeControls.customize-28562.test.tsx:appmoves to the customized types.StudioDesignSurface.oneSurface-28572.test.tsx: one existing pin changes on purpose. It said the "+" on a read-only base offers no navigation entry; it now says it does, and that creating an item still links none.navEditClosesOnReadOnly-11167stays unchanged and green: its base declares no version, so a read-only answer still closes nav editing there.Ablations (each through
scripts/ablation-replace.mjs, restored to the pre-mutation blob):'app'fromSTUDIO_CUSTOMIZE_TYPES→ 4 failed / 2 passed (the CONTROL stays green).saved = declared→saved = { ...saved }) → 2 failed / 4 passed.gateFor, since renamedgatePatch) → 1 failed / 5 passed.navEditClosesOnReadOnly-11167red on 2 tests; with the rule they pass.Acceptance notes
Dev boot of
examples/app-crm(objectstack dev --seed-admin --fresh) with the console on Vite;com.example.crm4.0.0 is read-only (writable: false). The run was on6f468077. The last commit changes nothing it exercised: the base has a version, and a pair-object binding writes the same gate. Screenshots are in the project'sui-orders/30059-shots/folder; file names below.crm_custom_contract) in the Data pillar: the ensure door answered 201 and createdcom.example.crm.custom(02-object-in-pair.png). In Interfaces, the "+" → Object added an entry; the picker listed合同 (crm_custom_contract)after the base's objects (03-picker-lists-pair-object.png); the rail showed the mark and "saved to CRM (minimal example)'s customization" (04-entry-added-marked.png). The first save's body was the whole app (both groups, all six shipped entries, the dashboard entry's CELvisible) withoverrides: {package: "com.example.crm", basedOn: "4.0.0"}. Pending changes listed object (new) + app (update) in the pair, "publishes all 2 drafts at once" (05-pending-changes-one-batch.png);POST /packages/com.example.crm.custom/publish-draftsansweredpublishedCount: 2,published: [object crm_custom_contract, app crm_app], onecommitId. Repeated with a second object (发票) for the screenshot: same shape,publishedCount: 2, one commit. The runtime sidebar then listed 合同 →/apps/com.example.crm/crm_custom_contract(06-runtime-admin-sees-contract.png).13-mark-popover-restore.png); Restore asks first (14-restore-confirm.png), then sentDELETE …?state=draft&package=com.example.crm.customandDELETE …?package=com.example.crm.custom(both 200). The rail lost the mark and showed the shipped navigation (15-restored-shipped-nav.png), the runtime sidebar the shipped six entries (16-restored-runtime-nav.png), andGET /meta/app/crm_app?layers=trueansweredoverlay: null,effective == code, with the basecodehashing the same before and after.member(no CRM object permissions) gets 403 on/data/crm_custom_contractand/data/crm_lead. Their sidebar shows none of those entries, including 合同 (07-runtime-member-no-read.png), and the typed URL shows "no access" (08-member-opens-contract.png). Limit: this member reads none of the CRM objects, so it shows the pair's entry treated like the shipped ones, not a member who reads the base's objects but not the pair's.crm_custom_contractleft the runtime entry linking to "object 'crm_custom_contract' does not exist" (09-before-gate-object-removed-dead-link.png,10-before-gate-studio-rail.png). That is why Scope item 3 above was added. With the gate written by the picker, the entry shows while the object exists (17-gated-entry-object-present.png) and is hidden after it is deleted (18-object-removed-entry-hidden.png). The Studio rail still lists it, so the author can remove it.Observed, not changed: the pending-changes row and the Restore dialog name the app by its raw label "CRM" while the rail shows the translated "客户管理".
Verification
All under
scripts/pm/os-verify-lock.shunless noted. Head6e106af3(main merged after #30113 landed).turbo run buildfor@object-ui/app-shell..., 119/119 tasks.@object-ui/app-shelltype-check(both tsconfigs) → exit 0, before and after merging main.StudioDesignSurface,StudioNavItemInspector,InterfacesNavRailorStudioCustomizeControls: 197 files, 1359 tests, all passed. The first sweep found 3 red files, fixed inbee40cbe(see Tests). After the merge, the 8 files the merge and this PR share: 180 passed.npx eslinton the 6 touched source/test files (unlocked): 0 errors, 58 warnings.node scripts/check-sdui-manifest.mjs→ exit 0.node scripts/pm/dispatch-gates.mjs --commands: 35 commands, all recorded:: exit 0;--ran→ "35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN". One run was red at first:pnpm --dir ui exec vitest run scripts/had 2 tests time out at 15 s while the 197-file sweep shared the box. Those 2 files then passed alone, and the whole command passed on rerun (170 files, exit 0). That rerun's exit is the recorded one.🤖 Generated with Claude Code
https://claude.ai/code/session_01WGufzerTiGyoUPgrA6K1SY