Repository navigation
Commit 9182ff6
feat(studio): the All metadata lists mark a base's customizations, with an only-customizations filter and tile counts (#28667)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbYJa9oMBxT4YrnmXWABr1eP)_
Fixes #28568
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:Studio「全部元数据」区里,打开一个已安装的基础包(如 HotCRM)时,每个类型列表同时列出基础包和它的定制包
`hotcrm.custom`
的项。被覆盖的项标「已定制(HotCRM)」,定制包新增的项标「新增」;列表多一个「仅看定制(n)」筛选;目录页每个类型卡片显示已定制数(对象卡片另计扩展字段等),页头显示基础包的定制总数。跨包视图(`~all`)里,定制包的项标「HotCRM
的定制」。
- **为什么改**:ADR-0146 D3「一个界面」:管理员要在同一处看出「我们在 HotCRM
上改了什么、加了什么」,包括四大支柱不覆盖的类型(翻译、邮件模板等)。
- **风险与回滚**:低。只读展示,数据全部来自服务器的 `GET
/api/v1/packages/:id/customizations`,不做任何比对或推断;读取中或失败时不画标记、不显示数字(失败时显示服务器原话),绝不显示
0;Setup 页面完全不变、也不发这个请求。回滚即还原本 PR。
-
**先看**:`ui/packages/app-shell/src/views/studio-design/customizationMarks.ts`(哪一行标什么的规则)、`ResourceListPage.tsx`
里列表范围加入定制包那一行、`CustomizationMark.tsx`(全控制台唯一的标记组件,N-19 复用)。
- **两分钟验证**:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/metadata-admin/metadataAreaHost.customizations-28568.test.tsx
packages/app-shell/src/views/studio-design/customizationMarks.test.ts`,预期
2 个文件、16 个测试全绿。
- **需要你做的**:无,席位落地。
## What changes
- **One reader, extended.** `studio-design/packageCustomizations-io.ts`
gains `fetchPackageCustomizations(baseId)`. It returns the whole answer
of `GET /api/v1/packages/:id/customizations`, judged by
`GetPackageCustomizationsResponseSchema`. The landing's
`fetchPackageCustomizationCounts` now calls it, so there is still one
request path, one schema check and one failure-text rule
(`readEnvelopeFailureText`). The pair-id refusal before any request is
kept.
- **The rule, pure.** `studio-design/customizationMarks.ts` decides
which mark a row carries, from two served facts:
- the read's `overrides` (type, name and, for a type identified by a
pair such as `email_template`'s locale, its `member`);
- the `_packageId` the list serves the row under. A served override is
served under the base's id (ADR-0146 D8 item 2). An item of the pair
that overrides nothing is served under the pair's id.
A row the read lists as an override is `customized`, whether it is
served under the base or the pair. Any other row of the pair is `added`.
The base's other rows and every other package carry nothing. There is no
diff and no `basedOn` verdict (D2, D6). The same module counts by type
for the directory: the overrides of the type, plus the extension members
on `object`. The header total is served overrides plus served
extensions, the landing's "customized" figure.
- **One mark component.** `studio-design/CustomizationMark.tsx` exports
`CustomizationMark` (kinds `customized`, `added`, `customizationOf`) and
`usePackageCustomizations(baseId)`. The hook has four states: off,
loading, failed with the server's sentence, and read. It names the base
from the package list the area already reads, shared through
`packages-io.ts`, and falls back to the id. N-19's pillar rails can
reuse both.
- **List (`ResourceListPage.tsx`).**
- Under the area with a base open, the scope admits the pair's rows
beside the base's (`pkg === activePackage || pkg === pairId`).
- Each row's first cell draws its mark.
- "Only customizations (n)" appears once the read has answered and the
base has a customization. It narrows the list to the marked rows.
- A failed read shows its sentence under the filter row.
- In the cross-package leg, a row served under a `BASE.custom` id is
labelled "Customization of BASE" (`customizationBasePackageId`).
- **Directory (`DirectoryPage.tsx`).** Each tile shows the type's served
customization count, and only when it is above zero. The header shows
the base's total. A failed read shows the sentence and no number.
- **Setup is unchanged.** With no area provider, the host is `null`, so
no read is made and no pair row is admitted.
- **Strings.** Thirteen keys go into `@object-ui/i18n`'s studio
namespace, in `en` and `zh`: `engine.customizationMark.*`,
`engine.directory.customizations.*` and
`engine.list.customizations.only`. The placeholders are `base`, `pair`,
`customized`, `marked` and `message`. There is no `count` token.
## Tests
`metadataAreaHost.customizations-28568.test.tsx` runs the real list, the
real directory and the real reader under an area provider. Only `fetch`
and the metadata client are stubbed. The fixture is base HotCRM with one
overridden translation, one added email template, one extension field,
and an override of a different email-template member. The cases are the
card's five plus three controls:
- The translation list marks the override "Customized (HotCRM)".
Toggling the filter shows that row alone, and toggling back restores the
rest. The base's unlisted row is unmarked.
- The email-template list holds the pair's `renewal_notice` marked
"Added". The base's `welcome` in `en` is unmarked, because the read
overrides only its `zh-CN` member.
- The directory's object tile reads "1 customized" from the extension.
The translation and email-template tiles read 1 each, and the header
reads "3 customized on HotCRM". A type with no customization draws no
number.
- A failed read (503 with an error envelope) draws no mark, no filter
and no number on the list or the directory. It shows the server's
sentence.
- A base without a pair (an empty read) is unchanged: no mark, no
filter, no failure line.
- Control: Setup (no area) makes no customization read and marks
nothing.
- The cross-package leg labels a pair's permission set with its base. It
makes no read, and the base's own item names no base.
`customizationMarks.test.ts` pins the pure rule (10 cases): override
under the base or the pair, member identity, type identity, other
packages, a pair id that has no pair, the per-type counts and the total.
## Acceptance notes
- **Tiles and the header count what the read lists.** That is overrides,
plus extensions on `object`. An "added" item of the pair is marked and
filterable in its list, but no tile or header counts it, because N-15's
read does not list items the pair adds. So a list's "Only customizations
(n)" can be larger than its tile's number. Counting added items needs
the read to serve them (a change to N-15's declaration), or a second
count, which the card rules out ("from N-15's served counts").
- **The directory's item counts and tile list are the base's alone.**
The diagnostics sweep behind them (`useGlobalDiagnostics` in
`useMetadata.ts`) is scoped to one package. A type that only the pair
holds therefore draws no tile on the base's directory, and a tile's item
count does not include the pair's rows. That type's list does show them.
Closing this needs a second, pair-scoped sweep in `useMetadata.ts`,
which is outside the claimed file surface. It is left for the seat to
card or carry.
- **A pair row opens in the pair's area.** Its link carries
`?package=PAIR` (the row's owning package, ADR-0048), and the area
re-roots a `?package=` naming another package onto that package
(`areaLocationRedirect`). An added item therefore opens writable, in
`/studio/PAIR/metadata/...`. This is the area's existing rule, unchanged
here.
- **Type identity.** The list route's `:type` is matched against the
read's canonical singular `type`. Both come from the same registry
vocabulary, so no alias is folded.
- **Clause-② measured.** Every new string is in the studio namespace (a
plain string table, outside the `locales/*` packs), so the claim's
`Clause-②: no` holds.
- **NOT MEASURED: a live Console.** No dev server was run. The route was
mounted by #28635 and is read live by the landing (#28634). This PR
exercises it only through the stubbed answers above.
## Verification
Readings are at head `879da8ff`, unless a step names another head.
`f42f397b`, the head after it, changes only the changeset text.
<details><summary>New and neighbouring suites · 39 files · 356
tests</summary>
The set is every ui test file that names `DirectoryPage`,
`ResourceListPage`, `packageCustomizations-io`, `CustomizationMark`,
`customizationMarks`, `metadataAreaHost` or `StudioMetadataArea` (`git
grep -l`), the two new files included. It ran under `os-verify-lock`
with `--maxWorkers=2`: Test Files 39 passed (39), Tests 356 passed
(356). This is a declared narrowing; CI's Test UI shards run the rest of
app-shell.
</details>
<details><summary>Ablations · both red</summary>
Both ran through `scripts/ablation-replace.mjs` against the committed
fix. Each restored the file with blob equal to HEAD and an empty `git
diff HEAD`.
1. The override key drops `member`: 2 failed (the member-identity unit
case and the email-template list case).
2. The list scope drops the pair's rows: 2 failed (the email-template
list case and the failed-read case, which still lists the pair's row).
</details>
<details><summary>Type-check and lint</summary>
- `pnpm --filter @object-ui/app-shell run type-check`: exit 0, at
`52eb6b11` and again at `879da8ff`.
- `pnpm --filter @object-ui/i18n run type-check`: exit 0.
- `eslint` on the nine touched files: 0 errors (warnings only).
</details>
<details><summary>Derived gates · `dispatch-gates --commands` · 34
commands</summary>
Every command exited 0.
- At `f42f397b`, 31 of them ran, the PR-body gates against this body
among them.
- Three ran at `52eb6b11`, and the later commits touch none of their
inputs beyond one five-line refactor in `ResourceListPage.tsx`:
- `pnpm --dir ui exec vitest run scripts/`: 169 files and 4900 tests
passed, 2 skipped;
- `check:query-options-erasure`;
- `check:type-check-debt`, under the lock.
`dispatch-gates --ran`: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN.
</details>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01QsR2jSKpVAotd33JtFuXkP
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jSKpVAotd33JtFuXkP)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 7dcbc03 commit 9182ff6
10 files changed
Lines changed: 783 additions & 7 deletions
File tree
- .changeset
- ui/packages
- app-shell/src/views
- metadata-admin
- studio-design
- i18n/src/namespaces/studio
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
| 44 | + | |
| 45 | + | |
44 | 46 | | |
45 | 47 | | |
46 | 48 | | |
| |||
169 | 171 | | |
170 | 172 | | |
171 | 173 | | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
172 | 183 | | |
173 | 184 | | |
174 | 185 | | |
| |||
248 | 259 | | |
249 | 260 | | |
250 | 261 | | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
251 | 275 | | |
252 | 276 | | |
253 | 277 | | |
| |||
348 | 372 | | |
349 | 373 | | |
350 | 374 | | |
| 375 | + | |
351 | 376 | | |
352 | 377 | | |
353 | 378 | | |
| |||
409 | 434 | | |
410 | 435 | | |
411 | 436 | | |
| 437 | + | |
412 | 438 | | |
413 | 439 | | |
414 | 440 | | |
| |||
423 | 449 | | |
424 | 450 | | |
425 | 451 | | |
| 452 | + | |
| 453 | + | |
426 | 454 | | |
427 | 455 | | |
428 | 456 | | |
| |||
467 | 495 | | |
468 | 496 | | |
469 | 497 | | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
470 | 508 | | |
471 | 509 | | |
472 | 510 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
73 | 73 | | |
74 | 74 | | |
75 | 75 | | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
76 | 84 | | |
77 | 85 | | |
78 | 86 | | |
| |||
195 | 203 | | |
196 | 204 | | |
197 | 205 | | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
198 | 219 | | |
199 | 220 | | |
200 | 221 | | |
| |||
548 | 569 | | |
549 | 570 | | |
550 | 571 | | |
551 | | - | |
| 572 | + | |
| 573 | + | |
552 | 574 | | |
553 | | - | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
554 | 595 | | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
555 | 605 | | |
556 | 606 | | |
557 | 607 | | |
558 | 608 | | |
559 | 609 | | |
| 610 | + | |
560 | 611 | | |
561 | 612 | | |
562 | 613 | | |
| |||
719 | 770 | | |
720 | 771 | | |
721 | 772 | | |
| 773 | + | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
722 | 785 | | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
723 | 791 | | |
724 | 792 | | |
725 | 793 | | |
| |||
924 | 992 | | |
925 | 993 | | |
926 | 994 | | |
| 995 | + | |
| 996 | + | |
| 997 | + | |
| 998 | + | |
| 999 | + | |
| 1000 | + | |
| 1001 | + | |
| 1002 | + | |
| 1003 | + | |
| 1004 | + | |
| 1005 | + | |
| 1006 | + | |
| 1007 | + | |
| 1008 | + | |
| 1009 | + | |
| 1010 | + | |
927 | 1011 | | |
928 | 1012 | | |
929 | 1013 | | |
| |||
985 | 1069 | | |
986 | 1070 | | |
987 | 1071 | | |
| 1072 | + | |
988 | 1073 | | |
989 | 1074 | | |
990 | 1075 | | |
| |||
0 commit comments