From 26c5f7ae49e87486c679f07b74aaec252aa4f62b Mon Sep 17 00:00:00 2001 From: Neppkun Date: Sun, 24 May 2026 10:15:45 +0300 Subject: [PATCH] feat: show tooltip with full workspace name on truncated sidebar titles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wrap the workspace title span in AgentListItem with TooltipIfPresent so hovering a row reveals the full name when the title is clipped by the existing 'truncate' utility. A ResizeObserver tracks scrollWidth vs clientWidth on the span and only attaches the tooltip while overflow is present, so non-truncated rows stay tooltip-free. The trigger lives on the title element only, leaving the rest of the row (group label, badges, terminal-count tooltip) untouched. Uses displayTitle so variants/best-of-n group labels (e.g. 'A · …') are included, matching the row's aria-labels. --- .../AgentListItem/AgentListItem.tsx | 47 +++++++++++++++---- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/browser/components/AgentListItem/AgentListItem.tsx b/src/browser/components/AgentListItem/AgentListItem.tsx index 7139ff6a3e..9854e50cbc 100644 --- a/src/browser/components/AgentListItem/AgentListItem.tsx +++ b/src/browser/components/AgentListItem/AgentListItem.tsx @@ -27,7 +27,7 @@ import { useDrag } from "react-dnd"; import { getEmptyImage } from "react-dnd-html5-backend"; import { SubAgentListItem } from "./SubAgentListItem"; -import { Tooltip, TooltipTrigger, TooltipContent } from "../Tooltip/Tooltip"; +import { Tooltip, TooltipTrigger, TooltipContent, TooltipIfPresent } from "../Tooltip/Tooltip"; import { Popover, PopoverContent, PopoverTrigger, PopoverAnchor } from "../Popover/Popover"; import { PositionedMenu, PositionedMenuItem } from "../PositionedMenu/PositionedMenu"; import { @@ -481,6 +481,26 @@ function RegularAgentListItemInner(props: AgentListItemProps) { const displayTitle = groupLabel ? `${groupLabel} · ${workspaceTitle}` : workspaceTitle; const isEditing = editingWorkspaceId === workspaceId; + // Track whether the title span is actually clipped by `truncate` so we only + // attach a hover tooltip when the user can't already read the full name. + // Callback ref instead of useRef so we re-measure when the span mounts/unmounts + // (e.g. when toggling in/out of the rename input branch). + const [titleEl, setTitleEl] = useState(null); + const [isTitleTruncated, setIsTitleTruncated] = useState(false); + useEffect(() => { + if (!titleEl) { + setIsTitleTruncated(false); + return; + } + const check = () => { + setIsTitleTruncated(titleEl.scrollWidth > titleEl.clientWidth); + }; + check(); + const observer = new ResizeObserver(check); + observer.observe(titleEl); + return () => observer.disconnect(); + }, [titleEl, displayTitle]); + const linkSharingEnabled = useLinkSharingEnabled(); const [shareTranscriptOpen, setShareTranscriptOpen] = useState(false); const [heartbeatModalOpen, setHeartbeatModalOpen] = useState(false); @@ -1030,16 +1050,23 @@ function RegularAgentListItemInner(props: AgentListItemProps) { {groupLabel && ( {groupLabel} )} - - {workspaceTitle} - + + {workspaceTitle} + + )}