mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-14 04:01:35 +00:00
refactor(plugin-inventory): settings-row style group headers
Both scope groups drop their boxed chrome for the General-settings row idiom: a title row (session-plugins title with the right-aligned preset selector pill; global-plugins title), a grey subtitle line carrying the count, a hairline divider between groups, and the cards grid below. Counts ride the subtitle as text while the data attributes keep the raw numbers for tests.
This commit is contained in:
+24
-30
@@ -225,21 +225,29 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
border: 1px solid var(--dsw-alias-border-l2);
|
||||
border-radius: 12px;
|
||||
padding: 10px 12px 12px;
|
||||
}
|
||||
|
||||
.groupHeader,
|
||||
.groupToggle {
|
||||
.groupTitleRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 28px;
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.group + .group {
|
||||
border-top: 1px solid var(--dsw-alias-border-l2);
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
.headerEnd {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.groupToggle {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
@@ -263,22 +271,17 @@
|
||||
}
|
||||
|
||||
.groupTitle {
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
font-weight: 500;
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
.groupSubtitle {
|
||||
overflow: hidden;
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.groupCount {
|
||||
margin-left: auto;
|
||||
.groupSub {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 8px;
|
||||
margin: -6px 0 0 20px;
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
@@ -286,10 +289,7 @@
|
||||
}
|
||||
|
||||
.failedCount {
|
||||
flex: none;
|
||||
color: var(--dsw-alias-state-error-primary);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
/* The preset switcher trigger mirrors the General-settings selector pill. */
|
||||
@@ -331,12 +331,6 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.iconToggle {
|
||||
flex: none;
|
||||
width: auto;
|
||||
min-height: 0;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.groupBody {
|
||||
display: flex;
|
||||
|
||||
+52
-43
@@ -365,46 +365,51 @@ export function PluginInventorySettingsTab({ list, t }: PluginInventorySettingsT
|
||||
|
||||
{selected !== undefined ? (
|
||||
<section className={css.group} data-plugin-scope="preset" data-preset-id={selected.id}>
|
||||
<div className={css.groupHeader}>
|
||||
<div className={css.groupTitleRow}>
|
||||
<button
|
||||
type="button"
|
||||
className={`${css.groupToggle} ${css.iconToggle}`}
|
||||
className={css.groupToggle}
|
||||
aria-expanded={presetEffectiveOpen}
|
||||
aria-controls={`${sectionId}-preset`}
|
||||
aria-label={t('presetSubtitle')}
|
||||
onClick={() => { setPresetOpen(!presetEffectiveOpen) }}
|
||||
>
|
||||
<IconChevronDownOutline14 className={css.chevron} size={12} aria-hidden="true" />
|
||||
<span className={css.groupTitle}>{t('presetTitle')}</span>
|
||||
</button>
|
||||
<Menu
|
||||
open={switcherOpen}
|
||||
onClose={() => { setSwitcherOpen(false) }}
|
||||
items={presets.map(preset => ({ id: preset.id, label: presetLabel(preset, t) }))}
|
||||
selectedId={selected.id}
|
||||
onSelect={(id) => {
|
||||
setSwitcherOpen(false)
|
||||
setChosenPreset(id)
|
||||
}}
|
||||
portal
|
||||
anchor={(
|
||||
<button
|
||||
type="button"
|
||||
className={css.switcher}
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={switcherOpen}
|
||||
aria-label={t('switcherLabel')}
|
||||
onClick={() => { setSwitcherOpen(value => !value) }}
|
||||
>
|
||||
<span className={css.switcherLabel}>{presetLabel(selected, t)}</span>
|
||||
<IconChevronDownOutline14 className={css.chevron} size={12} aria-hidden="true" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<span className={css.groupSubtitle}>{t('presetSubtitle')}</span>
|
||||
<span className={css.groupCount} data-preset-plugin-count={selectedRows.length}>
|
||||
{selectedRows.length}
|
||||
</span>
|
||||
<div className={css.headerEnd}>
|
||||
<Menu
|
||||
open={switcherOpen}
|
||||
onClose={() => { setSwitcherOpen(false) }}
|
||||
items={presets.map(preset => ({ id: preset.id, label: presetLabel(preset, t) }))}
|
||||
selectedId={selected.id}
|
||||
onSelect={(id) => {
|
||||
setSwitcherOpen(false)
|
||||
setChosenPreset(id)
|
||||
}}
|
||||
align="end"
|
||||
portal
|
||||
anchor={(
|
||||
<button
|
||||
type="button"
|
||||
className={css.switcher}
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={switcherOpen}
|
||||
aria-label={t('switcherLabel')}
|
||||
onClick={() => { setSwitcherOpen(value => !value) }}
|
||||
>
|
||||
<span className={css.switcherLabel}>{presetLabel(selected, t)}</span>
|
||||
<IconChevronDownOutline14 className={css.chevron} size={12} aria-hidden="true" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className={css.groupSub}>
|
||||
{t('presetSubtitle')}
|
||||
<span data-preset-plugin-count={selectedRows.length}>
|
||||
{` · ${String(selectedRows.length)} ${t('countUnit')}`}
|
||||
</span>
|
||||
</p>
|
||||
{presetEffectiveOpen ? (
|
||||
<div id={`${sectionId}-preset`} className={css.groupBody}>
|
||||
{selected.broken !== undefined ? (
|
||||
@@ -437,21 +442,25 @@ export function PluginInventorySettingsTab({ list, t }: PluginInventorySettingsT
|
||||
|
||||
{entries.length > 0 ? (
|
||||
<section className={css.group} data-plugin-scope="global">
|
||||
<button
|
||||
type="button"
|
||||
className={css.groupToggle}
|
||||
aria-expanded={globalEffectiveOpen}
|
||||
aria-controls={`${sectionId}-global`}
|
||||
onClick={() => { setGlobalOpen(!globalEffectiveOpen) }}
|
||||
>
|
||||
<IconChevronDownOutline14 className={css.chevron} size={12} aria-hidden="true" />
|
||||
<span className={css.groupTitle}>{t('globalTitle')}</span>
|
||||
<span className={css.groupSubtitle}>{t('globalSubtitle')}</span>
|
||||
<span className={css.groupCount} data-plugin-count={globalCount}>{globalCount}</span>
|
||||
<div className={css.groupTitleRow}>
|
||||
<button
|
||||
type="button"
|
||||
className={css.groupToggle}
|
||||
aria-expanded={globalEffectiveOpen}
|
||||
aria-controls={`${sectionId}-global`}
|
||||
onClick={() => { setGlobalOpen(!globalEffectiveOpen) }}
|
||||
>
|
||||
<IconChevronDownOutline14 className={css.chevron} size={12} aria-hidden="true" />
|
||||
<span className={css.groupTitle}>{t('globalTitle')}</span>
|
||||
</button>
|
||||
</div>
|
||||
<p className={css.groupSub}>
|
||||
{t('globalSubtitle')}
|
||||
<span data-plugin-count={globalCount}>{` · ${String(globalCount)} ${t('countUnit')}`}</span>
|
||||
{filteredFailed.length > 0 ? (
|
||||
<span className={css.failedCount}>{filteredFailed.length} {t('failedCountLabel')}</span>
|
||||
) : null}
|
||||
</button>
|
||||
</p>
|
||||
{globalEffectiveOpen && globalCount > 0 ? (
|
||||
<ul className={css.cards} id={`${sectionId}-global`}>
|
||||
{filteredFailed.map(entry => globalRowCard(entry))}
|
||||
|
||||
@@ -9,11 +9,13 @@ export const zh = {
|
||||
search: '搜索插件',
|
||||
empty: '暂无插件。',
|
||||
emptySearch: '没有匹配的插件。',
|
||||
presetSubtitle: '会话使用的插件',
|
||||
presetTitle: '会话插件',
|
||||
presetSubtitle: '由 Agent 预设按会话组成',
|
||||
countUnit: '个',
|
||||
switcherLabel: '选择要查看的 Agent 预设',
|
||||
presetOptionDefault: '{name}(默认)',
|
||||
presetOptionBroken: '{name}(加载失败)',
|
||||
globalTitle: '全局',
|
||||
globalTitle: '全局插件',
|
||||
globalSubtitle: '系统与所有会话共用',
|
||||
presetProvidedDetail: '全局已停用,由 Agent 预设按会话提供',
|
||||
enabledIn: '启用于',
|
||||
@@ -50,11 +52,13 @@ export const en = {
|
||||
search: 'Search plugins',
|
||||
empty: 'No plugins are available.',
|
||||
emptySearch: 'No matching plugins.',
|
||||
presetSubtitle: 'Plugins your sessions run',
|
||||
presetTitle: 'Session plugins',
|
||||
presetSubtitle: 'Composed per session by agent presets',
|
||||
countUnit: 'plugins',
|
||||
switcherLabel: 'Choose the agent preset to inspect',
|
||||
presetOptionDefault: '{name} (default)',
|
||||
presetOptionBroken: '{name} (failed to load)',
|
||||
globalTitle: 'Global',
|
||||
globalTitle: 'Global plugins',
|
||||
globalSubtitle: 'Shared by the system and every session',
|
||||
presetProvidedDetail: 'Disabled globally; agent presets provide it per session',
|
||||
enabledIn: 'Enabled in',
|
||||
|
||||
@@ -92,7 +92,7 @@ describe('PluginInventorySettingsTab', () => {
|
||||
fireEvent.keyDown(document, { key: 'Escape' })
|
||||
expect(screen.queryAllByRole('menuitem')).toHaveLength(0)
|
||||
expect(screen.getByText(en.presetSubtitle)).toBeTruthy()
|
||||
expect(view.container.querySelector('[data-preset-plugin-count]')?.textContent).toBe('6')
|
||||
expect(view.container.querySelector('[data-preset-plugin-count]')?.getAttribute('data-preset-plugin-count')).toBe('6')
|
||||
|
||||
// Only the preset group lists rows while the global plane stays collapsed.
|
||||
expect(screen.getAllByRole('listitem')).toHaveLength(6)
|
||||
@@ -105,7 +105,7 @@ describe('PluginInventorySettingsTab', () => {
|
||||
expect(screen.queryByRole('img', { name: 'Not running' })).toBeNull()
|
||||
|
||||
expect(globalToggle().getAttribute('aria-expanded')).toBe('false')
|
||||
expect(view.container.querySelector('[data-plugin-count]')?.textContent).toBe('7')
|
||||
expect(view.container.querySelector('[data-plugin-count]')?.getAttribute('data-plugin-count')).toBe('7')
|
||||
expect(screen.getByText(`1 ${en.failedCountLabel}`)).toBeTruthy()
|
||||
|
||||
// A preset row expands into its provenance facts.
|
||||
@@ -172,25 +172,25 @@ describe('PluginInventorySettingsTab', () => {
|
||||
}
|
||||
|
||||
pickPreset('ptc')
|
||||
expect(view.container.querySelector('[data-preset-plugin-count]')?.textContent).toBe('3')
|
||||
expect(view.container.querySelector('[data-preset-plugin-count]')?.getAttribute('data-preset-plugin-count')).toBe('3')
|
||||
fireEvent.click(screen.getAllByRole('button', { name: 'tool-bash, Enabled' })[0]!)
|
||||
// An unnamed preset labels provenance by its id.
|
||||
expect(screen.getByText(en.fromPreset).nextElementSibling?.textContent).toBe('ptc')
|
||||
|
||||
pickPreset('坏预设 (failed to load)')
|
||||
expect(screen.getByRole('alert').textContent).toBe('the composition file is missing')
|
||||
expect(view.container.querySelector('[data-preset-plugin-count]')?.textContent).toBe('0')
|
||||
expect(view.container.querySelector('[data-preset-plugin-count]')?.getAttribute('data-preset-plugin-count')).toBe('0')
|
||||
})
|
||||
|
||||
it('collapses the preset group until a search forces it open', async () => {
|
||||
const view = await renderReady()
|
||||
const toggle = screen.getByRole('button', { name: en.presetSubtitle })
|
||||
const toggle = screen.getByRole('button', { name: en.presetTitle })
|
||||
|
||||
expect(toggle.getAttribute('aria-expanded')).toBe('true')
|
||||
fireEvent.click(toggle)
|
||||
expect(toggle.getAttribute('aria-expanded')).toBe('false')
|
||||
// The header keeps its count while the rows are folded away.
|
||||
expect(view.container.querySelector('[data-preset-plugin-count]')?.textContent).toBe('6')
|
||||
expect(view.container.querySelector('[data-preset-plugin-count]')?.getAttribute('data-preset-plugin-count')).toBe('6')
|
||||
expect(view.container.querySelectorAll('[data-plugin-scope="preset"] li')).toHaveLength(0)
|
||||
|
||||
fireEvent.change(screen.getByRole('searchbox', { name: en.search }), { target: { value: 'pwsh' } })
|
||||
@@ -221,8 +221,8 @@ describe('PluginInventorySettingsTab', () => {
|
||||
|
||||
fireEvent.change(search, { target: { value: 'tool-bash' } })
|
||||
// Searching forces the collapsed global plane and drawer open.
|
||||
expect(view.container.querySelector('[data-preset-plugin-count]')?.textContent).toBe('1')
|
||||
expect(view.container.querySelector('[data-plugin-count]')?.textContent).toBe('1')
|
||||
expect(view.container.querySelector('[data-preset-plugin-count]')?.getAttribute('data-preset-plugin-count')).toBe('1')
|
||||
expect(view.container.querySelector('[data-plugin-count]')?.getAttribute('data-plugin-count')).toBe('1')
|
||||
expect(screen.getByText(en.presetEnabledTag)).toBeTruthy()
|
||||
expect(screen.queryByText(`1 ${en.failedCountLabel}`)).toBeNull()
|
||||
const hint = screen.getByText((text: string) => text.startsWith('2 more matches'))
|
||||
@@ -232,13 +232,13 @@ describe('PluginInventorySettingsTab', () => {
|
||||
|
||||
// A match visible only in another preset keeps the pointer without rows.
|
||||
fireEvent.change(search, { target: { value: 'crashy' } })
|
||||
expect(view.container.querySelector('[data-preset-plugin-count]')?.textContent).toBe('0')
|
||||
expect(view.container.querySelector('[data-preset-plugin-count]')?.getAttribute('data-preset-plugin-count')).toBe('0')
|
||||
expect(screen.getByText((text: string) => text.startsWith('1 more matches'))).toBeTruthy()
|
||||
expect(screen.queryByText(en.emptySearch)).toBeNull()
|
||||
|
||||
// A match on a Loader entry id only reaches the global plane.
|
||||
fireEvent.change(search, { target: { value: '8a1b2c3d' } })
|
||||
expect(view.container.querySelector('[data-plugin-count]')?.textContent).toBe('1')
|
||||
expect(view.container.querySelector('[data-plugin-count]')?.getAttribute('data-plugin-count')).toBe('1')
|
||||
expect(screen.queryByText((text: string) => text.includes('more matches'))).toBeNull()
|
||||
|
||||
fireEvent.change(search, { target: { value: 'not-a-plugin' } })
|
||||
|
||||
Reference in New Issue
Block a user