Skip to content

feat(studio): the All metadata lists mark a base's customizations, with an only-customizations filter and tile counts - #28667

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/project-thread-84w3mr
Nov 1, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/project-thread-84w3mr

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

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 fix(runtime): GET /packages/:id/customizations answers over HTTP, mounted beside the other package reads #28635 and is read live by the landing (feat(app-shell): each read-only base card on the Studio landing says how many items are customized and switched off #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.

New and neighbouring suites · 39 files · 356 tests

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.

Ablations · both red

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).
Type-check and lint
  • 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).
Derived gates · `dispatch-gates --commands` · 34 commands

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.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QsR2jSKpVAotd33JtFuXkP


Generated by Claude Code

… outside the zh base-term census

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jSKpVAotd33JtFuXkP
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

席位核对 · ACCEPT · 2026-11-01T13:24Z

Head-sha: bfca84cdc30a02187bf6c0308df5bf8e9a33a7e0

  • Shape: draft → main, first content line Fixes #28568, no other card number beside a closing keyword. 10 files, +783 / −7 (790 lines ≤ 3000), no governed path (check-governed-merges --pr 28667). Author objectstack-fleet[bot]. Clause-②: no / Metadata-protocol: none line-initial in body and changeset, as claimed. Every new string is one of 13 keys in @object-ui/i18n's studio namespace (Record<string, string>, en + zh); no locales/* pack key moved. No contract review is owed.
  • Wanted, item by item, read in the diff:
    1. With a base open in the All metadata area, each type list holds the base's and the pair's items. A pair item the read lists as an override is marked "Customized (BASE)"; any other pair item is marked "Added". Both come from customizationMarkOf over the served read; nothing is diffed client-side.
    2. Each list has an "only customizations" filter from the same read.
    3. Directory tiles carry the type's served count, extensions on the object tile. The header carries the base's total (engine.directory.customizations.total).
    4. A pending or failed read draws no mark and no number, with the server's sentence on failure. A base with no pair draws nothing.
    5. One mark component, CustomizationMark.tsx, created here; N-19 (Studio: a base and its <base>.custom as one surface — "customized" / "added" marks, "only customizations", the pair folded under its base, New lands in the pair #28572) reuses it.
    6. The cross-package leg labels a pair's row with its base (customizationBaseOfRow).
  • Head history: at f42f397b, Test UI (5/16) was red on the zh base-term census (zh-base-term.i18n-13131). Its \bbase\b regex matched the {base} placeholder in six of this PR's zh rows. bfca84cd renames that placeholder to {baseName} in en, zh and the three call sites (CustomizationMark.tsx, DirectoryPage.tsx, the 28568 test): 5 files, 18/18 lines, renames only. The dev checked the census goes red with zh.ts restored to f42f397b, and that no remaining call site passes base.
  • Gates (check-runs on this head): 44 success, 11 skipped, 0 failed or pending. All ten required contexts are included; Build Core is skipped by its path filter on a ui-only diff.
  • Tests: metadataAreaHost.customizations-28568 has 8 cases: the card's five plus three controls (Setup makes no read; the cross-package base label; an unlisted base row stays unmarked). PR body correction: the body says customizationMarks.test.ts pins the pure rule in "10 cases". It has 8 (it( count at this head). This record supersedes that number.
  • Acceptance notes, carried: two gaps the body names are out of this card's surface. Both go to N-19 (Studio: a base and its <base>.custom as one surface — "customized" / "added" marks, "only customizations", the pair folded under its base, New lands in the pair #28572), which this seat claims next:
    • (a) Items the pair adds are marked and filterable but counted on no tile, because N-15's read does not list them.
    • (b) A type that only the pair holds draws no tile on the base's directory. The diagnostics sweep is scoped to one package (useGlobalDiagnostics).
  • Out of scope / NOT MEASURED: no live Console run; the read's route (fix(runtime): GET /packages/:id/customizations answers over HTTP, mounted beside the other package reads #28635) is read live by the landing (feat(app-shell): each read-only base card on the Studio landing says how many items are customized and switched off #28634) and stubbed at fetch here. Other languages fall back to en for the 13 new keys, as the whole studio namespace does. Skills-docs: not owed.
  • Landing: ordinary queue landing, pr_ready then automerge_enable. Fixes #28568 closes the card on merge. Neighbours PR feat(studio): New similar on a flow in the Automations pillar, opening the copy where the clone door landed it #28668 (queued) and PR feat(studio): New similar on a read-only base's permission set, opening the copy where the clone door landed it #28669 edit StudioDesignSurface.tsx, which this PR does not touch. All three add rows to the studio en/zh packs in other regions, so the queue rebuilds whichever lands later.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 1, 2026 13:26
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 1, 2026 13:26
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit 9182ff6 Nov 1, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-84w3mr branch November 1, 2026 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Studio: All metadata lists show a base's .custom items marked "customized" or "added", with an "only customizations" filter and per-type counts

1 participant