Skip to content

Commit 0c7e95a

Browse files
fix(app-shell): Studio's rails mark a customization with a short word, no fill, instead of a filled plus that read as an add button (#30468)
domain:ui#5 · os-sales · session_01L6bhgXtD8kC2LpK5YoB2sK <!-- claude-projects-attribution: {"github_login":"os-sales"} --> _Requested by **Sales** · [project thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuGDAtpB63x28wJ1Y26PFgRx)_ Fixes #30070 Clause-②: yes (widening) Metadata-protocol: none ## 维护者速读 - 改了什么:Studio 左栏里定制过的行,原来右侧是一个带底色的小方块图标(新增是绿底加号),现在改成一个彩色短词、没有底色:中文「定制」「新增」,英文「Custom」「Added」。鼠标悬停的提示和读屏名称不变,仍是完整说明(「已定制(CRM)」加定制包那句话)。 - 为什么改:绿底加号看起来像「添加」按钮,维护者点过一次没反应;裁决 6631229752 选 A:用文字、不要底色、保留提示、点击仍只是选中这一行。 - 风险与回滚:只改一个展示组件的紧凑分支和一个新词条(en/zh 各一行),不动数据和接口;英文比原来的 18px 图标宽约 22px(Custom 40px),行名会多截一点;回滚即还原本 PR。 - 先看:`ui/packages/app-shell/src/views/studio-design/CustomizationMark.tsx` 的 `if (compact)` 分支、`StudioDesignSurface.oneSurface-28572.test.tsx` 的「the rail mark never takes the row’s name」用例、`ui/packages/i18n/src/namespaces/studio/en.ts` 的 `engine.customizationMark.customizedShort`。 - 两分钟验证:`pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/StudioDesignSurface.oneSurface-28572.test.tsx`,预期 27 个用例全绿。 - 需要你做的:无,席位落地。 ## What changes - **The compact mark.** `CustomizationMark`'s `compact` branch (every pillar rail row through `RailCustomizationMark`, and the Interfaces rail's navigation-override mark, which wraps it in its popover button) no longer draws an 18×18 filled block with a lucide glyph. It prints one short word in the kind's colour with no background: `customized` reads the new `engine.customizationMark.customizedShort` (定制 / Custom), `added` reads the existing `engine.customizationMark.added` (新增 / Added). Its `role="img"`, accessible name (the full label, e.g. 已定制(HotCRM)) and tooltip (that label, then the sentence naming the pair) are unchanged. It carries no handler: a click on it is a click on its row. - **The non-compact chip** (All metadata lists) is unchanged. - **i18n (the widening).** One new `studio` namespace row in en and zh: `engine.customizationMark.customizedShort`. - **Comments.** `CustomizationMark.tsx` and `RailCustomizationMark` say the mark is a word, no fill, and not a control. - **Changeset.** `.changeset/30070-customization-mark-words.md`, `@object-ui/app-shell` and `@object-ui/i18n` patch. Branch: the harness-assigned `claude/project-thread-p3zei2`, not `claude/issue-30070-*`; the card is #30070. ## Tests `StudioDesignSurface.oneSurface-28572.test.tsx`'s rail-mark case (it pinned `textContent ''`) now pins: the customized mark prints `engine.customizationMark.customizedShort`, the added mark prints `engine.customizationMark.added`, both keep the full label as accessible name and tooltip head, neither holds an `svg`, and neither carries a `bg-` class. Expectations are read from Studio's table, never retyped. ## Acceptance notes - **The English word is `Custom`, not `Customized`.** The ruling names the zh words only. Measured on the example CRM's automations rail (row 223 px wide): 定制 and 新增 are 22 px each (the glyph was 18 px), `Added` is 34 px, `Customized` was 61 px and cut the selected row to "Convert Lea…", `Custom` is 40 px. The seat may prefer another word; it is one en row. - `InterfacesNavRail.tsx` was not edited (PR #30401 holds it). Its `InterfacesNavOverrideMark` doc comment still says "the compact customized glyph"; the comment is now stale wording only, behaviour measured below. Left for whoever next touches that file. - `KIND_CLASS` (the filled colours) now serves the non-compact chip only; `customizationOf` has no compact caller and would print its full label if one appeared. ## Verification Gates and tests ran on `cabd8ba8`; the head adds an evidence commit and its removal, the tree is byte-identical (`git diff cabd8ba HEAD` is empty). Base `b6caa141`. <details><summary>Live rig and screenshots (zh-CN and en-US, 1440 wide)</summary> `examples/app-crm` `objectstack dev --seed-admin --fresh` on a private port, the console via `vite` in `ui/apps/console` with `DEV_PROXY_TARGET`, Chromium. `POST /api/v1/packages/com.example.crm/ensure-customization` made `com.example.crm.custom`; through that pair, an override of flow `crm_convert_lead_wizard` and of app `crm_app` (navigation), and a new flow `crm_custom_follow_up_reminder`, each saved as a draft and published. Before = `CustomizationMark.tsx` restored to the base on the running vite, then restored to `HEAD` (blob equal). Measured marks (`data-testid="customization-mark"`): before, every mark 18 px wide with a filled background and an `svg`; after, background transparent, no `svg`, widths 定制 22, 新增 22, Custom 40, Added 34. Clicking the Interfaces heading mark still opens the override popover with 恢复软件包原版; clicking the 新增 mark on a rail row selects that row (`?surface=flow:crm_custom_follow_up_reminder`). Before, automations, zh / en: ![before automations zh](https://github.com/objectstack-ai/objectstack/blob/cf6a0b46df75b416e3a07c44e455ac616e15be6d/evidence/30070/before-automations-zh-CN-1440.png?raw=true) ![before automations en](https://github.com/objectstack-ai/objectstack/blob/cf6a0b46df75b416e3a07c44e455ac616e15be6d/evidence/30070/before-automations-en-US-1440.png?raw=true) After, automations, zh / en: ![after automations zh](https://github.com/objectstack-ai/objectstack/blob/cf6a0b46df75b416e3a07c44e455ac616e15be6d/evidence/30070/after-automations-zh-CN-1440.png?raw=true) ![after automations en](https://github.com/objectstack-ai/objectstack/blob/cf6a0b46df75b416e3a07c44e455ac616e15be6d/evidence/30070/after-automations-en-US-1440.png?raw=true) Before / after, interfaces (navigation-override mark beside the heading), zh / en: ![before interfaces zh](https://github.com/objectstack-ai/objectstack/blob/cf6a0b46df75b416e3a07c44e455ac616e15be6d/evidence/30070/before-interfaces-zh-CN-1440.png?raw=true) ![after interfaces zh](https://github.com/objectstack-ai/objectstack/blob/cf6a0b46df75b416e3a07c44e455ac616e15be6d/evidence/30070/after-interfaces-zh-CN-1440.png?raw=true) ![before interfaces en](https://github.com/objectstack-ai/objectstack/blob/cf6a0b46df75b416e3a07c44e455ac616e15be6d/evidence/30070/before-interfaces-en-US-1440.png?raw=true) ![after interfaces en](https://github.com/objectstack-ai/objectstack/blob/cf6a0b46df75b416e3a07c44e455ac616e15be6d/evidence/30070/after-interfaces-en-US-1440.png?raw=true) After, the override popover still opens from the mark: ![after popover](https://github.com/objectstack-ai/objectstack/blob/cf6a0b46df75b416e3a07c44e455ac616e15be6d/evidence/30070/after-nav-override-popover-zh-CN-1440.png?raw=true) </details> <details><summary>Tests, ablation, typecheck, lint</summary> - Every test file that names the mark (`git grep` for `customization-mark`, `CustomizationMark`, `customizationMark.`, `RailCustomizationMark`, `if-nav-override-mark` under `ui/**/*.test.ts*`: 9 files) plus `packages/i18n/src/__tests__/studio-namespace.test.ts` and `studio-namespace-lazy.test.tsx`: Test Files 11 passed (11), Tests 230 passed (230), lock VERDICT command-exit 0. A first run over all of `studio-design/` and `metadata-admin/` (556 files) was stopped for time; CI's Test UI runs the full set. - Ablation 1 (`scripts/ablation-replace.mjs`, anchor x1 to x0): the compact word replaced by `{null}`: 1 failed / 26 passed (`expected '' to be 'Custom'`). Restored, blob equals HEAD, `git diff HEAD` empty. - Ablation 2: the compact class list given the filled `KIND_CLASS` back: 1 failed / 26 passed (`not to match /(^|\s)bg-/`). Restored, blob equals HEAD. - `pnpm --filter @object-ui/i18n --filter @object-ui/app-shell type-check` (both `tsc --noEmit` and `tsc -p tsconfig.test.json`): lock VERDICT command-exit 0. - `pnpm --dir ui run check:spec-symbols`: exit 0. - eslint on the five changed source files: exit 0, warnings only on lines this PR does not change. - `pnpm check:new-line-citations` (ui): 0 new citations, exit 0. </details> <details><summary>Derived gates (35/35 exit 0)</summary> `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 35 families at `cabd8ba8`; every one ran and exited 0. `--ran` reconciliation: 35 derived, 35 run, 0 NOT-MEASURED (a derived zero, every line carries an exit code). `pnpm --dir ui exec vitest run scripts/`: 170 files passed, 2 skipped; 4985 tests passed, 2 skipped. CI runs the rest. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01L6bhgXtD8kC2LpK5YoB2sK)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01L6bhgXtD8kC2LpK5YoB2sK --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a1d0b1e commit 0c7e95a

6 files changed

Lines changed: 39 additions & 21 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
fix(app-shell): Studio's rails mark a customized row with the word 定制 / Custom and an added row with 新增 / Added, no fill, instead of a filled square with a glyph that read as an add button (#30070)
7+
8+
Clause-②: yes (widening) — new studio i18n key `engine.customizationMark.customizedShort` (en, zh)
9+
Metadata-protocol: none
10+
11+
The compact customization mark the pillar rails draw (and the Interfaces rail's navigation-override mark, which wraps it) now prints one short coloured word with no background. Its tooltip and accessible name are unchanged: the full text ("已定制(HotCRM)") followed by the sentence naming the customization package. It is not a control: a click on it is a click on its row.

‎ui/packages/app-shell/src/views/studio-design/CustomizationMark.tsx‎

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616

1717
import * as React from 'react';
18-
import { Layers3, PencilRuler, Plus } from 'lucide-react';
1918
import { cn } from '@object-ui/components';
2019
import { customizationPackageId } from '@objectstack/spec/kernel';
2120
import { t, tFormat } from '../metadata-admin/i18n.js';
@@ -36,18 +35,20 @@ const KIND_CLASS: Record<CustomizationMarkChipKind, string> = {
3635
customizationOf: 'bg-muted text-muted-foreground',
3736
};
3837

39-
/** objectstack#29693 — the glyph the compact mark draws in place of its text. */
40-
const KIND_ICON: Record<CustomizationMarkChipKind, React.ElementType> = {
41-
customized: PencilRuler,
42-
added: Plus,
43-
customizationOf: Layers3,
38+
/** The compact mark's colour: its word alone, no fill, so it never reads as a button. */
39+
const KIND_TEXT_CLASS: Record<CustomizationMarkChipKind, string> = {
40+
customized: 'text-violet-700 dark:text-violet-300',
41+
added: 'text-emerald-700 dark:text-emerald-300',
42+
customizationOf: 'text-muted-foreground',
4443
};
4544

4645
/**
47-
* objectstack#29693 — `compact` (the pillar rails): the mark is its glyph
48-
* alone, so it never takes a row's name ("已定制(ObjectStack Showcase)" left a
49-
* selected flow row no room for its label). Its text is its accessible name
50-
* and leads its tooltip, before the sentence naming the pair.
46+
* `compact` (the pillar rails): the mark prints one short word with no
47+
* background (objectstack#30070: 定制 for customized, 新增 for added), so it
48+
* reads as a status, never as a button, and never takes a row's name
49+
* (objectstack#29693). Its full text is its accessible name and leads its
50+
* tooltip, before the sentence naming the pair. It is not a control: a click
51+
* on it is a click on its row.
5152
*/
5253
export function CustomizationMark({
5354
kind,
@@ -64,7 +65,7 @@ export function CustomizationMark({
6465
pairId?: string;
6566
locale: string;
6667
className?: string;
67-
/** Draw the glyph alone, the text in its accessible name and tooltip. */
68+
/** Print one short word, no fill; the full text is its accessible name and leads its tooltip. */
6869
compact?: boolean;
6970
}): React.ReactElement {
7071
const base = baseName ?? '';
@@ -76,7 +77,6 @@ export function CustomizationMark({
7677
? tFormat(`engine.customizationMark.${kind}Title`, locale, { baseName: base, pair: pairId })
7778
: undefined;
7879
if (compact) {
79-
const Icon = KIND_ICON[kind];
8080
return (
8181
<span
8282
data-testid="customization-mark"
@@ -85,9 +85,9 @@ export function CustomizationMark({
8585
role="img"
8686
aria-label={label}
8787
title={title ? `${label}\n${title}` : label}
88-
className={cn('inline-flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-[5px]', KIND_CLASS[kind], className)}
88+
className={cn('inline-flex shrink-0 items-center whitespace-nowrap text-[11px] font-medium leading-none', KIND_TEXT_CLASS[kind], className)}
8989
>
90-
<Icon className="h-3 w-3" aria-hidden="true" />
90+
{kind === 'customized' ? t('engine.customizationMark.customizedShort', locale) : label}
9191
</span>
9292
);
9393
}

‎ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.oneSurface-28572.test.tsx‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -464,8 +464,8 @@ describe('the Interfaces rail lists a base and its pair as one list (objectstack
464464
});
465465
});
466466

467-
describe('the rail mark never takes the row’s name (objectstack#29693)', () => {
468-
it('is its glyph alone: the row prints only its label, and the mark’s text is its name and leads its tooltip', async () => {
467+
describe('the rail mark never takes the row’s name (objectstack#29693, objectstack#30070)', () => {
468+
it('prints one short word with no fill and no glyph: its full text is its name and leads its tooltip', async () => {
469469
renderAt(`/studio/${BASE}/interfaces`);
470470
await interfacesReady();
471471
const row = rowOf(ifRail(), 'dashboard', 'pipeline')!;
@@ -475,11 +475,16 @@ describe('the rail mark never takes the row’s name (objectstack#29693)', () =>
475475
expect(mark).toHaveAttribute('data-compact');
476476
expect(mark).toHaveAccessibleName(label);
477477
expect(mark.getAttribute('title')?.split('\n')[0]).toBe(label);
478-
expect(mark.textContent).toBe('');
478+
expect(mark.textContent).toBe(t('engine.customizationMark.customizedShort', 'en'));
479479
expect(within(row).queryByText(label)).toBeNull();
480480
const added = within(rowOf(ifRail(), 'dashboard', 'wins')!).getByTestId('customization-mark');
481481
expect(added).toHaveAccessibleName(t('engine.customizationMark.added', 'en'));
482-
expect(added.textContent).toBe('');
482+
expect(added.textContent).toBe(t('engine.customizationMark.added', 'en'));
483+
// A status, not a control: no filled block, no glyph that reads as an add button.
484+
for (const m of [mark, added]) {
485+
expect(m.querySelector('svg')).toBeNull();
486+
expect(m.className).not.toMatch(/(^|\s)bg-/);
487+
}
483488
});
484489
});
485490

‎ui/packages/app-shell/src/views/studio-design/StudioRailCustomizations.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,9 @@ export function railCustomizationsOf(
115115
}
116116

117117
/**
118-
* A row's mark in the rail's trailing slot, or nothing. objectstack#29693 —
119-
* compact (its glyph, the text in its name and tooltip), so it never takes the
120-
* row's label.
118+
* A row's mark in the rail's trailing slot, or nothing. Compact (one short
119+
* word, no fill; the full text in its name and tooltip), so it never takes the
120+
* row's label and never reads as a button.
121121
*/
122122
export function RailCustomizationMark({
123123
row,

‎ui/packages/i18n/src/namespaces/studio/en.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3682,6 +3682,7 @@ const STUDIO_EN: Record<string, string> = {
36823682
// objectstack ADR-0146 D3 (#28568) — the customization marks, counts and
36833683
// filter of the All metadata area, from a base's customization read.
36843684
'engine.customizationMark.customized': 'Customized ({baseName})',
3685+
'engine.customizationMark.customizedShort': 'Custom',
36853686
'engine.customizationMark.customizedTitle': 'The customization package {pair} overrides this item of {baseName}',
36863687
'engine.customizationMark.added': 'Added',
36873688
'engine.customizationMark.addedTitle': 'Added to {baseName} by its customization package {pair}',

‎ui/packages/i18n/src/namespaces/studio/zh.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3257,6 +3257,7 @@ const STUDIO_ZH: Record<string, string> = {
32573257
// objectstack ADR-0146 D3 (#28568) — the customization marks, counts and
32583258
// filter of the All metadata area, from a base's customization read.
32593259
'engine.customizationMark.customized': '已定制({baseName})',
3260+
'engine.customizationMark.customizedShort': '定制',
32603261
'engine.customizationMark.customizedTitle': '定制包 {pair} 覆盖了 {baseName} 的此项',
32613262
'engine.customizationMark.added': '新增',
32623263
'engine.customizationMark.addedTitle': '由 {baseName} 的定制包 {pair} 新增',

0 commit comments

Comments
 (0)