cmux NEXT: pull request links get a styled hover card; tooltip audit
Summary
Leo asked (2026-10-20) for the full title of a cut-off pull request to be one hover away, on a styled card and not the system tooltip. He also asked for an audit of every tooltip in the app: drop the useless ones and keep and style the useful ones.
One link hover card (chips/LinkHoverCard.tsx). Every link in the transcript now opens the same card: a link in a reply, a pull request chip in a reply, and a pull request bar above the composer. Its head shows the site's favicon, the page title cmux already has (or the host), Open (a cmux tab) and Open in ▾ (#22461's OpenInMenu). Its foot shows the host and the full address, with secret-looking values shown as ••• (#22461's linkDisplay.ts). The card sits on the tooltip's frosted glass. The open and close logic is shared in useHoverCard.ts and keeps #22461's rules: the card stays open while Open in is open, and a pointer pick in that menu doesn't pin it.
Pull request section. Hovering or focusing a pull request bar above the composer opens a card. So does a pull request chip in a reply, where the card is the link's card with the PR details added above the address and Open kept. The card shows:
- the full title,
owner/repo#Nand the state (Open, Draft, Merged or Closed, in the header chip's colours) - the author's login
- the checks ("7 of 9 checks passed, 2 failing", with a dot in the state's colour)
Until the host answers, it shows what the chat already knows.
Where the data comes from. A new passive page method, pr.inspect {url}, runs gh pr view <url> --json … (now including author) on the header reader's queue, two at a time. The reader and the cache now live in CmuxNextAgentPane (AgentChatPullRequestReader.swift), so CmuxNextApp stays under its size cap. It runs in the home folder and never prompts, and each link is cached for a minute in the App (AgentReplyPullRequests) and on the page (pullRequestStore.ts). The URL comes from untrusted reply text, so the host accepts only https://github.com/<owner>/<repo>/pull/<n>, with an optional trailing slash or tab like /files. It rebuilds that URL from its parts, so gh never sees anything else: no other host, no user info, no query, no part starting with -.
Rule. tooltip-rules.ts changes from "no tooltip on cut-off text" to: a cut-off pull request or link title gets the styled hover card, and plain decorative text gets neither. The scanner itself is unchanged; a hover card is not a tooltip.
Audit. Every tooltip and hover hint in the app, web and Swift, is listed below with what this PR did to it.
- Web: a
titlealready draws the frosted title tooltip (ui/titleTooltips.ts), not WebKit's bubble. - Swift:
.themedHelpandthemedToolTipare themed. Raw.help(andNSView.toolTipdrew the stock AppKit bubble against TooltipPresenter's own rule, so every kept one now goes through the themed path. A new guard,NativeTooltipGuardTests, fails on any raw view tooltip. It allowsNSMenuItemand a// tooltip-native-allow: <reason>marker.
Audit: 321 entries, 91 drop, 22 keep-but-style, 208 keep
Dropped (they repeat the visible label or sit on an obvious icon such as ×, copy, pencil, ‹ ›, ←, +, ↻, ⋯ or a media glyph). Every control keeps its accessible name; a Swift control that had none gets an accessibilityLabel.
| area | entries |
|---|---|
| agent pane | App row copy, jump-to-latest pill; composer + menu (Picker tooltip prop removed, as it had one caller); queued prompt edit / remove; dictation notice ×; header ⋯; summary card fold ×2 / close ×2; summary sources +; artifact ‹ › copy ×; CopyButton; EditDiff copy; image viewer copy; session-start note ×; turn copy; user bubble ‹ › copy edit; recap ×; reply media expand; PDF viewer ×; diff panel back; changes ⋯; mention row (row shows name and folder); model row star; Now Playing prev / play / next; suggested task × |
| pages | Chief back; Home back; message ⋯; quick-reaction emoji hints; run sheet ×; thread × and cancel reply; composer emoji / attach / @ / send hints; sound ▶; keybindings dismiss / remove; Markdown ‹ › hints; link bubble copy / edit hints; update dismiss ×; app switch |
| diff and shared | base picker ✓; file ⋯; file-filter × |
| Swift | todo checkbox; machine section +; text box paperclip; vault ↻; files back / forward and view-mode segments; eject; Now Playing × prev next play; Enabled switch; bookmarks more ×2; feed refresh; debug settings clear; copy command; close × on notification, toast, helper panel, share, extensions, page info; page-info rows that repeat their title (5; their strings removed from Swift); bookmark star; extension ⋯; Stop sharing; pane toolbar ⋯ |
Kept and moved to the themed tooltip (Swift raw .help / toolTip): todo attachment names; right sidebar mode buttons; text box action-cycle button (2); vault live dot and row path; browser fit-frame explanation; files Enclosing folder and icon-size slider; Chief pet and its rail; unknown recipient chip; Chief avatar. Left native, with a reason: the tab strip's speaker tooltip. The speakers are layers, so the themed tooltip, which shows on view entry, can't anchor to one; that needs per-speaker regions. The menu item at AgentPaneContextMenu:185 is an NSMenuItem.
Kept on the web title tooltip, already frosted and styled; not moved to a card in this PR: the access chip and access menu (multi-line), queued prompt cut off, subagent task, reaction "who reacted", art and backdrop credits. They read well on the title tooltip, which wraps lines and widens to cut-off text. A card for them is follow-up work if wanted.
Kept (an icon-only control whose meaning isn't plain, a keyboard shortcut, a state, a reason, a path or a value not on screen, or a styled card already):
| area | entries |
|---|---|
| agent pane (81) | coverage chip detail; steer / stop / send with shortcut or reason; location and folder paths; collapsed icon-only chips; Fast toggle; dictation; floating chat buttons with shortcuts; session sidebar rail and marks; session preview card; header status detail; header tools (new tab, changes, files, preview, split, terminal, browser); session details toggle; summary button; subagent dots; gallery alt; subagent stop; pair-note error; artifact save; wrap lines; image too large; open preview ×2; retry, fork, retry with; read state, bookmark, fork; image thumbnails; trust hint; link preview card; reply image alt; attachment thumb name; session hover card; diff tools; open file in tab / editor; push reason; tree row cut-off name; effort descriptions; env picker; model chip usage; picker rail tabs; refresh status; new-tab shortcuts; stage and widget cards; replay steps and ticks; minimap bookmark and preview card; the pull request card (new); file-review eye ("Mark as viewed" says what a click does) |
| pages (50) | attachment names; new message / new Chief; emoji names; invite / edit Chief / copy link; react / reply; thread status dot, resolve, copy link, rename, set owner; formatting buttons; undo / redo step; reset ↺; filter; setting error detail; theme swatches; managed lock; keybinding conflicts, held, change, reset; Markdown status, Aa, width hint, image title, remote image URL, link bubble and its URL, unlink; build version; chart cards; transfer error; debug reset; password row icons, export reason; stats cells; agent activity frame and lane |
| diff and shared (13) | host-support reasons; jump to file; toolbar pills; hide file search; segments; path picker icons; All Chats icons; find pill replace and its shortcut buttons |
| Swift (64) | menu item URLs and reasons (7); app help; bookmark bar item URL; feed details; server info; debug reset; task dots, initials and badges; incognito, focus, machine and profile badges; back / forward / sidebar with shortcuts; certificate issuer; page-info chip; chrome buttons (zoom, profile, theme, reason, shortcut); extension action and pin; find pill match case; group colour, agent cursor, status label, home header, import, palette search and scope, remote hover toolbar; sidebar folder path, section header, icon-only rows with shortcuts, card buttons, notice line, profile regions, workspace badges; presence dots; pane toolbar items with shortcuts; 5 Swift hover cards |
Testing
- Web, from
webviews/:bun test test/pr-hover-card.test.tsxwas red on the first commit (no card, no store) and is green now. It also covers an inline link opening the same card: favicon, title,host · …&token=•••, Open and Open in, and noghread.test/link-cards.test.tsx(#22461) andtest/link-preview-card-styles.test.ts(now coveringLinkHoverCard.tsx) pass. Also green:test/url-chip-hover.test.tsx(now also expects thepr.inspectask),test/tooltip-rules.test.ts,test/chat-header-tooltips.test.tsx,test/chip-fixed-width.test.ts,test/pr-dock-stale-state.test.tsx,test/edit-resend.test.tsxand the files each drop touched.bunx tsc --noEmit -p .is clean.vp checkshows 0 errors (the warnings are already on base). - Swift:
AgentChatPullRequestStatusTestsnow reads the field list fromAgentPanePullRequest.fields(review finding: its hard-coded copy made the fake gh reject the newauthorfield).AgentPanePullRequestInspectTests(URL contract, author, reply shape),AgentReplyPullRequestsTests(reads by link with a fakegh, one read a minute) andNativeTooltipGuardTestswere written red first and run on CI. Locally onlyverify-local.py --swift-changed: swift-syntax and test-wiring pass. A Python copy of the guard's scanner flagged all 22 raw tooltips before the change and none after. - Not run locally (the Mac is overloaded; no Mac builds here): the Swift build and tests, and the browser lanes
composer-claude-split.test.tsandui-a11y.test.ts(expectations updated for the dropped hints). CI runs them.
Changelog
Added: hovering a link in the agent pane shows one card with its site, address (secrets masked), Open and Open in; a pull request's also shows its full title, state, author and checks
Changed: tooltips that only repeated a button's label or named an obvious icon are gone, and the rest use the themed tooltip
🤖 Generated with Claude Code
Agent session: cosmic-gray