feat(new-workspace): set selected agent as default

This commit is contained in:
Neil
2026-07-29 17:27:13 -07:00
parent b339fe0346
commit d153b1bb0c
2 changed files with 156 additions and 51 deletions
@@ -1,11 +1,93 @@
// @vitest-environment happy-dom
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { renderToStaticMarkup } from 'react-dom/server'
import { describe, expect, it, vi } from 'vitest'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { TuiAgent } from '../../../../shared/types'
import { AGENT_CATALOG, AgentIcon } from '@/lib/agent-catalog'
import { AGENT_FAVICON_ASSETS } from '@/lib/agent-favicon-assets'
import AgentCombobox from './AgentCombobox'
afterEach(cleanup)
describe('AgentCombobox', () => {
it('sets the closed trigger selection as the default agent', () => {
const onSetDefault = vi.fn()
render(
<AgentCombobox
agents={AGENT_CATALOG}
value="codex"
onValueChange={vi.fn()}
defaultAgent="claude"
onSetDefault={onSetDefault}
/>
)
const trigger = screen.getByRole('combobox')
fireEvent.contextMenu(trigger)
expect(trigger.getAttribute('aria-expanded')).toBe('false')
fireEvent.click(screen.getByRole('menuitem', { name: 'Set as default' }))
expect(onSetDefault).toHaveBeenCalledWith('codex')
})
it('maps the closed Blank Terminal selection to the blank default preference', () => {
const onSetDefault = vi.fn()
render(
<AgentCombobox
agents={AGENT_CATALOG}
value={null}
onValueChange={vi.fn()}
defaultAgent="codex"
onSetDefault={onSetDefault}
/>
)
fireEvent.contextMenu(screen.getByRole('combobox'))
fireEvent.click(screen.getByRole('menuitem', { name: 'Set as default' }))
expect(onSetDefault).toHaveBeenCalledWith('blank')
})
it('marks the closed trigger when its selection is already the default', () => {
const onSetDefault = vi.fn()
render(
<AgentCombobox
agents={AGENT_CATALOG}
value="codex"
onValueChange={vi.fn()}
defaultAgent="codex"
onSetDefault={onSetDefault}
/>
)
fireEvent.contextMenu(screen.getByRole('combobox'))
const menuItem = screen.getByRole('menuitem', { name: 'Current default' })
expect(menuItem.hasAttribute('data-disabled')).toBe(true)
fireEvent.click(menuItem)
expect(onSetDefault).not.toHaveBeenCalled()
})
it('keeps the expanded agent-row default action available', () => {
const onSetDefault = vi.fn()
render(
<AgentCombobox
agents={AGENT_CATALOG}
value="codex"
onValueChange={vi.fn()}
defaultAgent="codex"
onSetDefault={onSetDefault}
/>
)
fireEvent.click(screen.getByRole('combobox'))
fireEvent.contextMenu(screen.getByRole('option', { name: 'Claude' }))
fireEvent.click(screen.getByRole('menuitem', { name: 'Set as default' }))
expect(onSetDefault).toHaveBeenCalledWith('claude')
})
it('keeps enough trigger width for GitHub Copilot when callers pass min-w-0', () => {
const markup = renderToStaticMarkup(
<AgentCombobox
@@ -43,7 +43,7 @@ type AgentComboboxProps = {
* currently-applied choice. */
defaultAgent?: DefaultAgentPreference
/** Optional handler for right-click "Set as default" action. When provided,
* each list item (including Blank Terminal) gets a context menu. */
* the selected trigger and each list item get a context menu. */
onSetDefault?: (agent: DefaultAgentPreference) => void
triggerClassName?: string
/** When set, pressing Enter on the closed combobox trigger invokes this
@@ -69,6 +69,12 @@ type ItemRenderArgs = {
label: string
}
type AgentDefaultContextMenuProps = {
children: React.ReactNode
isDefault: boolean
onSetDefault?: () => void
}
function AgentIconLabel({
icon,
label
@@ -86,6 +92,29 @@ function AgentIconLabel({
)
}
function AgentDefaultContextMenu({
children,
isDefault,
onSetDefault
}: AgentDefaultContextMenuProps): React.ReactNode {
if (!onSetDefault) {
return children
}
return (
<ContextMenu>
<ContextMenuTrigger asChild>{children}</ContextMenuTrigger>
<ContextMenuContent className="z-[70]">
<ContextMenuItem onSelect={onSetDefault} disabled={isDefault}>
<Star className="size-3.5" />
{isDefault
? translate('auto.components.agent.AgentCombobox.1b0d6965fa', 'Current default')
: translate('auto.components.agent.AgentCombobox.9c6b59fe58', 'Set as default')}
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
)
}
function renderItem({
key,
itemValue,
@@ -109,23 +138,12 @@ function renderItem({
<AgentIconLabel icon={icon} label={label} />
</CommandItem>
)
if (!onSetDefault) {
return row
}
return (
// Why: z-[70] sits above PopoverContent's z-[60] so the right-click menu
// renders in front of the still-open combobox popover instead of behind it.
<ContextMenu key={key}>
<ContextMenuTrigger asChild>{row}</ContextMenuTrigger>
<ContextMenuContent className="z-[70]">
<ContextMenuItem onSelect={onSetDefault} disabled={isDefault}>
<Star className="size-3.5" />
{isDefault
? translate('auto.components.agent.AgentCombobox.1b0d6965fa', 'Current default')
: translate('auto.components.agent.AgentCombobox.9c6b59fe58', 'Set as default')}
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
<AgentDefaultContextMenu key={key} isDefault={isDefault} onSetDefault={onSetDefault}>
{row}
</AgentDefaultContextMenu>
)
}
@@ -288,41 +306,46 @@ export default function AgentCombobox({
// trigger free to overflow its dialog column and look misaligned with Project/Name.
<div className="min-w-0 w-full">
<Popover open={open} onOpenChange={handleOpenChange}>
<PopoverTrigger asChild>
<Button
ref={triggerRef}
type="button"
variant="outline"
role="combobox"
aria-expanded={open}
onKeyDown={handleTriggerKeyDown}
className={cn(
// Why: callers sometimes pass `min-w-0` for grid layouts, but
// the compact trigger still needs room for "GitHub Copilot".
// py-0 clears the default size's py-2 so icon+label center in h-8/h-9.
'h-8 justify-between px-3 py-0 text-xs font-normal',
triggerClassName,
!allowNarrowTrigger && TRIGGER_MIN_WIDTH_CLASS
)}
data-agent-combobox-root="true"
>
{selectedAgent ? (
<AgentIconLabel
icon={<AgentIcon agent={selectedAgent.id} size={14} />}
label={selectedAgent.label}
/>
) : (
<AgentIconLabel
icon={<Terminal className="size-3.5" />}
label={
emptyLabel ??
translate('auto.components.agent.AgentCombobox.986f946354', 'Blank Terminal')
}
/>
)}
<ChevronsUpDown className="size-3.5 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<AgentDefaultContextMenu
isDefault={defaultAgent === (value ?? 'blank')}
onSetDefault={onSetDefault ? () => onSetDefault(value ?? 'blank') : undefined}
>
<PopoverTrigger asChild>
<Button
ref={triggerRef}
type="button"
variant="outline"
role="combobox"
aria-expanded={open}
onKeyDown={handleTriggerKeyDown}
className={cn(
// Why: callers sometimes pass `min-w-0` for grid layouts, but
// the compact trigger still needs room for "GitHub Copilot".
// py-0 clears the default size's py-2 so icon+label center in h-8/h-9.
'h-8 justify-between px-3 py-0 text-xs font-normal',
triggerClassName,
!allowNarrowTrigger && TRIGGER_MIN_WIDTH_CLASS
)}
data-agent-combobox-root="true"
>
{selectedAgent ? (
<AgentIconLabel
icon={<AgentIcon agent={selectedAgent.id} size={14} />}
label={selectedAgent.label}
/>
) : (
<AgentIconLabel
icon={<Terminal className="size-3.5" />}
label={
emptyLabel ??
translate('auto.components.agent.AgentCombobox.986f946354', 'Blank Terminal')
}
/>
)}
<ChevronsUpDown className="size-3.5 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
</AgentDefaultContextMenu>
<PopoverContent
align="start"
className={cn(