fix(web): extend hover-reveal info affordance to touch devices (#1046)

MessageInfoPopover's trigger button carried the same
happy-message-actions-desktop-only class as the other hover-reveal
actions from 4c76668a, so it never rendered on touch-only viewports
(no hover: hover match). Switch it to the always-visible flex pattern
the sibling copy button already uses, matching desktop's hover-reveal
opacity animation on the parent row.

Also widen the action row's desktop-only-row guard to stay reachable
when a tool-only response (no copyable text) still carries model/
duration metadata from its first tool block, so the info popover isn't
hidden behind an empty row on mobile.
This commit is contained in:
Junmo Kim
2026-07-16 12:33:51 +08:00
committed by GitHub
parent 89df3fd5f2
commit adb6f41858
3 changed files with 52 additions and 17 deletions
@@ -67,4 +67,53 @@ describe('MessageActions', () => {
expect(screen.queryByRole('button', { name: 'Message details' })).toBeNull()
})
it('keeps the info button reachable on touch devices (no hover-only class)', () => {
renderActions({
align: 'start',
copyText: 'message body',
metadata: { durationMs: 1250, model: 'gpt-5.2-codex' }
})
const button = screen.getByRole('button', { name: 'Message details' })
expect(button.className.split(' ')).not.toContain('happy-message-actions-desktop-only')
})
it('keeps the action row reachable on touch devices for tool-only messages with metadata', () => {
// Tool-only assistant turns (no trailing text) have no copyText, but
// can still carry model/duration metadata from the first tool block
// in the response group (see assistant-runtime.ts toThreadMessageLike).
renderActions({
align: 'start',
copyText: undefined,
metadata: { durationMs: 1250, model: 'gpt-5.2-codex' }
})
const button = screen.getByRole('button', { name: 'Message details' })
const row = button.closest('.happy-message-actions')
expect(row).not.toBeNull()
expect(row!.className.split(' ')).not.toContain('happy-message-actions-desktop-only-row')
})
it('keeps the timestamp reachable on touch devices (no hover-only class)', () => {
renderActions({ align: 'start', copyText: 'message body' })
const time = document.querySelector('time')
expect(time).not.toBeNull()
const wrapper = time!.parentElement!
expect(wrapper.className.split(' ')).not.toContain('happy-message-actions-desktop-only')
})
it('keeps the row reachable on touch devices even with neither copy text nor metadata (timestamp-only row)', () => {
// DesktopTimestamp always renders inside the row regardless of
// canCopy/hasMetadata, so the row is never actually empty -- hiding
// it via the hover-only row class would hide a real timestamp.
renderActions({ align: 'end', copyText: undefined, metadata: undefined })
const time = document.querySelector('time')
expect(time).not.toBeNull()
const row = time!.closest('.happy-message-actions')
expect(row).not.toBeNull()
expect(row!.className.split(' ')).not.toContain('happy-message-actions-desktop-only-row')
})
})
@@ -22,8 +22,7 @@ export function MessageActions({ align, copyText, metadata }: MessageActionsProp
<div
className={cn(
'happy-message-actions mt-1 flex h-5 items-center gap-1',
align === 'end' ? 'justify-end' : 'justify-start',
!canCopy && 'happy-message-actions-desktop-only-row'
align === 'end' ? 'justify-end' : 'justify-start'
)}
>
{align === 'end' ? <DesktopTimestamp /> : null}
@@ -46,7 +45,7 @@ export function MessageActions({ align, copyText, metadata }: MessageActionsProp
function DesktopTimestamp() {
return (
<span className="happy-message-actions-desktop-only ml-1 items-center">
<span className="inline-flex ml-1 items-center">
<MessageTimestamp className="text-[10px] leading-none text-[var(--app-hint)]" />
</span>
)
@@ -61,7 +60,7 @@ function MessageInfoPopover({ metadata }: { metadata: Omit<MessageMetadataProps,
type="button"
title={t('message.info')}
aria-label={t('message.info')}
className="happy-message-actions-desktop-only h-5 w-5 items-center justify-center rounded text-[var(--app-hint)] transition-colors hover:bg-[var(--app-subtle-bg)] hover:text-[var(--app-fg)]"
className="flex h-5 w-5 items-center justify-center rounded text-[var(--app-hint)] transition-colors hover:bg-[var(--app-subtle-bg)] hover:text-[var(--app-fg)]"
>
<InfoIcon className="h-3.5 w-3.5" />
</button>
-13
View File
@@ -433,11 +433,6 @@ body {
margin-top: max(0px, calc((var(--app-chat-line-height) - var(--app-message-action-size)) / 2));
}
.happy-message-actions-desktop-only,
.happy-message-actions-desktop-only-row {
display: none;
}
@media (hover: hover) and (pointer: fine) {
.happy-message-actions {
opacity: 0;
@@ -450,14 +445,6 @@ body {
opacity: 1;
pointer-events: auto;
}
.happy-message-actions-desktop-only {
display: inline-flex;
}
.happy-message-actions-desktop-only-row {
display: flex;
}
}
.aui-md :where(.contains-task-list) {