diff --git a/src/renderer/src/components/hover-reveal-touch-action-visibility.test.ts b/src/renderer/src/components/hover-reveal-touch-action-visibility.test.ts index 30c7d53b17b..d47c278582a 100644 --- a/src/renderer/src/components/hover-reveal-touch-action-visibility.test.ts +++ b/src/renderer/src/components/hover-reveal-touch-action-visibility.test.ts @@ -17,6 +17,7 @@ const HOVER_REVEAL_FILES = [ resolve(__dirname, 'editor/DiffSectionHeader.tsx'), resolve(__dirname, 'github-project/ProjectPicker.tsx'), resolve(__dirname, 'github-project/ProjectRow.tsx'), + resolve(__dirname, 'native-chat/NativeChatMessageRow.tsx'), resolve(__dirname, 'right-sidebar/AiVaultSessionRow.tsx'), resolve(__dirname, 'right-sidebar/ChecksPanel.tsx'), resolve(__dirname, 'right-sidebar/local-port-row.tsx'), diff --git a/src/renderer/src/components/native-chat/NativeChatMessageList.test.tsx b/src/renderer/src/components/native-chat/NativeChatMessageList.test.tsx index bdfc80c5f77..144ce1e87ec 100644 --- a/src/renderer/src/components/native-chat/NativeChatMessageList.test.tsx +++ b/src/renderer/src/components/native-chat/NativeChatMessageList.test.tsx @@ -50,7 +50,7 @@ describe('NativeChatMessageList assistant messages', () => { const controls = copyButton.parentElement expect(row).toHaveClass('select-text') - expect(controls).toHaveClass('select-none', 'pointer-events-none', 'mt-1') + expect(controls).toHaveClass('select-none', 'can-hover:pointer-events-none', 'mt-1') expect(controls).not.toHaveClass('absolute') expect(prose.compareDocumentPosition(controls!)).toBe(Node.DOCUMENT_POSITION_FOLLOWING) }) diff --git a/src/renderer/src/components/native-chat/NativeChatMessageRow.test.tsx b/src/renderer/src/components/native-chat/NativeChatMessageRow.test.tsx index e74db58dd3f..b2a55abf154 100644 --- a/src/renderer/src/components/native-chat/NativeChatMessageRow.test.tsx +++ b/src/renderer/src/components/native-chat/NativeChatMessageRow.test.tsx @@ -23,7 +23,7 @@ function renderMessage(role: NativeChatMessage['role'], timestamp: number | null ) } -describe('MessageRow hover timestamps', () => { +describe('MessageRow control visibility', () => { it('appends time to the existing agent controls and inherits their reveal', () => { renderMessage('assistant') const copy = screen.getByRole('button', { name: 'Copy message' }) @@ -31,24 +31,32 @@ describe('MessageRow hover timestamps', () => { const time = screen.getByRole('time') expect(Array.from(copy.parentElement!.children)).toEqual([copy, scroll, time]) expect(copy.parentElement).toHaveClass( - 'opacity-0', + 'can-hover:opacity-0', + 'can-hover:pointer-events-none', 'group-hover:opacity-100', - 'group-focus-within:opacity-100' + 'group-has-[:focus-visible]:opacity-100', + 'group-hover:pointer-events-auto', + 'group-has-[:focus-visible]:pointer-events-auto' ) + expect(copy.parentElement).not.toHaveClass('opacity-0', 'pointer-events-none') expect(time).not.toHaveAttribute('tabindex') copy.focus() expect(copy).toHaveFocus() }) - it('gives user bubbles only a hover/focus timestamp', () => { + it('gives user bubbles a timestamp that only hides on hover-capable devices', () => { renderMessage('user') const time = screen.getByRole('time') expect(screen.queryByRole('button')).toBeNull() expect(time).toHaveClass( - 'opacity-0', + 'can-hover:opacity-0', + 'can-hover:pointer-events-none', 'group-hover:opacity-100', - 'group-focus-within:opacity-100' + 'group-has-[:focus-visible]:opacity-100', + 'group-hover:pointer-events-auto', + 'group-has-[:focus-visible]:pointer-events-auto' ) + expect(time).not.toHaveClass('opacity-0', 'pointer-events-none') expect(time.parentElement).toHaveClass('group') time.focus() expect(time).toHaveFocus() diff --git a/src/renderer/src/components/native-chat/NativeChatMessageRow.tsx b/src/renderer/src/components/native-chat/NativeChatMessageRow.tsx index e30b46e594d..8fe5f47261a 100644 --- a/src/renderer/src/components/native-chat/NativeChatMessageRow.tsx +++ b/src/renderer/src/components/native-chat/NativeChatMessageRow.tsx @@ -153,7 +153,7 @@ export const MessageRow = memo(function MessageRow({ {deliveryFailed ? (
@@ -168,7 +168,7 @@ export const MessageRow = memo(function MessageRow({ } // Plain assistant prose is the copyable unit; reasoning/system asides stay - // chrome-free. The controls reveal on hover (and on keyboard focus-within). + // chrome-free. Controls reveal on hover/keyboard focus and stay visible on touch. const showControls = !isReasoning && !isSystem && markdown.length > 0 return ( @@ -212,7 +212,7 @@ export const MessageRow = memo(function MessageRow({ markdown={markdown} timestamp={message.timestamp} onScrollToTop={scrollToTop} - className="pointer-events-none mt-1 -mb-5 w-fit select-none opacity-0 transition-opacity group-hover:pointer-events-auto group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:opacity-100" + className="mt-1 -mb-5 w-fit select-none transition-opacity can-hover:pointer-events-none can-hover:opacity-0 group-hover:pointer-events-auto group-hover:opacity-100 group-has-[:focus-visible]:pointer-events-auto group-has-[:focus-visible]:opacity-100" /> ) : null}