feat(web): make the preset card the control that picks it

Four changes to the Agent 预设 section, all from the same reading: choosing
a preset is the common act and everything else is occasional.

Picking is now clicking the card rather than hunting a small "Set as
default" button. The card body is the button; the action row sits outside it
because nesting buttons is invalid and those actions operate ON a card
rather than select it. The body's accessible name is set explicitly —
otherwise it is the whole card read aloud, title through id.

Actions are icon-only with their label on hover and on `aria-label`, so four
controls stop competing with the content for attention.

Built-in and custom presets are separated under headings, and the trust
badge moved up beside the title where it qualifies the name. A group with no
members renders no heading.

`Local` reads as a location; these are the user's own, so the badge and the
heading both say `Custom` / 自定义.
This commit is contained in:
Yichen Jiang
2026-08-05 16:51:13 +08:00
parent 2886c6391b
commit 4f41608cbc
4 changed files with 204 additions and 121 deletions

View File

@@ -98,35 +98,65 @@ describe('the preset list', () => {
expect(within(rowFor('mine')).getByText(en.userTrust)).toBeTruthy()
})
it('separates built-in presets from custom ones', () => {
renderSection()
// Two different things: one set ships with the deployment and is
// read-only, the other is the user's own.
expect(screen.getByRole('heading', { name: en.builtInGroup })).toBeTruthy()
expect(screen.getByRole('heading', { name: en.customGroup })).toBeTruthy()
expect(within(rowFor('standard')).getByText(en.builtIn)).toBeTruthy()
expect(within(rowFor('mine')).getByText(en.userTrust)).toBeTruthy()
})
it('shows no group heading for a set nobody has', () => {
renderSection({ rows: [{ id: 'standard', trust: 'system', isDefault: true }] })
expect(screen.queryByRole('heading', { name: en.customGroup })).toBeNull()
})
it('picks a preset by clicking its card, and the one in use is inert', () => {
const actions = renderSection()
const inUse = within(rowFor('standard')).getByRole('button', { name: `${en.inUse}: 标准模式` })
expect(inUse).toHaveProperty('disabled', true)
fireEvent.click(inUse)
// Clicking the card IS the choice; the preset already in use cannot be
// re-picked, so the click reaches nothing.
expect(actions.makeDefault).not.toHaveBeenCalled()
})
it('offers Edit for a local preset and View for a shipped one', () => {
renderSection()
expect(within(rowFor('mine')).getByText(en.edit)).toBeTruthy()
expect(within(rowFor('mine')).getByRole('button', { name: en.edit })).toBeTruthy()
// A shipped composition is readable but not editable, and the label is
// what says so before the editor opens.
expect(within(rowFor('standard')).getByText(en.view)).toBeTruthy()
expect(within(rowFor('standard')).getByRole('button', { name: en.view })).toBeTruthy()
})
it('offers Delete only for a locally authored preset', () => {
renderSection()
expect(within(rowFor('mine')).getByText(en.delete)).toBeTruthy()
expect(within(rowFor('standard')).queryByText(en.delete)).toBeNull()
expect(within(rowFor('mine')).getByRole('button', { name: en.delete })).toBeTruthy()
expect(within(rowFor('standard')).queryByRole('button', { name: en.delete })).toBeNull()
})
it('hides duplication and disables creation when nothing is writable', () => {
renderSection({ authorable: false })
expect(screen.queryByText(en.duplicate)).toBeNull()
expect(screen.queryByRole('button', { name: en.duplicate })).toBeNull()
expect(screen.getByText(`+ ${en.newPreset}`)).toHaveProperty('disabled', true)
})
it('routes the row actions to the controller', () => {
const actions = renderSection()
fireEvent.click(within(rowFor('mine')).getByText(en.setDefault))
fireEvent.click(within(rowFor('mine')).getByText(en.edit))
fireEvent.click(within(rowFor('mine')).getByText(en.duplicate))
// The card body is the control that picks a preset.
fireEvent.click(within(rowFor('mine')).getByRole('button', { name: `${en.setDefault}: mine` }))
fireEvent.click(within(rowFor('mine')).getByRole('button', { name: en.edit }))
fireEvent.click(within(rowFor('mine')).getByRole('button', { name: en.duplicate }))
fireEvent.click(screen.getByText(`+ ${en.newPreset}`))
expect(actions.makeDefault).toHaveBeenCalledWith('mine')
@@ -259,7 +289,7 @@ describe('deleting a preset', () => {
it('asks before deleting', () => {
const actions = renderSection()
fireEvent.click(within(rowFor('mine')).getByText(en.delete))
fireEvent.click(within(rowFor('mine')).getByRole('button', { name: en.delete }))
expect(actions.confirmDelete).toHaveBeenCalledWith('mine')
})