From d7e8fdb10ca5e055b7af6d22f67d9a8f42cec8ed Mon Sep 17 00:00:00 2001
From: Taylor Ho
Date: Tue, 18 Aug 2026 12:55:00 -0700
Subject: [PATCH] fix(shared-ui): delay hover disclosures by default (#5821)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
**Category:** fix
**User Impact:** Hover tooltips and informational popovers now wait for
deliberate pointer dwell instead of appearing while users move around
the app.
**Problem:** Tooltips and hover-controlled popovers appeared after
inconsistent, often very short delays, so moving across composer and
navigation controls could obstruct the next interaction.
**Solution:** Establish a 500 ms shared dwell default with no tooltip
skip-delay cascade, apply it to informational hover-controlled Popovers,
and preserve immediate click and keyboard behavior. The responsive
Community actions navigation submenu retains its documented 80 ms open /
160 ms close timing.
## Before / after
| Before | After |
|---|---|
| 
| 
|
File changes
**desktop/src/shared/ui/tooltip.tsx**
Wraps the Radix provider with documented 500 ms and zero skip-delay
defaults. The provider API still permits a future proven exception, but
no current `desktop/src` caller overrides either timing.
**desktop/src/shared/ui/popover.tsx**
Exports the documented shared hover-open timing for controlled popovers;
ordinary click/focus Popovers remain immediate.
**desktop/src/main.tsx**
Uses the shared Tooltip provider defaults at the application root.
**desktop/src/shared/ui/sidebar.tsx**
Removes the sidebar's instant Tooltip timing override.
**desktop/src/features/channels/ui/AddChannelBotTeamsSection.tsx**
Removes the local short Tooltip timing override.
**desktop/src/features/home/ui/InboxDetailPane.tsx**
Removes the local short Tooltip timing override.
**desktop/src/features/messages/ui/MessageTimeline.tsx**
Removes the timeline's local short Tooltip timing override.
**desktop/src/features/messages/ui/MessageTimestamp.tsx**
Drops the timestamp-only provider now that its 500 ms, zero-skip
behavior is shared globally.
**desktop/src/features/channels/ui/BotActivityBar.tsx**
Raises composer agent-activity hover dwell from 150 ms to the shared
default while preserving immediate click/focus opening.
**desktop/src/features/sidebar/ui/ChannelActivityPopover.tsx**
Raises channel activity hover dwell from 250 ms to the shared default
while preserving immediate focus opening.
**desktop/src/features/profile/ui/UserProfilePopover.tsx**
Reuses the shared hover timing in place of its equivalent local
constant.
**desktop/src/shared/ui/PubKey.tsx**
Reuses the shared hover timing in place of its equivalent local
constant.
**desktop/src/shared/ui/markdown/InlineEmojiPopover.tsx**
Raises emoji inspection hover dwell from 200 ms to the shared default
while preserving immediate focus opening.
**desktop/src/features/messages/ui/MessageReactions.tsx**
Raises reaction inspection hover dwell from 200 ms to the shared default
while preserving immediate focus and reaction clicks.
**desktop/src/features/communities/ui/CommunitySwitcher.tsx**
Documents the Community actions navigation submenu as an intentional
timing exception: 80 ms to open responsively and 160 ms to preserve the
pointer bridge into its portalled panel.
## Reproduction steps
Move the pointer rapidly across each surface first, then hold it still
over a labeled control. Hover-only disclosures should stay closed during
transit and open after about **500 ms** of deliberate dwell. Moving
directly between adjacent Tooltip triggers should start a fresh 500 ms
dwell rather than cascading the next Tooltip open immediately.
| Surface | Where to test | What to expect |
|---|---|---|
| Composer controls | Attachment, emoji, image editor, formatting, and
composer toolbar buttons | No Tooltip while sweeping across controls;
the hovered control's Tooltip opens after ~500 ms. Clicking remains
immediate. |
| Message actions | Hover a message, then test reply, react,
more-actions, edit, and related action-bar controls | Each Tooltip waits
~500 ms, including when moving between adjacent actions. The action
itself still runs immediately on click or keyboard activation. |
| Message metadata and content tools | Message timestamps, code-block
copy controls, diff controls, system-message controls, and video-player
controls | Tooltip appears after ~500 ms. Timestamp behavior should look
unchanged; it was already 500 ms with no skip cascade. |
| Reaction pills | Hover a reaction with one or more reactors; also
click the pill | Reactor Popover waits ~500 ms instead of 200 ms.
Clicking still toggles the reaction immediately. |
| Inline custom emoji | Hover a rendered custom emoji in message
Markdown, then focus it with the keyboard | Emoji inspector waits ~500
ms on hover instead of 200 ms. Keyboard focus opens it immediately. |
| Masked links | Hover a masked Markdown link, including one revealed
inside a spoiler | Destination Tooltip waits ~500 ms instead of the
former app-level 300 ms. Hidden spoilers still reveal no destination;
keyboard focus remains immediate. |
| Main and collapsed sidebar controls | Collapse the sidebar and hover
icon-only navigation/menu buttons; also test community-rail controls |
Sidebar Tooltips wait ~500 ms instead of opening instantly. Rapid
movement across icons should not produce a tooltip cascade. |
| Channel header and management controls | Channel members, huddle,
settings, thread-view mode, management rows, and quick-agent controls |
Each Tooltip waits ~500 ms; click/keyboard behavior remains immediate. |
| Add-channel team chips | Open Add channel where saved teams are
available and hover a team chip | Team details Tooltip waits ~500 ms
instead of 150 ms; clicking the chip still toggles the team immediately.
|
| Channel activity preview | Hover a sidebar channel that has activity,
then keyboard-focus its trigger | Activity Popover waits ~500 ms instead
of 250 ms. Focus opens it immediately, and quickly crossing channel rows
should not leave previews in the way. |
| Composer agent activity | Run an agent so the composer activity
control is present; hover, click, and focus it | Hover Popover waits
~500 ms instead of 150 ms. Click and keyboard focus still open it
immediately. |
| Inbox and draft controls | Home inbox open-context/more-actions
controls, inbox-list controls, draft detail, and drafts panel | Tooltips
wait ~500 ms instead of the inbox detail's former 200 ms/local defaults.
Clicks remain immediate. |
| Profile and public-key previews | Hover avatars, names/mentions,
project author identities, and displayed public keys | Profile/pubkey
Popovers open after ~500 ms, matching their prior behavior; the change
centralizes that timing. Click actions and focus behavior remain
immediate. |
| Agent, team, memory, and update controls | Managed-agent rows, team
identity cards, restart-diff badges, memory actions, setup steps, and
update indicator | Tooltips wait ~500 ms and do not cascade when
traversing adjacent controls. Actions remain immediate. |
| Huddle controls | Huddle bar/indicator, mic controls, and
participant-list actions | Tooltips wait ~500 ms; mute, join,
participant, and keyboard actions remain immediate. |
| Projects and activity surfaces | Project/repository cards, overview
rail, contribution graph, activity feed, reviewers, and Pulse note
controls | Tooltips wait ~500 ms with a fresh dwell between adjacent
targets. Clicking/focusing interactive controls remains immediate. |
| Intentional interaction-mode exceptions | Focus a Tooltip trigger;
click/focus an ordinary Popover; move the pointer from an open hover
Popover into its panel | Tooltip focus and Popover click/focus open
immediately because they are explicit user intent, not incidental hover.
Hover Popovers retain their 180–200 ms close grace so the pointer can
cross into the panel. The Community actions navigation submenu is the
intentional exception: it opens after 80 ms and keeps its 160 ms pointer
bridge; informational hover Popovers use the shared 500 ms delay. |
## Verification
- `pnpm typecheck`
- `pnpm check` (passes with three existing diagnostics outside this
diff)
- `pnpm test` — 4,775 passed
- `pnpm build`
---------
Signed-off-by: Taylor Ho
---
.../channels/ui/AddChannelBotTeamsSection.tsx | 2 +-
.../features/channels/ui/BotActivityBar.tsx | 10 ++-
.../communities/ui/CommunitySwitcher.tsx | 10 ++-
.../src/features/home/ui/InboxDetailPane.tsx | 2 +-
.../features/messages/ui/MessageReactions.tsx | 12 ++-
.../features/messages/ui/MessageTimeline.tsx | 2 +-
.../features/messages/ui/MessageTimestamp.tsx | 46 ++++-------
.../profile/ui/UserProfilePopover.tsx | 10 ++-
.../sidebar/ui/ChannelActivityPopover.tsx | 10 ++-
desktop/src/main.tsx | 2 +-
desktop/src/shared/ui/PubKey.tsx | 10 ++-
.../shared/ui/markdown/InlineEmojiPopover.tsx | 12 ++-
desktop/src/shared/ui/popover.tsx | 4 +
desktop/src/shared/ui/sidebar.tsx | 2 +-
desktop/src/shared/ui/tooltip.tsx | 18 ++++-
desktop/tests/e2e/community-rail.spec.ts | 81 ++++++++++++++++++-
.../e2e/composer-tooltip-dismiss.spec.ts | 31 ++++++-
17 files changed, 207 insertions(+), 57 deletions(-)
diff --git a/desktop/src/features/channels/ui/AddChannelBotTeamsSection.tsx b/desktop/src/features/channels/ui/AddChannelBotTeamsSection.tsx
index 69e511fb40..512ee6899f 100644
--- a/desktop/src/features/channels/ui/AddChannelBotTeamsSection.tsx
+++ b/desktop/src/features/channels/ui/AddChannelBotTeamsSection.tsx
@@ -78,7 +78,7 @@ export function AddChannelBotTeamsSection({
-
+
{teams.map((team) => {
const resolution = resolveTeamPersonas(team, personas);
diff --git a/desktop/src/features/channels/ui/BotActivityBar.tsx b/desktop/src/features/channels/ui/BotActivityBar.tsx
index d685a96103..cfa84f02e3 100644
--- a/desktop/src/features/channels/ui/BotActivityBar.tsx
+++ b/desktop/src/features/channels/ui/BotActivityBar.tsx
@@ -10,7 +10,12 @@ import {
import type { UserProfileLookup } from "@/features/profile/lib/identity";
import type { ManagedAgent } from "@/shared/api/types";
import { cn } from "@/shared/lib/cn";
-import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover";
+import {
+ DEFAULT_POPOVER_HOVER_OPEN_DELAY_MS,
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/shared/ui/popover";
import { Shimmer } from "@/shared/ui/Shimmer";
import { UserAvatar } from "@/shared/ui/UserAvatar";
@@ -26,7 +31,6 @@ type BotActivityBarProps = {
variant?: "toolbar" | "inline";
};
-const HOVER_OPEN_DELAY_MS = 150;
const HOVER_CLOSE_DELAY_MS = 180;
const HEADLINE_ROTATION_MS = 2200;
@@ -106,7 +110,7 @@ export function BotActivityComposerAction({
clearHoverTimer();
hoverTimerRef.current = setTimeout(() => {
setOpen(true);
- }, HOVER_OPEN_DELAY_MS);
+ }, DEFAULT_POPOVER_HOVER_OPEN_DELAY_MS);
}, [clearHoverTimer]);
const closeWithDelay = React.useCallback(() => {
diff --git a/desktop/src/features/communities/ui/CommunitySwitcher.tsx b/desktop/src/features/communities/ui/CommunitySwitcher.tsx
index cd530e6908..4d6f2d258b 100644
--- a/desktop/src/features/communities/ui/CommunitySwitcher.tsx
+++ b/desktop/src/features/communities/ui/CommunitySwitcher.tsx
@@ -39,6 +39,12 @@ import { writeTextToClipboard } from "@/shared/lib/clipboard";
import { useActiveCommunityIcon } from "@/features/communities/useCommunityIcons";
import { EditCommunityDialog } from "./EditCommunityDialog";
+// Community actions is a responsive navigation submenu, not an informational
+// disclosure. Keep its short hover dwell explicit rather than inheriting the
+// shared 500 ms Popover delay intended to prevent incidental inspection UI.
+const PROFILE_MENU_HOVER_OPEN_DELAY_MS = 80;
+const PROFILE_MENU_HOVER_CLOSE_DELAY_MS = 160;
+
const CONNECTION_STATE_LABEL: Record = {
idle: "Not connected",
connecting: "Connecting…",
@@ -128,7 +134,9 @@ export function CommunitySwitcher({
clearProfileMenuHoverTimer();
profileMenuHoverTimer.current = window.setTimeout(
() => setDropdownOpen(nextOpen),
- nextOpen ? 80 : 160,
+ nextOpen
+ ? PROFILE_MENU_HOVER_OPEN_DELAY_MS
+ : PROFILE_MENU_HOVER_CLOSE_DELAY_MS,
);
}
diff --git a/desktop/src/features/home/ui/InboxDetailPane.tsx b/desktop/src/features/home/ui/InboxDetailPane.tsx
index 9bb593087d..4b64ad10e0 100644
--- a/desktop/src/features/home/ui/InboxDetailPane.tsx
+++ b/desktop/src/features/home/ui/InboxDetailPane.tsx
@@ -622,7 +622,7 @@ function InboxMessageDetailPane({