PR dock chips; tooltips only where nothing else names the control

Merged
TE
22d ago
+369-302

Leo's agent-chat bugs (cosmic-gray): the opened-PR row above the composer was a full-width bar with its X clipped and a needless tooltip, and tooltips sat on text that already shows.

Captures

Real cmux DEV builds, the same seeded agent chat with a pull request (a gh pr create in the mock fixture), cropped to the chat column; the host's computer name is painted out. Before is base a5341bc5 with this PR's mock fixture only (so its dock shows the same pull request); after is this PR's head 3dc7d0c3. The merged head 0d5c6813 only adds base merges after that, none touching these files. The pointer is the debug page hover (debug.agent_pane hover), so the page gets real pointer events and its tooltips, but no CSS :hover tint, and the arrow in the clip is drawn in.

Dark

PR dock and tooltips, dark

Light

PR dock and tooltips, light

Hover across the chip, its ×, and a code block's Copy (dark, base left, this PR right): base shows a tooltip on all three; this PR only on Copy, which is icon only.

Hover clip

Full quality:

What changed

  • PR dock: each pull request is now a chip as wide as its content: icon, #N, repo, and the title cut off at 360 px. The dismiss X sits inside the chip, and the chip has no tooltip. Clicking it still opens the PR.
  • Tooltip audit: a row, chip, link, path or thought whose text is already on screen no longer gets a tooltip, even when the text is cut off. Tooltips remain on:
    • icon-only controls;
    • shortcuts;
    • text that appears nowhere else (each marked tooltip-allow: <reason>):
      • why a control is disabled: Passwords export, the diff options needing host support, Push with nothing to push;
      • a failure or warning detail: settings row error, cloud transfer error, the chat header warning, Markdown read-only or save failed, an image too large to show;
      • who reacted, emoji names, icon+count badges (keybinding conflicts, the settings filter, channel members), an image attachment's name;
      • policy enforcement detail (handoff review, session coverage, permission coverage);
      • New Tab kind shortcuts.
  • Hover delay: dropped from this PR. Base #20528 (hover intent, app-wide) already makes tooltips wait, then stay warm, so the merge takes base's ui/titleTooltips.ts and ui/Tooltip. The Swift .themedHelp audit found nothing to remove (every use is on an icon-only or label-hidden control).
  • Lint: webviews/test/tooltip-rules.test.ts reads the JSX and fails on title/data-tooltip/ui/Tooltip around an element whose text shows. It skips the dev-only gallery/ and optchat-inspector/.

Verification

  • Red, at b46b555f3430c: bun test test/tooltip-rules.test.ts → 105 findings.
  • Green, at the head: same command → 1 pass.
  • bun run check passes. jsdom suites run file by file all pass, except diff-label-bundle and diff-worker-bundle, which need built bundles (environment only, not run here).
  • The PR dock browser test now checks the chip's size, that the X sits inside it, and that it has no tooltip. Browser lanes run only in CI (no Chromium on big-red).

Removed tooltips

~85 tooltips across 48 files
  • BranchBasePicker.tsx: control (row.worktreeDir); button.base-picker-button (full untruncated comparison)
  • DiffToolbar.tsx: span.source-pill-additions (label("additions")); span.source-pill-deletions (label("deletions"))
  • agent-session/acpmux/AddProjectPanel.tsx: span (path)
  • agent-session/acpmux/Composer.tsx: control (t(steerKind === "steer" ? "composer.steerTooltip" : "compose); div.acpmux-attachment (attachment.name); control (attachment.name)
  • agent-session/acpmux/ComposerContext.tsx: span.acpmux-menu-text (folder.id); span.acpmux-location-picker (selected)
  • agent-session/acpmux/ComposerPickers.tsx: control (t(PICKER_LABELS.planHint))
  • agent-session/acpmux/FileSearch.tsx: control (match.path)
  • agent-session/acpmux/FloatingChat.tsx: span.acpmux-floating-title (floating.title); folded bar: title + last line dropped, now just Expand ⌘J
  • agent-session/acpmux/Inspector.tsx: dd (snapshot.sessionId)
  • agent-session/acpmux/ProjectChooser.tsx: control (current ? ${t("project.label")}: ${current} : undefined); control (project?.cwd); span.acpmux-project-host (project.host)
  • agent-session/acpmux/SessionSidebar.tsx: control; control (placeLabel ? ${title}\n${placeLabel} : title)
  • agent-session/acpmux/TrustAsk.tsx: span.acpmux-trust-ask-text (ask.cwd); span.acpmux-trust-ask-text (ask.state === "failed" ? undefined : ask.cwd)
  • agent-session/acpmux/changes/FileHeader.tsx: control (file.path); span.acpmux-fh-badge (t("turn.outside.title"))
  • agent-session/acpmux/chips/LinkChips.tsx: control (outside ? ${path}\n${t("chip.outsideProject")} : path); a.cv-link (href)
  • agent-session/acpmux/chips/ReplyImage.tsx: control (outside ? ${path}\n${t("chip.outsideProject")} : path); span.cv-image-placeholder (src)
  • agent-session/acpmux/chips/ReplyMedia.tsx: span (path); span (url); span.cv-media (title)
  • agent-session/acpmux/conversation/ArtifactPanel.tsx: span.acpmux-artifact-title (title === language ? title : ${title} (${language})); control (t("artifact.versionCount", { index: index + 1, total: list.l)
  • agent-session/acpmux/conversation/EditDiff.tsx: span.cv-edit-diff__name (file.path} data-path={file.path)
  • agent-session/acpmux/conversation/EditedFilesCard.tsx: span.acpmux-edited-path (path} data-path={path); span.acpmux-edited-path (file.path} data-path={file.path); control (t("edited.undoHostLabel"))
  • agent-session/acpmux/conversation/Markdown.tsx: span.cv-link (lm[2]); span.cv-link (src); a.cv-link (src)
  • agent-session/acpmux/conversation/PreviewCard.tsx: a.acpmux-turn-preview-address (site?.title ? ${site.title}\n${url} : url)
  • agent-session/acpmux/conversation/RenderCard.tsx: span.acpmux-render-card-title (title)
  • agent-session/acpmux/header/PullRequestChip.tsx: control (label); a (check.name); span (check.name}>{check.name}</span)
  • agent-session/acpmux/header/WorktreePill.tsx: control (title)
  • agent-session/acpmux/newtab/AllChatsList.tsx: control (project.cwd ?? label); control (row.cwd ? ${title}\n${row.cwd} : title)
  • agent-session/acpmux/prototype/RowBranch.tsx: span.proto-row-branch (branch)
  • agent-session/acpmux/prototype/RowPullRequest.tsx: span.proto-row-pr (pullRequest.title)
  • agent-session/acpmux/prototype/WorkspaceShell.tsx: span.proto-profile (Browser profile: ${profile.name}); control (Open as a tab in ${workspaceLead(target).title})
  • agent-session/acpmux/question/QuestionCard.tsx: control (each.header ?? each.prompt)
  • agent-session/acpmux/replay/ReplayScreen.tsx: control (t("replay.speed")); control (t("replay.continueHint"))
  • agent-session/acpmux/shell/ShellRow.tsx: code.acpmux-shell-block-command (run.command)
  • agent-session/acpmux/summary/PullRequestDock.tsx: a.acpmux-pr-dock__bar (title || undefined); span.acpmux-pr-dock__repo (pr.repo)
  • agent-session/acpmux/summary/SummaryPopover.tsx: control (file.path); control (source.url); control (file.path); control (${pr.repo}#${pr.number}${pr.state === "open" ? "" : · ${t); span.acpmux-summary-text (task.command); span.acpmux-summary-text (wakeup.text)
  • agent-session/acpmux/toolCards/ToolCards.tsx: span.acpmux-trust-ask-text (card.text); control (action.hint)
  • diff-viewer/FileHeader.tsx: span.file-header-path (previousPath == null ? path : ${previousPath} → ${path}); span.file-header-additions (label("additions")); span.file-header-deletions (label("deletions")); control (reason === "generated" ? label("generatedFile") : label("lar)
  • diff-viewer/Toolbar.tsx: control (option.message); select repo picker (selected option's message); option (option.message)
  • pages/cloud/AccountPanel.tsx: control (signOutUnavailable ? t(L.unavailable) : undefined)
  • pages/editor/EditorPage.tsx: span.ed-file (path)
  • pages/files/FilesPage.tsx: header.files-header (root.folder)
  • pages/markdown/MarkdownPage.tsx: span.md-file (path)
  • pages/settings/components/ActionRow.tsx: div.row-help (help)
  • pages/settings/components/HarnessesCard.tsx: div.row-help (row.update.command)
  • pages/settings/components/HostCards.tsx: span.row-help (host.terminal.ghostty_config); span.row-help (host.terminal.shell_integration ?? undefined)
  • pages/settings/components/HostSections.tsx: div.row-help (row.subtitle)
  • pages/settings/components/ThemeStudio.tsx: div.row-help (text(appRow.summary))
  • pages/settings/editors/ChatRootsEditor.tsx: span.folder-path (folder.path)
  • pages/settings/editors/FolderListEditor.tsx: span.folder-path (folder)
  • ui/ContextMenu.tsx: span.ui-menu-label (item.label)
  • ui/variant-pick/VariantPick.tsx: ui/Tooltip around the Pick button

String keys whose only use was a removed tooltip (chip.outsideProject, replay.continueHint, composer.steerTooltip/interruptTooltip, picker.planHint, edited.undoHostLabel, turn.outside.title, artifact.versionCount, sidebar.folderOnHost, diffViewer.branchPickerComparing) are left in the catalogs. A string cleanup can drop them.

Changelog

  • Changed: The pull request bar above the agent composer is a compact chip with its dismiss button inside. Tooltips show only on icon buttons and shortcuts.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Makes the opened-PR row above the agent composer a compact chip as wide as its content, with its dismiss button inside and no tooltip. Across the pages, tooltips now appear only where nothing else names the control; about 85 tooltips are gone across 48 files.

Tooltip rules

  • webviews/test/tooltip-rules.test.ts scans the JSX and fails on any element whose own text shows but still carries a title/data-tooltip/ui/Tooltip; tooltip-allow: <reason> comments mark the exemptions kept.
  • Tooltips stay on icon-only controls, shortcuts, disabled-state reasons, and failure, policy, participation, or action-scope detail. Channel image attachments keep their name tooltip, since the name is visible only when the image fails to load.
  • Browser tests cover the chip's size, the dismiss X inside it, no tooltip, and the dock taking its own space so the transcript ends above it. The worked-chat mock now opens a pull request so captures show the dock.
  • Tooltip timing comes from base's hover intent; this PR's own delay logic was dropped. The fold button's tooltip is kept under tooltip-allow because it names what a click does; the Steer tooltip stays because it adds the shortcut.

Changelog

  • Changed: The pull request bar above the agent composer is a compact chip with its dismiss button inside. Tooltips show only on icon buttons and shortcuts.

Showcase

showcase
showcase
showcase

Open the live gallery