fix: preserve ai panel delete shortcuts

This commit is contained in:
lucaronin
2026-05-02 18:12:37 +02:00
parent 19b31cc96a
commit 20f34ab9d1
4 changed files with 87 additions and 7 deletions

View File

@@ -0,0 +1,55 @@
import { useState } from 'react'
import { describe, expect, it, vi } from 'vitest'
import { fireEvent, render, screen } from '@testing-library/react'
import { AiPanelMessageHistory } from './AiPanelChrome'
const aiMessageRender = vi.hoisted(() => vi.fn())
vi.mock('./AiMessage', () => ({
AiMessage: (props: { id?: string; response: string }) => {
aiMessageRender(props.id)
return <div data-testid="ai-message">{props.response}</div>
},
}))
const noop = () => {}
function HistoryRerenderHarness() {
const [draft, setDraft] = useState('')
const [messages] = useState([{
userMessage: 'Explain the note',
actions: [],
response: 'Here is a long answer with [[Test Note]].',
id: 'msg-stable',
}])
return (
<>
<button type="button" onClick={() => setDraft('typing')}>type</button>
<span data-testid="draft">{draft}</span>
<AiPanelMessageHistory
agentLabel="Claude Code"
agentReadiness="ready"
messages={messages}
isActive={false}
onOpenNote={noop}
onNavigateWikilink={noop}
hasContext
/>
</>
)
}
describe('AiPanelChrome performance', () => {
it('keeps stable message history from re-rendering while composer state changes', () => {
render(<HistoryRerenderHarness />)
expect(screen.getByTestId('ai-message')).toHaveTextContent('Here is a long answer')
expect(aiMessageRender).toHaveBeenCalledTimes(1)
aiMessageRender.mockClear()
fireEvent.click(screen.getByRole('button', { name: 'type' }))
expect(screen.getByTestId('draft')).toHaveTextContent('typing')
expect(aiMessageRender).not.toHaveBeenCalled()
})
})

View File

@@ -1,4 +1,4 @@
import { useEffect, useRef } from 'react'
import { memo, useEffect, useRef } from 'react'
import { Robot, X, PaperPlaneRight, Plus, Link } from '@phosphor-icons/react'
import { AiMessage } from './AiMessage'
import { Button } from '@/components/ui/button'
@@ -164,7 +164,7 @@ function AiPanelEmptyState({
)
}
export function AiPanelHeader({
export const AiPanelHeader = memo(function AiPanelHeader({
agentLabel,
agentReadiness,
locale = 'en',
@@ -221,7 +221,7 @@ export function AiPanelHeader({
/>
</div>
)
}
})
function AiPermissionModeToggle({
value,
@@ -277,7 +277,11 @@ function AiPermissionModeToggle({
)
}
export function AiPanelContextBar({ activeEntry, linkedCount, locale = 'en' }: AiPanelContextBarProps) {
export const AiPanelContextBar = memo(function AiPanelContextBar({
activeEntry,
linkedCount,
locale = 'en',
}: AiPanelContextBarProps) {
const t = createTranslator(locale)
return (
@@ -293,9 +297,9 @@ export function AiPanelContextBar({ activeEntry, linkedCount, locale = 'en' }: A
)}
</div>
)
}
})
export function AiPanelMessageHistory({
export const AiPanelMessageHistory = memo(function AiPanelMessageHistory({
agentLabel,
agentReadiness,
locale = 'en',
@@ -332,7 +336,7 @@ export function AiPanelMessageHistory({
<div ref={endRef} />
</div>
)
}
})
export function AiPanelComposer({
entries,

View File

@@ -437,6 +437,26 @@ describe('WikilinkChatInput', () => {
expect(screen.queryByTestId('inline-wikilink-chip')).toBeNull()
})
it('lets native modified delete shortcuts reach the browser editor pipeline', () => {
const onDraftChange = vi.fn()
render(<Controlled onDraftChange={onDraftChange} />)
updateEditorText('delete the previous words')
onDraftChange.mockClear()
const editor = screen.getByTestId('agent-input')
const optionBackspace = createEvent.keyDown(editor, { key: 'Backspace', altKey: true })
fireEvent(editor, optionBackspace)
const commandBackspace = createEvent.keyDown(editor, { key: 'Backspace', metaKey: true })
fireEvent(editor, commandBackspace)
const controlDelete = createEvent.keyDown(editor, { key: 'Delete', ctrlKey: true })
fireEvent(editor, controlDelete)
expect(optionBackspace.defaultPrevented).toBe(false)
expect(commandBackspace.defaultPrevented).toBe(false)
expect(controlDelete.defaultPrevented).toBe(false)
expect(onDraftChange).not.toHaveBeenCalled()
})
it('submits serialized wikilink text and resolved references', () => {
const onSend = vi.fn()
render(<Controlled onSend={onSend} />)

View File

@@ -61,6 +61,7 @@ function handleDeleteKeys({
onDeleteContent,
}: HandleDeleteKeysArgs): boolean {
if (isInlineWikilinkCompositionEvent(event, isComposing)) return false
if (event.altKey || event.ctrlKey || event.metaKey) return false
if (event.key === 'Backspace') {
event.preventDefault()