You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 584f7b8
Browse filesBrowse the repository at this point in the historyBrowse files
feat(app-shell): show a disable refused for a dependent validation rule whole on the Setup packages page (#31016)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6Jao8FStJxkcthsASPRhE7tkGn)_Fixes#31003
Clause-②: yes (widening)
Metadata-protocol: none
## 维护者速读
- 改了什么:Setup 软件包详情里,停用被服务端以 409
`PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE`
拒绝时,页面先用管理员的语言显示一句标题(为什么不能停用、先停用或修改规则),再完整显示服务端原话(包 · 对象 ·
规则,以及补救办法),不再只露第一句、把补救藏在「更多」后面。停用确认框里多一行,说明有规则依赖这个包时停用可能会被拒绝。
- 为什么改:#30825 之后停用可能被拒,但页面把「停用」画成随时可用,被拒时补救办法又被折叠,管理员看不出该先去关哪条规则。
- 风险与回滚:只改前端显示和两条新文案(en/zh),其它拒绝码的显示方式不变,服务端零改动。回滚即 revert 本 PR。
- 先看:`PackagesPage.tsx` 里 `run()` 的 catch(按错误码加
heading)和消息框的渲染分支;确认框里新增的 `pkg-detail-disable-may-refuse` 一行;测试文件末尾新的
describe。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/metadata-admin/PackagesPage.moduleDisable-28118.test.tsx`,预期
15 passed,其中 en、zh 两条 409 用例都看到标题和完整原话。
- 需要你做的:无,席位落地。
## What changes
`ui/packages/app-shell/src/views/metadata-admin/PackagesPage.tsx`
(package detail sheet):
- **The refusal, drawn whole.** When an action on the sheet fails with
error code `PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE` (read off the
thrown error with `errorCodeIs`, which `apiJson` already fills from the
ADR-0112 envelope), the message box draws a heading in the reader's
language (`engine.packages.detail.disableRestricted`), and under it the
server's whole message as sent. The server's message names every
blocking rule as package · object · rule and then the remedy. Before,
`RefusalMessage` cut it to its first sentence, so the remedy was behind
"More". Every other refusal is drawn as before (`RefusalMessage`).
- **Disable no longer reads as unconditional.** The confirm dialog gains
one line (`engine.packages.detail.disableMayBeRefused`) saying the
disable is refused while an active validation rule reads a field this
package adds, and the refusal names each rule. Offering Disable still
follows the served `disableable` verdict alone. Nothing is computed in
the client.
`ui/packages/i18n/src/namespaces/studio/{en,zh}.ts`: the two new keys.
They are in the lazy `studio` namespace, not the eager `i18n-locale-en`
pack, so no ceiling moves.
### What the server serves (measured on `origin/main` `8ae0ab9b`,
`packages/runtime/src/domains/packages.ts`)
- `PATCH /packages/:id/disable` answers
`deps.error(disableDependentRulesMessage(...), 409, { code })`. On the
wire that is `{ success: false, error: { code, message } }`, with no
`error.details`. The JSDoc on the constant
`PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE` says a machine-readable
shape for the triples would be a spec contract first. The page therefore
shows the message and does not parse it.
- The served `disableable` is `isDisableablePackage` (a writable base,
or a module of an installed app). It does not read
`readDisableDependentRules`. Pre-computing it per row would need that
server read on every `GET /packages` row (a fold of every object per
package) and a change in what the field means. That is outside this
card's fence (`packages/**`), so the page shows the refusal when the
disable is attempted.
## Tests
`PackagesPage.moduleDisable-28118.test.tsx` gets a new top-level
describe, `a disable the server refuses for a dependent validation rule
(objectstack#31003)`, in the existing module-disable harness. Its wire
stub gains an optional per-door answer. The describe has 5 cases:
- the confirm dialog carries the may-be-refused line beside the existing
consequence copy;
- **en** and **zh**: a 409 with the code draws the heading in that
language and the server's whole sentence (rule listing and remedy both
on screen, no "More"), and Disable stays on offer;
- CONTROL: another refusal code (403 `WRITABLE_PACKAGE_REQUIRED`, two
sentences) keeps the first-sentence drawing with "More" and no heading;
- CONTROL: a disable with no dependent rule lands in one confirm (one
`PATCH …/disable`, success line, no refusal drawn).
The suite also gains module-top warm imports of `@objectstack/formula`
and `@objectstack/lint`, which `check:test-source-alias` asks of an
edited test whose reach loads them lazily.
## Acceptance notes
- The heading is console copy and the listing stays the server's English
sentence. Translating the listing would need the package · object · rule
triples as data (`error.details`), which is a `packages/spec` contract
and not opened here.
- The heading is keyed on the error code, not on the door, so the same
drawing would apply to that code wherever the sheet's `run()` meets it.
Today only the disable door answers it on this sheet. The install door
also answers it for `enableOnInstall: false`, but that door belongs to
the import flow and is not touched here.
## Verification
Head `0b6d41bd`, tree `objectstack-ai/objectstack`.
<details><summary>Tests</summary>
- `pnpm --dir ui exec vitest run
packages/app-shell/src/views/metadata-admin/PackagesPage.moduleDisable-28118.test.tsx`:
Tests 15 passed (15), 10 existing and 5 new.
- Every `PackagesPage.*.test.tsx` (18 files) on the first head: 17
passed, plus 1 failure in the new zh case (the test clicked the English
label), fixed in the next commit. The file is green since then.
- `pnpm --dir ui exec vitest run
packages/i18n/src/__tests__/studio-namespace.test.ts`: 18 passed (en ⊆
zh, placeholder parity).
- Ablation (`node scripts/ablation-replace.mjs`, anchor `{msg.kind ===
'err' && msg.heading ? (` → `… && false ? (`): the en and zh 409 cases
fail (2 failed, 13 passed). Restored to a blob equal to HEAD, and `git
diff HEAD` is empty.
</details>
<details><summary>Derived gates: 36 of 36 run</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 36 lines. All 36 were run with stdin
redirected from /dev/null, and `--ran` reports `36 derived famil(ies)
accounted for — 36 run, 0 NOT-MEASURED`. On the first pass, 34 exited 0
and 2 exited 1:
- `pnpm check:test-source-alias`: the edited suite reaches lazy
`@objectstack/formula` and `@objectstack/lint` imports. Fixed with the
warm imports. Exit 0 on `0b6d41bd`.
- `pnpm --dir ui exec vitest run scripts/`: 7 failures in
`check-doc-snippet-types.test.ts` and
`spec-symbol-ledger-plugin-detail-7265.test.ts`. Both read
`@objectstack/spec` declarations, and this worktree's spec was built
with `OS_SKIP_DTS=1`. After `env -u OS_SKIP_DTS pnpm --filter
@objectstack/spec build`, the full run on `0b6d41bd` gives Test Files
170 passed and 2 skipped; Tests 4991 passed and 2 skipped; `VERDICT
command-exit 0`.
Also run beyond the derivation, all exit 0: `pnpm --dir ui
check:i18n-keys`, `check:i18n-drift`, `check:i18n-dead-keys` (neither
new key is listed), `check:new-line-citations` (0 new), `pnpm
check:undeclared-dep-imports`. `node scripts/check-sdui-manifest.mjs` is
among the derived 36.
</details>
<details><summary>Not run locally</summary>
`pnpm --filter @object-ui/app-shell run type-check` is NOT MEASURED.
This worktree's `@object-ui/*` packages have no `.d.ts`, so the run
reports about 1,700 missing-module errors (TS2307 and TS7006), and none
of them is on a line this diff adds. Type Check UI runs it.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L9qibwxPsNfCDtuhas4VDr)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
feat(app-shell): the Setup packages page shows a disable refused for a dependent validation rule whole, and its confirm dialog says such a refusal can happen
7
+
8
+
Clause-②: yes (widening)
9
+
Metadata-protocol: none
10
+
11
+
When `PATCH /api/v1/packages/:id/disable` answers `409 PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE` (an active validation rule of an enabled package reads a field the package adds through `objectExtensions`), the package sheet now draws a heading in the reader's language over the server's whole sentence: every blocking rule as package · object · rule, then the remedy. Before, the message was cut to its first sentence and the remedy sat behind "More". Every other refusal is drawn as before. The Disable confirm dialog gains one line saying that such a rule can still refuse the disable, because the served `disableable` verdict says what the package allows and does not read rules elsewhere. Two new keys in the `@object-ui/i18n``studio` namespace (`en` and `zh`): `engine.packages.detail.disableRestricted` and `engine.packages.detail.disableMayBeRefused`.
'engine.packages.detail.disableDescription': 'The package is taken out of service for every user until it is enabled again. No records are deleted, and you can enable it again at any time.',
1936
+
'engine.packages.detail.disableMayBeRefused': 'The disable is refused while an active validation rule reads a field this package adds; the refusal names each such rule.',
1937
+
'engine.packages.detail.disableRestricted': 'This package cannot be disabled while an active validation rule reads a field it adds. Switch off or change each rule named below first, or keep the package enabled.',
0 commit comments