fix(desktop): show composer action shortcuts (#69707)

Expose the existing platform-aware keybind hints on Send, Steer, and Queue
composer control tooltips. Add focused tooltip coverage for each action.
This commit is contained in:
ethernet 2026-07-22 21:00:50 -04:00 committed by GitHub
parent fae3ba2c44
commit deadb43cc2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 90 additions and 3 deletions

View file

@ -0,0 +1,79 @@
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { ChatBarState } from '@/app/chat/composer/types'
import { I18nProvider } from '@/i18n'
import { ComposerControls } from './controls'
vi.mock('./model-pill', () => ({ ModelPill: () => null }))
const state: ChatBarState = {
model: { canSwitch: false, model: '', provider: '' },
tools: { enabled: false, label: '' },
voice: { active: false, enabled: false }
}
function renderControls(overrides: Partial<React.ComponentProps<typeof ComposerControls>> = {}) {
return render(
<I18nProvider configClient={null} initialLocale="en">
<ComposerControls
autoSpeak={false}
busy={false}
busyAction="stop"
canSubmit={true}
conversation={{
active: false,
level: 0,
muted: false,
onEnd: vi.fn(),
onStart: vi.fn(),
onStopTurn: vi.fn(),
onToggleMute: vi.fn(),
status: 'idle'
}}
disabled={false}
hasComposerPayload={true}
onDictate={vi.fn()}
onQueue={vi.fn()}
onToggleAutoSpeak={vi.fn()}
state={state}
voiceStatus="idle"
{...overrides}
/>
</I18nProvider>
)
}
async function expectShortcutTooltip(label: string, shortcut: string) {
fireEvent.pointerMove(screen.getByLabelText(label), { pointerType: 'mouse' })
const tooltip = await screen.findByRole('tooltip')
expect(tooltip.textContent).toContain(label)
expect(tooltip.textContent).toContain(shortcut)
}
afterEach(() => {
cleanup()
})
describe('ComposerControls shortcut tooltips', () => {
it('shows Enter for Send', async () => {
renderControls()
await expectShortcutTooltip('Send', '↵')
})
it('shows Ctrl+Enter for Steer', async () => {
renderControls({ busy: true, busyAction: 'steer' })
await expectShortcutTooltip('Steer the current run', 'Ctrl+↵')
})
it('shows Enter for Queue', async () => {
renderControls({ busy: true, busyAction: 'queue' })
await expectShortcutTooltip('Queue message', '↵')
})
})

View file

@ -1,6 +1,6 @@
import { Button } from '@/components/ui/button'
import { Codicon } from '@/components/ui/codicon'
import { Tip } from '@/components/ui/tooltip'
import { Tip, TipKeybindLabel } from '@/components/ui/tooltip'
import { useI18n } from '@/i18n'
import { triggerHaptic } from '@/lib/haptics'
import { AudioLines, iconSize, Layers3, Loader2, Square, SteeringWheel, Volume2, VolumeX } from '@/lib/icons'
@ -81,7 +81,7 @@ export function ComposerControls({
<DictationButton disabled={disabled} onToggle={onDictate} state={state.voice} status={voiceStatus} />
<AutoSpeakButton active={autoSpeak} disabled={disabled} onToggle={onToggleAutoSpeak} />
{busyAction === 'steer' ? (
<Tip label={c.queueMessage}>
<Tip label={<TipKeybindLabel actionId="composer.send" text={c.queueMessage} />}>
<Button
aria-label={c.queueMessage}
className={GHOST_ICON_BTN}
@ -112,7 +112,15 @@ export function ComposerControls({
</Button>
</Tip>
) : (
<Tip label={busy ? busyLabel : c.send}>
<Tip
label={
busy ? (
<TipKeybindLabel actionId={busyAction === 'steer' ? 'composer.steer' : 'composer.send'} text={busyLabel} />
) : (
<TipKeybindLabel actionId="composer.send" text={c.send} />
)
}
>
<Button
aria-label={busy ? busyLabel : c.send}
className={PRIMARY_ICON_BTN}