mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user