diff --git a/packages/client/ui-agent-preset/src/client/PresetMenu.tsx b/packages/client/ui-agent-preset/src/client/PresetMenu.tsx index 1049ee6bc2..2a6bc6ea28 100644 --- a/packages/client/ui-agent-preset/src/client/PresetMenu.tsx +++ b/packages/client/ui-agent-preset/src/client/PresetMenu.tsx @@ -28,8 +28,6 @@ export interface PresetMenuProps { chevronClassName: string | undefined /** Whether the trigger refuses interaction. */ disabled: boolean - /** Native tooltip, absent where the surface offers none. */ - title?: string /** Whether the menu is open — the surface owns this so it can force it shut. */ open: boolean /** Report the menu's next open state. */ @@ -45,7 +43,7 @@ export interface PresetMenuProps { */ export function PresetMenu({ options, selectedId, label, userTrustLabel, buttonClassName, chevronClassName, - disabled, title, open, onOpenChange, onSelect, + disabled, open, onOpenChange, onSelect, }: PresetMenuProps) { return ( { onOpenChange(!open) }} > diff --git a/packages/client/ui-agent-preset/tests/apply.spec.ts b/packages/client/ui-agent-preset/tests/apply.spec.ts index 5e23b889f7..c263d96417 100644 --- a/packages/client/ui-agent-preset/tests/apply.spec.ts +++ b/packages/client/ui-agent-preset/tests/apply.spec.ts @@ -300,7 +300,13 @@ describe('ui-agent-preset apply', () => { // lives on another screen: without this the next session — the very one // the setting governs — would be composed from the previous default until // a reload. + // An unrelated namespace moves nothing: the chip re-reads on its own + // setting, not on every settings write in the process. moveDefault() + ctx.emit('settings/changed', 'llm-deepseek') + await Promise.resolve() + expect(seat.hooks.agentPresetSeat.getSnapshot().current).toBe('standard') + ctx.emit('settings/changed', 'agent-presets') await vi.waitFor(() => { expect(seat.hooks.agentPresetSeat.getSnapshot().current).toBe('minimal') diff --git a/packages/client/ui-agent-preset/tests/components.spec.tsx b/packages/client/ui-agent-preset/tests/components.spec.tsx index c0d1bf45b1..7bc3d59e04 100644 --- a/packages/client/ui-agent-preset/tests/components.spec.tsx +++ b/packages/client/ui-agent-preset/tests/components.spec.tsx @@ -105,6 +105,29 @@ describe('the General-settings row', () => { expect(screen.getAllByText('标准模式')).toHaveLength(2) }) + it('falls back to the id for a preset that published no name', () => { + renderRow({ + currentValue: 'mine', + options: [ + { id: 'standard', trust: 'system', name: '标准模式' }, + { id: 'bare', trust: 'system' }, + { id: 'mine', trust: 'user' }, + { id: 'ours', trust: 'user', name: '团队模式' }, + ], + }) + + // The trigger names the preset; with no metadata the id is all there is. + expect(screen.getByRole('button').textContent).toContain('mine') + + fireEvent.click(screen.getByRole('button')) + + // A locally authored preset is marked whether or not it named itself. + expect(screen.getByText(`团队模式 · ${en.userTrust}`)).toBeTruthy() + expect(screen.getByText(`mine · ${en.userTrust}`)).toBeTruthy() + // A shipped preset with no metadata is listed by id and carries no mark. + expect(screen.getByText('bare')).toBeTruthy() + }) + it('writes the picked preset and closes the menu', () => { const actions = renderRow() fireEvent.click(screen.getByRole('button')) diff --git a/packages/client/ui-agent-preset/tests/section.spec.tsx b/packages/client/ui-agent-preset/tests/section.spec.tsx index bedfcb3ded..b710951b3d 100644 --- a/packages/client/ui-agent-preset/tests/section.spec.tsx +++ b/packages/client/ui-agent-preset/tests/section.spec.tsx @@ -200,6 +200,16 @@ describe('the composition editor', () => { writable: true, name: '我的预设', description: '', saving: false, error: null, } + it('opens a blank draft without naming a preset it came from', () => { + const { source: _copied, ...blank } = draft + renderSection({ draft: { ...blank, id: '', creating: true } }) + + // "New preset" starts empty — copying is what the per-row Duplicate does, + // so a blank draft has no source to name and shows no copied-from hint. + expect(screen.getByText(en.newPreset)).toBeTruthy() + expect(screen.queryByText(new RegExp(en.copyOf))).toBeNull() + }) + it('replaces the list while editing, and returns to it', () => { const actions = renderSection({ draft }) diff --git a/packages/client/ui-agent-preset/tests/settings-store.spec.ts b/packages/client/ui-agent-preset/tests/settings-store.spec.ts index d497eda221..b7b3265607 100644 --- a/packages/client/ui-agent-preset/tests/settings-store.spec.ts +++ b/packages/client/ui-agent-preset/tests/settings-store.spec.ts @@ -419,4 +419,26 @@ describe('the new-session chip controller', () => { expect(controller.store.getSnapshot().error).toBe('socket closed') }) + + it('reports a refused describe as a failure rather than a half-read row', async () => { + const api = { + agentPresets: { + list: () => Promise.resolve({ + rpcId: 'r', + result: { ok: true as const, value: { presets: [{ id: 'standard', trust: 'system', isDefault: true }], authorable: true } }, + }), + }, + // The roster answered; `settings.describe` is what rejected, and the row + // cannot claim a writable default it never confirmed. + settings: { describe: () => Promise.reject(new Error('socket closed')) }, + } as unknown as IApiClient + const controller = new AgentPresetSettingsController(api) + + await controller.load() + + expect(controller.store.getSnapshot().status).toBe('error') + expect(controller.store.getSnapshot().error).toBe('socket closed') + }) + + })