Repository navigation
feat(app-shell): offer Disable / Enable on a suite's module rows from the served disableable verdict - #28903
Conversation
… the served disableable verdict The package detail sheet gates Disable on the row's served `disableable` (the disable door's own verdict) instead of `writable`, so a module of an installed app, read-only for authoring, can be switched off and on from the Packages page and the Installed Apps view. Every other write action stays gated on `writable`. The read-only note on such a row says it can be disabled (two studio keys, en and zh), and the marketplace handbook names the button and the key. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jSW86XpeUhXzbQFo4Z
…action Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jSW86XpeUhXzbQFo4Z
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Path: none Triage: stale PR ·
|
|
domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu 席位注记 · parked (landing-operations.md §C) · 2026-11-03T03:09ZAnswer to triage 6594440097: this PR is parked as a draft on purpose. It is not idle work. Blocked-by: objectstack-ai/hotcrm#2383
|
|
平台的开发先行,hotcrm随后 |
|
domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu 席位核对 · ACCEPT · 2026-11-03T06:12ZHead-sha:
|
Requested by Steedos · project thread
Fixes #28118
Clause-②: no
Metadata-protocol: none
维护者速读
disableable判定(不再看writable)。套件里type: 'module'的模块包(对创作只读、但服务端允许禁用)现在会出现「禁用」,走原有确认对话框;禁用后出现「启用」。编辑、发布、还原、复制、收编、删除仍然只看writable,在模块行上继续隐藏。模块行的只读提示改为说明「不能编辑或删除,但可以禁用并重新启用」(新增两个 studio 文案,en / zh)。市场手册补充了这个按钮和disableable键。GET /packages上下发了disableable。裁决「最小做法」:不加新页面,就在现有的软件包页给开关。disableable: true时出现,没有新的写入路径,也没有新权限(仍是manage_metadata)。可写包的服务端判定同样是disableable: true,所以它们的「禁用」不变。缺少该键的行不会出现「禁用」(失败即关闭)。落地顺序受 app: the HotCRM app's UI-side reads of a disabled module (lookups, add-to-campaign / quote actions, the revenue dashboard tile) meet 404 OBJECT_PACKAGE_DISABLED instead of hiding (objectstack#28117 ruling A) hotcrm#2383 约束:在它落地前本 PR 保持草稿,不对客户可见。回滚就是 revert 本 PR。ui/packages/app-shell/src/views/metadata-admin/PackagesPage.tsx(disableable读取、按钮门槛、只读提示的分支)、PackagesPage.moduleDisable-28118.test.tsx(新测试)、content/docs/deployment/marketplace-handbook.mdx(手册两处)。pnpm --dir ui exec vitest run packages/app-shell/src/views/metadata-admin/PackagesPage.moduleDisable-28118.test.tsx,预期 10 个用例全绿:模块行有「禁用」且需确认、其余写操作隐藏、无判定的只读行没有「禁用」、已禁用的模块行有「启用」、Installed Apps 视图里模块是单独一行。What changes
PackageDetailSheetgates its Disable / Enable button on the row'sdisableable(the disable door's own verdict,package-api-assembled.zod.tsSERVED_ROW_VERDICTS, served byGET /api/v1/packagesandGET /api/v1/packages/:id), not onwritable. It is read the way the sheet readswritable: anything buttrueoffers no Disable. ⛔ Nothing is re-derived frommanifest.type,scopeorwritable. Enable is still offered on any disabled package, as before (the enable door has no writability gate).writable. Edit, Publish, Revert, Duplicate, Adopt loose items and Delete are unchanged, so a module row (writable: false,disableable: true) offers only Disable / Enable beside the reads.engine.packages.detail.readOnlyDisableableandengine.packages.detail.codeReadOnlyDisableable(en, zh), say it cannot be edited or deleted but can be disabled and enabled again. The confirm dialog's consequence sentence (disableDescription) already reads right for a module and is unchanged.InstalledPackageRowgainsdisableable?: InstalledPackageAtEitherStage['disableable'], taken from the spec's served row. It is optional on the row type so that a row without the key types and renders as "not disableable".content/docs/deployment/marketplace-handbook.mdx: step 7 says each package of a suite is its own row in Installed Apps and the sheet offers Disable where the runtime allows it; "Disabling a module" names the Console path and thedisableablekey.Where module rows appear, measured before coding (reported on the card): install-local registers every package of the artifact with
source: 'marketplace', so a marketplace suite's modules are their own rows in Setup → Installed Apps (?source=marketplace). A suite booted from code hassource: 'code'; its modules are on the unfiltered Setup → Packages page, with the same button.Tests
PackagesPage.moduleDisable-28118.test.tsx(10 cases, realPackageDetailSheetand realPackagesPage):disableable: trueshows Disable; it opens the confirm with the consequence copy, sends nothing until confirmed, then sends the onePATCH /api/v1/packages/:id/disable;disableable: falseand a row with no key offer no Disable and keep the plain read-only note;disableable: trueoffers Disable: the verdict decides, notmanifest.type;disableable: false) offers none.readOnlyActions-13157,readOnlyReasonAndDisableConfirm-27521,adoptLooseItems-13339andconfirmFlows-11784now carrydisableableas the server serves it for a non-module package (equal towritable).2d45950f:node scripts/ablation-replace.mjsput the gate back towritable || !enabled(anchor 1 → 0, blob93d7556c→7ebf253c). The new file went red, 3 failed / 7 passed: (1), (5) and the Installed Apps case. Restored blob93d7556cequals HEAD andgit diff HEADis empty. Case (4) and the controls stay green both ways, as expected: Enable does not change.Acceptance notes
disableableis optional on the row type and the list does not refuse a row without it, unlikesourceandwritable. A missing key fails closed (no Disable, the door's own answer) instead of refusing the whole list. Making it a refused key would touch every page-level fixture; noted, not done.Blocked-by:), per the ordering addendum 6523692915. The PR stays draft until then.source: 'code'); that is the existing Source filter's meaning and is unchanged here.Verification
Local runs
pnpm --dir ui exec vitest runon the 5 touched test files: 5 files, 55 tests green.MarketplaceRowActions.installedAppsPins: 25 files, 292 tests green.pnpm --dir ui exec vitest run packages/i18n/: 94 files, 1438 passed, 13 skipped.check:i18n-keys,check:i18n-drift,check:i18n-dead-keys: exit 0.turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/i18n(each runstsc --noEmitandtsc -p tsconfig.test.json, so the tests are compiled): 66 tasks, exit 0.pnpm --dir ui exec vitest run scripts/: 169 files passed, 2 skipped; 4900 tests passed.check:new-line-citations: 0 new.node scripts/check-sdui-manifest.mjs: exit 0 (in the derived set).dispatch-gates --commandsat2d45950f: 66 derived, 66 run, all exit 0;--ranreconciles 66 of 66 with 0 NOT-MEASURED.check:skill-examplesfirst refused with exit 3 (@objectstack/client-reactdeclarations not built); afterpnpm --filter @objectstack/client-react buildit ran: 280 prose examples type-check, exit 0.4775dfce(4 commits; two touch app-shell and the studio en / zh catalogues, no conflict). Re-run on that head: the app-shell + i18n type-check (66 tasks, exit 0), the 25 Packages page files pluspackages/i18n/(119 files, 1730 passed, 13 skipped), the three i18n checks andcheck-sdui-manifest(exit 0). The derived-gate sweep above is from2d45950f, before the merge; the merge adds no file to this diff.!in the title and no(narrowing)arm.packages-disable-app-module.test.tspins the module row'ssourceper install path;packages-disableable-verdict.test.tspins the served verdict).Generated by Claude Code