LibreChat/client
Marco Beretta df5abbb377
🖱️ fix: Reveal Message Metadata on Hover, Not on Click (#14900)
* fix: reveal message metadata on hover, not on click

The message timestamp, the provider/model label crossfade, and the hover
action toolbar all revealed on `:focus-within` over the message row. A mouse
click sets focus, so clicking a tool card, an expand toggle, or a code block
button parked focus inside the row and pinned all three open long after the
pointer had left.

Key the focus half of each reveal on `:focus-visible` instead. A pointer
click no longer counts, while keyboard focus still does, so a sighted
keyboard user still reaches the model name and the timestamp by tabbing. An
action that opens a surface keeps the toolbar up through `hover-button-active`
as before.

* fix: fade the message row reveal instead of snapping it

The footer actions carried no opacity transition at all, so they arrived in a
single frame while the timestamp eased in behind them over 200ms and the
provider/model crossfade ran on the 300ms card-resize spring it had borrowed.
One hover, three different arrivals.

Put all three on the shared `duration-theme-normal` motion role with a common
ease-out, and add the reduced-motion guard the timestamp and the footer were
missing. `MinimalHoverButtons` now composes the shared reveal helper rather
than repeating its classes inline.

The reveal transition names `color` and `background-color` alongside `opacity`
because `cn` merges the whole `transition-*` group: a bare `transition-opacity`
would replace the `transition-colors` a `Button` contributes and the hover tint
would snap.

* fix: widen the message row keyboard-focus test

Two gaps in the `:focus-visible` reveal, both raised in review.

`:has()` never matches its own subject, so keying the reveal on
`:has(:focus-visible)` missed the row element itself. `MessageNav` moves the
reader by setting `tabindex="-1"` on the row and focusing it, which left a
focused row showing its focus ring while its timestamp, its model name and its
actions all stayed hidden.

Text-entry controls match `:focus-visible` even when a mouse clicks them, so a
click into the textareas `ToolApproval` and `AskUserQuestion` render inside a
row still pinned that row's metadata open with the pointer somewhere else. They
are excluded from the descendant half of the test. Every toolbar action is a
button, so a keyboard user still never focuses a hidden one.

Both halves are now one condition,
`:is(:focus-visible, :has(:focus-visible:not(:is(input, textarea, [contenteditable]))))`,
applied to the timestamp, the header label and the footer actions alike.

* fix: split the row focus test into two variants

Folding the row-itself and descendant halves into a single
`group-[&:is(...)]` made Tailwind emit a bare `.group$ { opacity: 1 }`, which
lightningcss refuses to minify. That failed the client CSS build and every job
downstream of it while jest and tsc stayed green, because neither ever builds
the stylesheet.

The condition is unchanged in behaviour, expressed as `group-focus-visible`
plus `group-has-[:focus-visible:not(:is(input,textarea,[contenteditable]))]`.
The plain CSS in style.css keeps the `:is()` form, which is valid there.

The stale string also had to come out of the specs: tailwind scans
`src/**/*.{ts,tsx}`, so a class literal in a test file reaches the production
stylesheet.

* fix: split the timestamp focus selector too

`:has()` nested inside `:is()` made postcss log "Failed to parse selector" on
every client build. The rule survived intact, but the warning was noise coming
from this change, and splitting it matches how the Tailwind side now expresses
the same condition.

Behaviour is unchanged: hover, a focused row and a mouse-clicked textarea all
measure the same as before.
2026-08-17 01:22:00 +02:00
..
public 🪢 feat: Langfuse Fanout Connection Setting (#14108) 2026-07-29 18:33:10 -04:00
scripts
src 🖱️ fix: Reveal Message Metadata on Hover, Not on Click (#14900) 2026-08-17 01:22:00 +02:00
sw 🛟 fix: Auto-Recover from Stale Service Worker Assets After Deploys (#13686) 2026-06-11 11:57:06 -04:00
test 🔒 chore: Upgrade react-router-dom to v7.18.2 (security) (#14582) 2026-08-01 17:19:52 -04:00
babel.config.cjs
check_updates.sh
index.html 📡 feat: add rum browser page-load diagnostics (#14106) 2026-07-05 11:32:53 -04:00
jest.config.cjs 🚀 chore: Prepare v0.8.8-rc1 (#14394) 2026-08-14 03:24:59 -04:00
jest.resolver.cjs ⬆️ chore: Migrate off deprecated @ariakit/react-core to @ariakit/react-components (#13940) 2026-06-24 23:13:57 -04:00
nginx.conf 🐳 feat: Bundle Admin Panel in Docker Compose Stacks (#13876) 2026-06-22 16:59:08 -04:00
package.json 🚀 chore: Prepare v0.8.8-rc1 (#14394) 2026-08-14 03:24:59 -04:00
postcss.config.cjs
tailwind.config.cjs 🎨 feat: Color the Context Gauge by Category and Collapse its Breakdown (#14855) 2026-08-14 22:46:10 -04:00
tsconfig.json 👷 ci: Type-check the Client Workspace (#13560) 2026-06-06 18:40:31 -04:00
vite.config.ts 🎨 refactor: adopt the @librechat/client design system (semantic color tokens + component migration) (#13879) 2026-08-06 09:15:17 -04:00