From e563bb4bbc8f98f36d7bbd66bf5f14db05ff4ea3 Mon Sep 17 00:00:00 2001 From: ZiyaZhang <199893125+ZiyaZhang@users.noreply.github.com> Date: Fri, 25 Sep 2026 00:49:05 -0700 Subject: [PATCH] fix(ui-settings-plugin-inventory): address review on card ids and loading The visible id chip and the accessible name now share one check for whether the entry id adds to the title, so an include: id that matches the title is left out of both. The loading skeleton's status region carries visually hidden text so screen readers announce it. The settings-chrome browser scenario expects the preset group open and enabled rows without a tag, and its two ARIA snapshots are refreshed. The row-partner CSS comment states its two-column assumption. --- .../plugin-instances.expected.md | 13 +++----- .../settings-chrome/plugins.expected.md | 5 ++- apps/web/tests/settings-chrome.e2e.ts | 31 ++++++++++++------- .../README.i18n.yaml | 4 +-- .../ui-settings-plugin-inventory/README.md | 2 +- .../ui-settings-plugin-inventory/README.zh.md | 2 +- .../PluginInventorySettingsTab.module.css | 6 ++-- .../src/client/PluginInventorySettingsTab.tsx | 19 +++++++----- .../tests/components.client.spec.tsx | 10 +++--- 9 files changed, 51 insertions(+), 41 deletions(-) diff --git a/apps/web/tests/expected/settings-chrome/plugin-instances.expected.md b/apps/web/tests/expected/settings-chrome/plugin-instances.expected.md index 612291fe84..16d2109528 100644 --- a/apps/web/tests/expected/settings-chrome/plugin-instances.expected.md +++ b/apps/web/tests/expected/settings-chrome/plugin-instances.expected.md @@ -1,23 +1,20 @@ - list: - listitem: - - button "tool-subagent-control, tool-subagent-control, 已启用": + - button "tool-subagent-control, 已启用": - strong: tool-subagent-control - - text: 已启用 Globally named send_message, interrupt_agent, and list_agents tools over ctx.subagents continuations - - code: tool-subagent-control + - text: Globally named send_message, interrupt_agent, and list_agents tools over ctx.subagents continuations - listitem: - button "tool-subagent-control/list-agents, tool-subagent-list-agents, 已启用": - strong: tool-subagent-control/list-agents - - text: 已启用 - code: tool-subagent-list-agents - listitem: - - button "tool-subagent, tool-subagent, 已启用": + - button "tool-subagent, 已启用": - strong: tool-subagent - - text: 已启用 Model-facing subagent delegation tool over the ctx.subagents seam - - code: tool-subagent + - text: Model-facing subagent delegation tool over the ctx.subagents seam - listitem: - button "tool-subagent, tool-subagent-fork, 已启用": - strong: tool-subagent - - text: 已启用 Model-facing subagent delegation tool over the ctx.subagents seam + - text: Model-facing subagent delegation tool over the ctx.subagents seam - code: tool-subagent-fork - listitem: - button "tool-subagent, tool-subagent-codex, 已停用": diff --git a/apps/web/tests/expected/settings-chrome/plugins.expected.md b/apps/web/tests/expected/settings-chrome/plugins.expected.md index 7cfe33f5f0..20e096a308 100644 --- a/apps/web/tests/expected/settings-chrome/plugins.expected.md +++ b/apps/web/tests/expected/settings-chrome/plugins.expected.md @@ -1,5 +1,4 @@ - listitem: - - button "tool-subagent, tool-subagent, 已启用": + - button "tool-subagent, 已启用": - strong: tool-subagent - - text: 已启用 Model-facing subagent delegation tool over the ctx.subagents seam - - code: tool-subagent + - text: Model-facing subagent delegation tool over the ctx.subagents seam diff --git a/apps/web/tests/settings-chrome.e2e.ts b/apps/web/tests/settings-chrome.e2e.ts index 14278995c4..54b5e5f6f8 100644 --- a/apps/web/tests/settings-chrome.e2e.ts +++ b/apps/web/tests/settings-chrome.e2e.ts @@ -111,16 +111,17 @@ describe('web e2e: settings modal and General preferences', () => { // rewrite this surface's golden. await dialog.getByRole('button', { name: '内置插件', exact: true }).click() await dialog.getByRole('heading', { name: '内置插件', exact: true }).waitFor({ timeout: 10_000 }) - // Both groups start collapsed; the preset group's header still carries its display-only switcher. + // The preset group starts open under its display-only switcher; the global group starts folded. const presetSwitcher = dialog.getByRole('button', { name: '选择要查看的 Agent 预设' }) await presetSwitcher.waitFor({ timeout: 10_000 }) // The shipped default's zh display name comes from the zh dictionaries. expect(await presetSwitcher.textContent()).toBe('标准模式(默认)') const presetToggle = dialog.getByRole('button', { name: '会话插件', exact: true }) - expect(await presetToggle.getAttribute('aria-expanded')).toBe('false') - expect(await dialog.locator('[data-plugin-scope="preset"] [data-plugin-entry]').count()).toBe(0) - await presetToggle.click() - await dialog.getByRole('button', { name: /^全局/ }).click() + expect(await presetToggle.getAttribute('aria-expanded')).toBe('true') + expect(await dialog.locator('[data-plugin-scope="preset"] [data-plugin-entry]').count()).toBeGreaterThan(0) + const globalToggle = dialog.getByRole('button', { name: /^全局/ }) + expect(await globalToggle.getAttribute('aria-expanded')).toBe('false') + await globalToggle.click() const pluginRow = dialog.locator(PLUGIN_ROW_SELECTOR) await pluginRow.waitFor({ timeout: 10_000 }) const expectedPluginCount = [...scaffold.ctx.loader.entries()] @@ -132,11 +133,12 @@ describe('web e2e: settings modal and General preferences', () => { // presets took over included, preset compositions excluded. expect(await dialog.locator('[data-plugin-scope="global"] [data-plugin-entry]').count()) .toBe(expectedPluginCount) - // The enablement tag is the row's collapsed status: an active fiber draws no - // dot, so no global row names the active phase. Guard the assertion against - // matching nothing because no row is enabled. - expect(await dialog.locator('[data-plugin-scope="global"] [data-plugin-entry] button[aria-label$="已启用"]').count()) - .toBeGreaterThan(0) + // A plainly enabled row states its status only in its accessible name: it + // carries no tag, and an active fiber draws no dot, so no global row shows + // either. Guard the assertions against matching nothing because no row is enabled. + const enabledRows = dialog.locator('[data-plugin-scope="global"] [data-plugin-entry] button[aria-label$="已启用"]') + expect(await enabledRows.count()).toBeGreaterThan(0) + expect(await enabledRows.getByText('已启用', { exact: true }).count()).toBe(0) expect(await dialog.locator('[data-plugin-scope="global"] [role="img"][aria-label="运行中"]').count()).toBe(0) expect(await dialog.locator('[data-plugin-count]').getAttribute('data-plugin-count')) .toBe(String(expectedPluginCount)) @@ -159,10 +161,17 @@ describe('web e2e: settings modal and General preferences', () => { ] as const for (const [entryId, status] of instanceRows) { const row = dialog.locator(`[data-plugin-scope="preset"] [data-plugin-entry="${entryId}"]`) - const trigger = row.getByRole('button', { name: `tool-subagent, ${entryId}, ${status}`, exact: true }) + // An id that repeats the title adds nothing, so its card shows no chip and its name omits it. + const repeatsTitle = entryId === 'tool-subagent' + const name = repeatsTitle ? `tool-subagent, ${status}` : `tool-subagent, ${entryId}, ${status}` + const trigger = row.getByRole('button', { name, exact: true }) await trigger.waitFor({ timeout: 10_000 }) expect(await trigger.getAttribute('aria-expanded')).toBe('false') const identity = row.locator('code') + if (repeatsTitle) { + expect(await identity.count()).toBe(0) + continue + } expect(await identity.textContent()).toBe(entryId) expect(await identity.getAttribute('title')).toBe(entryId) } diff --git a/packages/client/ui-settings-plugin-inventory/README.i18n.yaml b/packages/client/ui-settings-plugin-inventory/README.i18n.yaml index b69016100b..b30b1c29f8 100644 --- a/packages/client/ui-settings-plugin-inventory/README.i18n.yaml +++ b/packages/client/ui-settings-plugin-inventory/README.i18n.yaml @@ -18,8 +18,8 @@ en: 67637808e7940157 zh: d4d0716ea4d33e4a /deepseek-ai-dsh-client-ui-settings-plugin-inventory/use-this-package/reading-a-card: - en: 6ccd8a53e3205f24 - zh: 1d0ad59bfb7786e4 + en: 141d6a73e99fee51 + zh: 2aa28b174867c967 /deepseek-ai-dsh-client-ui-settings-plugin-inventory/use-this-package/the-preset-switcher: en: 84e5938329199a22 zh: 6f5cfaac56433e8b diff --git a/packages/client/ui-settings-plugin-inventory/README.md b/packages/client/ui-settings-plugin-inventory/README.md index d6127c9ed9..f6aa2312b8 100644 --- a/packages/client/ui-settings-plugin-inventory/README.md +++ b/packages/client/ui-settings-plugin-inventory/README.md @@ -33,7 +33,7 @@ A failed installation with pending pnpm build permissions offers **Allow these s Each collapsed card uses the plugin's localized title and description when available; a description shows at most two lines until its card is expanded. Settings shortens literal package-name or module-name fallbacks by removing the npm scope and the `cordis:`, `cordis-plugin-`, or `dsh-`/`dsh-host-`/`dsh-client-` prefix; locale titles remain unchanged. The Host supplies locale and package-field fallbacks in `meta`; the Client selects the current language. Full module specifiers remain available in details and search. Missing descriptions are omitted, and metadata diagnostics appear with the card. This compact naming applies only to Settings, not the sidebar's Plugins page. -A small enablement tag marks only the states that differ from plainly enabled: disabled, conditional, provided by presets, and failed; a plainly enabled row carries no tag. A colored root-fiber status dot marks the live phases no tag states: `pending` maps to idle, while `loading` and `unloading` map to ongoing; an `active` or `failed` fiber shows no dot. The stable entry id appears in a code chip at the card's bottom-left, in line with the title, only when it differs from the title: a composition-generated id drops its leading `include:` marker there, and an id that then repeats the title is omitted. The chip's hover text, search, and expanded details retain the complete id, and so does the accessible name unless the id equals the title. Long entry ids truncate in the chip and remain available on hover. Expanding one card reveals its full description, the declared entry id, the full module specifier, and the state facts: a preset row names the preset it comes from, its runtime status when the composition is live, and its disable condition when it carries one; a preset-provided global row explains that agent presets provide it per session, names the presets that enable it, and offers a jump into the preset group. Preset names resolve through the shared `presetDisplayText` fold (`dsh-agent-preset-registry/display`) over [`ui-agent-preset`](../ui-agent-preset/README.md)'s dictionaries: shipped presets follow the active locale while user-authored ones keep their own metadata, so an English surface never echoes the preset declarations' Chinese names. Search filters both groups by localized title and description, module name, and entry id. +A small enablement tag marks only the states that differ from plainly enabled: disabled, conditional, provided by presets, and failed; a plainly enabled row carries no tag. A colored root-fiber status dot marks the live phases no tag states: `pending` maps to idle, while `loading` and `unloading` map to ongoing; an `active` or `failed` fiber shows no dot. The stable entry id appears in a code chip at the card's bottom-left, in line with the title, only when it differs from the title: a composition-generated id drops its leading `include:` marker there, and an id that then repeats the title is omitted. Whenever the chip shows, its hover text and the card's accessible name carry the complete id; search and expanded details always retain it. Long entry ids truncate in the chip and remain available on hover. Expanding one card reveals its full description, the declared entry id, the full module specifier, and the state facts: a preset row names the preset it comes from, its runtime status when the composition is live, and its disable condition when it carries one; a preset-provided global row explains that agent presets provide it per session, names the presets that enable it, and offers a jump into the preset group. Preset names resolve through the shared `presetDisplayText` fold (`dsh-agent-preset-registry/display`) over [`ui-agent-preset`](../ui-agent-preset/README.md)'s dictionaries: shipped presets follow the active locale while user-authored ones keep their own metadata, so an English surface never echoes the preset declarations' Chinese names. Search filters both groups by localized title and description, module name, and entry id. ### The preset switcher diff --git a/packages/client/ui-settings-plugin-inventory/README.zh.md b/packages/client/ui-settings-plugin-inventory/README.zh.md index a084d93919..cb0c2ce723 100644 --- a/packages/client/ui-settings-plugin-inventory/README.zh.md +++ b/packages/client/ui-settings-plugin-inventory/README.zh.md @@ -33,7 +33,7 @@ kind: "package-reference" 每张收起的卡片使用插件可用的本地化标题与描述;描述最多显示两行,展开卡片后显示全文。设置页会缩短字面的包名或模块名回退值,移除 npm scope 以及 `cordis:`、`cordis-plugin-` 或 `dsh-`/`dsh-host-`/`dsh-client-` 前缀;locale 标题保持原样。Host 在 `meta` 中提供 locale 与包字段的回退结果,Client 选择当前语言。详情与搜索仍保留完整模块标识。缺失的描述不显示,元信息诊断随卡片展示。这种短名显示仅用于设置页,不影响侧栏的插件页。 -表示启停状态的小标签只标记与普通启用不同的状态:已停用、条件启用、由预设提供与启动失败;普通启用的行不带标签。彩色根 fiber 状态圆点标记没有标签表达的存活阶段:`pending` 映射为 idle,`loading` 与 `unloading` 映射为 ongoing;`active` 或 `failed` fiber 不显示圆点。稳定的条目 id 仅在与标题不同时显示,位于卡片左下角的代码标签中,与标题左对齐:组合生成的 id 在此省略开头的 `include:` 标记,省略后与标题相同的 id 不再重复显示。代码标签的悬停提示、搜索与展开详情保留完整 id;除非 id 与标题相同,无障碍名称也保留完整 id。长条目 id 会在代码标签内截断,悬停时仍可查看完整值。展开卡片后会显示完整描述、声明的条目 id、完整模块标识与状态事实:预设行说明它来自哪个预设、组合存活时的运行状态,以及它携带的禁用条件;被预设提供的全局行说明它由 Agent 预设按会话提供、列出启用它的预设,并提供跳转到预设组的入口。预设名经共享的 `presetDisplayText` 纯函数(`dsh-agent-preset-registry/display`)叠在 [`ui-agent-preset`](../ui-agent-preset/README.zh.md) 的字典上解析:内置预设走当前语言,用户自建预设保留自己的元数据,因此英文界面不会回显预设声明里的中文名。搜索按本地化标题与描述、模块名称及条目 id 过滤两组。 +表示启停状态的小标签只标记与普通启用不同的状态:已停用、条件启用、由预设提供与启动失败;普通启用的行不带标签。彩色根 fiber 状态圆点标记没有标签表达的存活阶段:`pending` 映射为 idle,`loading` 与 `unloading` 映射为 ongoing;`active` 或 `failed` fiber 不显示圆点。稳定的条目 id 仅在与标题不同时显示,位于卡片左下角的代码标签中,与标题左对齐:组合生成的 id 在此省略开头的 `include:` 标记,省略后与标题相同的 id 不再重复显示。显示代码标签时,其悬停提示与卡片的无障碍名称带完整 id;搜索与展开详情始终保留完整 id。长条目 id 会在代码标签内截断,悬停时仍可查看完整值。展开卡片后会显示完整描述、声明的条目 id、完整模块标识与状态事实:预设行说明它来自哪个预设、组合存活时的运行状态,以及它携带的禁用条件;被预设提供的全局行说明它由 Agent 预设按会话提供、列出启用它的预设,并提供跳转到预设组的入口。预设名经共享的 `presetDisplayText` 纯函数(`dsh-agent-preset-registry/display`)叠在 [`ui-agent-preset`](../ui-agent-preset/README.zh.md) 的字典上解析:内置预设走当前语言,用户自建预设保留自己的元数据,因此英文界面不会回显预设声明里的中文名。搜索按本地化标题与描述、模块名称及条目 id 过滤两组。 ### 预设切换器 diff --git a/packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.module.css b/packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.module.css index 1d732ae93d..be3aa42773 100644 --- a/packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.module.css +++ b/packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.module.css @@ -113,8 +113,9 @@ } /* The other card in an expanded card's row keeps its own height instead of - stretching alongside the details. With two columns each row starts at an odd - position; in one column the matched card sits alone in its row, unaffected. */ + stretching alongside the details. The selectors assume the two columns + `.cards` declares, where each row starts at an odd position, and must change + with that count; in one column the matched card sits alone in its row. */ .card:nth-child(odd)[data-open='true'] + .card, .card:nth-child(odd):has(+ .card[data-open='true']) { align-self: start; @@ -414,7 +415,6 @@ height: 12px; } - .enabledIn { display: flex; flex-wrap: wrap; diff --git a/packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.tsx b/packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.tsx index a86672f79f..57810b993a 100644 --- a/packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.tsx +++ b/packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.tsx @@ -80,9 +80,14 @@ function entrySubtitle(entryId: string): string { return entryId.replace(/^include:/, '') } -/** Accessible card name: the title, the entry id unless it repeats the title, then the enablement state. */ +/** Whether a card shows its entry id: the id exists and, without its `include:` marker, differs from the title. */ +function idAddsToTitle(entryId: string | null, title: string): entryId is string { + return entryId !== null && entrySubtitle(entryId) !== title +} + +/** Accessible card name: the title, the complete entry id when the card shows one, then the enablement state. */ function cardLabel(title: string, entryId: string | null, state: string): string { - return [title, ...entryId === null || entryId === title ? [] : [entryId], state].join(', ') + return idAddsToTitle(entryId, title) ? `${title}, ${entryId}, ${state}` : `${title}, ${state}` } /** Preserve translated titles and shorten literal package or module name fallbacks in Settings. */ @@ -135,7 +140,6 @@ function PluginCard({ const open = expanded === rowKey const detailId = `plugin-details-${encodeURIComponent(rowKey)}` const descriptionId = useId() - const subtitle = entryId === null ? null : entrySubtitle(entryId) return (
{subtitle}
+ {entrySubtitle(entryId)}
- )}
+ ) : null}
{metadataError === undefined ? null : {metadataError}
} {open ?