cmux next: links in the agent chat show their site's icon
Web links in agent replies showed a plain globe, or nothing, so a reply with several sources read as a wall of blue text. Links in a person's own prompt were plain text. This PR draws each external link with its site's favicon, as T3 Code does: in replies, in prompts (a pasted URL becomes a link), and on web fetch tool rows.
- Host op
link.favicons {hosts}(AgentPaneSiteIcons.swift): an icon cmux already has from its own browser tabs comes first, with no network. Else the host asks a public favicon service for the host name (the link's path and query never leave the Mac), throughGuardedRemoteFetch: https only, every address of every hop public, only the service and its image servers (www.google.com,t0tot3.gstatic.com), at most 3 redirects, 64 KiB, 5 s, no cookies or referrer. The answer is decoded off the main actor and redrawn as a PNG of at most 64 px. A dark mark on a clear ground gets a light plate, so it shows on a dark theme. - Bounds: one fetch per host, at most 8 at once for all panes, 512 hosts kept. A site with no icon, or a failed fetch, is asked again after 10 minutes, not kept for the app run.
agentPane.images.remote = neverfetches nothing. - Page (
chips/SiteIcon.tsx,chips/siteIcons.ts): the mark is a fixed square from the first frame (globe until the icon arrives), so a late icon never reflows the paragraph. Requests are batched, one call in flight. A local or private address (localhost,192.168.x,.local) keeps the globe and is never sent. The host also never sends reserved names (.corp,.home,.intranet,.private,.alt, ...) or a company's own hosts (jira.corp.acme.com, anycorp,internalorintranetlabel), the same list T3 Code keeps off the service. GitHub and arXiv keep cmux's own marks. The icon is a CSS background, not an<img>, so copying a message copies text only and the composer attaches no images. Only a base64 PNG data URL is accepted from the host. - Prompts (
UserBubble.tsx): a typed or pastedhttp(s)address is a link chip with the same mark and hover card. Paths stay plain text. - Dev server (
webviews/dev-server/faviconHost.ts): the same op for?mockin a plain browser.
Why a service and not /favicon.ico: measured on 106 common developer sites, /favicon.ico finds 65%, the page's <link rel=icon> 87%, the service 97%. Sites like rust-lang.org declare their icon only in the page, and sites like npmjs.com refuse a direct fetch. The service sees the host names a chat links; that is the trade-off T3 Code makes too.
Changelog
- Added: links in agent chat replies, prompts and web fetch rows show the site's icon.
| Preview (Agent chat, dark theme) |
|---|
Tests
webviews/test/link-site-icons.test.tsx: globe then favicon from one batched ask; local, private and GitHub links never asked; a prompt's pasted URL becomes a link; a fetch row shows its page's icon; no<img>in the drawn marks; an answer that is not a base64 PNG is no icon; past 512 sites, pending sites still get their icons.Packages/macOS/CmuxNext/Tests/CmuxNextAgentPaneTests/AgentPaneSiteIconsTests.swift(stubbed resolver and transport, no network): request validation, only public host names asked, the service URL, the host pin, one fetch per host as a PNG, a page answer is no icon, bytes that are no image are no icon,neverfetches nothing, a tab's icon wins without a fetch, a dark mark gets a plate.- Updated:
link-cards.test.tsx,pr-hover-card.test.tsx,links-bluefor the new link mark.
Observed / Not run
- Observed: tagged build
chat-faviconson macOS 26 with Claude Code replies: React, MDN, both Rust sites, Python, npm, OpenAI, Vite, Svelte, Zed and Bun icons load;localhostkeeps the globe; the Rust book's black icon shows on its plate. - Observed:
bun run check,prepush-gate.py, the webviews tests above, Release compile (swift build -c release). - Observed:
swift test --filteronAgentPaneSiteIconsTestsand the 6 neighbouring reply-link suites: 80 tests pass (Xcode 26.6). Other guards (generated files, concurrency, motion, guardrails) pass; app-size and god-file guards warn (+2 App lines); gallery coverage, crash ratchet and localization fail the same way on base. - Not run: light theme in the app; the full webviews and Swift suites.
- Upstream red:
cmux-next checksfails only on "Package dependency lists sorted" (CmuxNextHomeEngineTestsimportsCmuxNextHomewithout declaring it, from a63986a44b0). It fails the same way on the base; this PR does not touchPackage.swift.
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Web links in agent replies, prompts, and web fetch tool rows now show the site's favicon instead of a plain globe or plain text, via a new link.favicons host op.
- The host fetches icons from a public favicon service for public host names only, guarded to https, public hops, at most 3 redirects, 64 KiB and 5 s, with no cookies or referrer, and caches one answer per host per app run.
- The mark is a fixed square drawn as a CSS background, so a late icon never reflows the paragraph and copying a message copies text only.
- Local and private addresses are never fetched, GitHub and arXiv keep their own marks, and
agentPane.images.remote = neverdisables icon fetching. - Pasted web addresses in prompts become link chips through the same Markdown path as replies, and fetch tool rows draw the page's icon.
- The service is used instead of
/favicon.icobecause it finds icons for 97% of 106 common developer sites, against 65% for/favicon.icoand 87% for the page's<link rel=icon>. - Webview and Swift tests cover request bounds, public-host validation, caching, batching, prompt links, fetch rows, and the store's size limit; only a base64 PNG data URL is accepted as an icon, whether fetched or already known.