From d922e1ef79624e4551ec44838343dc97de3362ec Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 14 Jul 2026 18:02:03 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=B1=20fix:=20Single-Tap=20Select=20for?= =?UTF-8?q?=20Pinned=20Agents,=20Model=20Specs=20&=20Models=20on=20Touch?= =?UTF-8?q?=20(#14272)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pin/options buttons on these items were hidden-until-hover on ALL pointers (invisible / opacity-0), making each item's rendering hover-dependent. On touch that triggers the iOS "first tap reveals the hover state, second tap activates" behavior, so selecting a model spec, model, or pinned/favorite agent took two taps. Gate the hover-reveal on hover CAPABILITY (the same fix #13712 applied to message hover buttons): the control is visible/tappable by default and only hidden-until-hover on hover-capable pointers via [@media(hover:hover)]. On touch the item is no longer hover-dependent, so the first tap selects. - ModelSpecItem / EndpointModelItem: pin button reveal - FavoriteItem (sidebar pinned agents): options button + wrapper reveal --- .../Chat/Menus/Endpoints/components/EndpointModelItem.tsx | 7 ++++++- .../Chat/Menus/Endpoints/components/ModelSpecItem.tsx | 7 ++++++- client/src/components/Nav/Favorites/FavoriteItem.tsx | 8 ++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx b/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx index 3c3e58fc9e..aa4cb8efaa 100644 --- a/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx +++ b/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx @@ -117,7 +117,12 @@ export function EndpointModelItem({ modelId, endpoint }: EndpointModelItemProps) 'rounded-md p-1 hover:bg-surface-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring-primary', isFavorite ? 'visible' - : 'invisible group-focus-within:visible group-hover:visible group-data-[active-item]:visible', + : // Visible by default so it's tappable on touch (no hover to + // reveal it); only hidden-until-hover on hover-capable pointers. + // A hover-gated child would otherwise make the whole item + // hover-dependent, so the first tap only reveals it and a second + // tap is needed to select (the iOS double-tap). + 'group-focus-within:visible group-hover:visible group-data-[active-item]:visible [@media(hover:hover)]:invisible', )} > {isFavorite ? ( diff --git a/client/src/components/Chat/Menus/Endpoints/components/ModelSpecItem.tsx b/client/src/components/Chat/Menus/Endpoints/components/ModelSpecItem.tsx index 10662fa55b..09b29502b7 100644 --- a/client/src/components/Chat/Menus/Endpoints/components/ModelSpecItem.tsx +++ b/client/src/components/Chat/Menus/Endpoints/components/ModelSpecItem.tsx @@ -61,7 +61,12 @@ export function ModelSpecItem({ spec, isSelected }: ModelSpecItemProps) { 'rounded-md p-1 hover:bg-surface-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring-primary', isFavorite ? 'visible' - : 'invisible group-focus-within:visible group-hover:visible group-data-[active-item]:visible', + : // Visible by default so it's tappable on touch (no hover to + // reveal it); only hidden-until-hover on hover-capable pointers. + // A hover-gated child would otherwise make the whole item + // hover-dependent, so the first tap only reveals it and a second + // tap is needed to select (the iOS double-tap). + 'group-focus-within:visible group-hover:visible group-data-[active-item]:visible [@media(hover:hover)]:invisible', )} > {isFavorite ? ( diff --git a/client/src/components/Nav/Favorites/FavoriteItem.tsx b/client/src/components/Nav/Favorites/FavoriteItem.tsx index f373d26e93..561b57c30c 100644 --- a/client/src/components/Nav/Favorites/FavoriteItem.tsx +++ b/client/src/components/Nav/Favorites/FavoriteItem.tsx @@ -152,7 +152,11 @@ export default function FavoriteItem(props: FavoriteItemProps) { 'absolute right-2 flex items-center', isPopoverActive ? 'pointer-events-auto opacity-100' - : 'pointer-events-none opacity-0 group-focus-within:pointer-events-auto group-focus-within:opacity-100 group-hover:pointer-events-auto group-hover:opacity-100', + : // Interactive by default so it's tappable on touch; only + // hidden-until-hover on hover-capable pointers. Otherwise the + // whole row is hover-dependent and the first tap just reveals + // this instead of selecting (the iOS double-tap). + 'group-focus-within:pointer-events-auto group-focus-within:opacity-100 group-hover:pointer-events-auto group-hover:opacity-100 [@media(hover:hover)]:pointer-events-none [@media(hover:hover)]:opacity-0', )} onClick={(e) => e.stopPropagation()} > @@ -168,7 +172,7 @@ export default function FavoriteItem(props: FavoriteItemProps) { 'inline-flex h-7 w-7 items-center justify-center rounded-md border-none p-0 text-sm font-medium ring-ring-primary transition-all duration-200 ease-in-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50', isPopoverActive ? 'opacity-100' - : 'opacity-0 focus:opacity-100 group-focus-within:opacity-100 group-hover:opacity-100 data-[open]:opacity-100', + : 'focus:opacity-100 group-focus-within:opacity-100 group-hover:opacity-100 data-[open]:opacity-100 [@media(hover:hover)]:opacity-0', )} aria-label={localize('com_nav_convo_menu_options')} data-testid="favorite-options-button"