Skip to content

cmux next: links in the agent chat show their site's icon - #27167

Open
lucasr1b wants to merge 9 commits into
feat-cmux-nextfrom
next-chat-favicons
Open

lucasr1b wants to merge 9 commits into
feat-cmux-nextfrom
next-chat-favicons

Conversation

@lucasr1b

@lucasr1b lucasr1b commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

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), through GuardedRemoteFetch: https only, every address of every hop public, only the service and its image servers (www.google.com, t0 to t3.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 = never fetches 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, any corp, internal or intranet label), 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 pasted http(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 ?mock in 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)
Preview

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, never fetches 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-blue for the new link mark.

Observed / Not run

  • Observed: tagged build chat-favicons on macOS 26 with Claude Code replies: React, MDN, both Rust sites, Python, npm, OpenAI, Vite, Svelte, Zed and Bun icons load; localhost keeps the globe; the Rust book's black icon shows on its plate.
  • Observed at 65aff84 (after merging feat-cmux-next): bun run check, prepush-gate.py (no App lines, no new singleton: the one site icons store is on AgentPaneShared), the webviews tests above (37 pass), Release compile (swift build -c release, Xcode 26.6).
  • Observed: swift test --filter AgentPaneSiteIcons passes 11 tests at 36fc5ef (the Swift code is the same at the head). The base's RowBadgeSpinnerDensityTests does not compile on Xcode 26.6 (from fix(sidebar): a row's working spinner draws at the status glyph size #33120, fix in base red: RowBadgeSpinnerDensityTests' argument table is nonisolated (from #33120) #33377); it was patched locally for this run only.
  • Observed: the agent-pane.transcript/site-favicons gallery state draws its stand-in favicons in dark and light (local gallery server). Its fixtures were SVG, which the page drops; they are now PNG.
  • Not run: light theme in the app; the full webviews and Swift suites; a test that two panes share one store (review P2, open).
  • Upstream reds on the last CI run (4faef99): cmux-next swift test (sidebar padding tests, and the run cut short at 1500 s) and nightly-guards / release-notary (runner pool 502). This PR changes none of those files.

View with [code]smith Autofix with [code]smith
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 = never disables 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.ico because it finds icons for 97% of 106 common developer sites, against 65% for /favicon.ico and 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.

Written for commit 38d593d. Summary will update on new commits.

View guided diff Turn on auto-fix

@lucasr1b lucasr1b added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 29, 2026
@lucasr1b
lucasr1b temporarily deployed to ci-routing-agent October 29, 2026 02:16 — with GitHub Actions Inactive
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 38d593d: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr27167 -f ref=next-chat-favicons -f platform=macos -f backend_mode=local

Unclear: web / react-apps-check failed: no known signature; failed step: Check webview test shards.

CI failed on 4faef997ac (run 41206867742 attempt 1): 1 code, 1 unknown.

Job Verdict Why
web / react-apps-base-check (4) code a web test failed
web / react-apps-check unknown no known signature; failed step: Check webview test shards

Failures:

  • seen on other PRs agent-pane-unknown-write-changed-files.test.ts changedFiles lists a Write of unknown old contents, without a failed one: failed (job)
  • seen on other PRs agent-pane-unknown-write-changed-files.test.ts a file one call diffed and another named without a diff lists once: failed (job)
  • seen on other PRs agent-pane-unknown-write-changed-files.test.ts an edit with no diff still waiting for its permission is not a changed file: failed (job)
  • seen on other PRs editor-csp.test.ts bundle > Monaco is lazy: no first load of the diff, markdown or editor page contains it: failed (job)
  • seen on other PRs pane-english.test.ts agent pane strings > no user-visible English is written in the pane's sources: failed (job)
Matched log lines
web / react-apps-base-check (4): (fail) changedFiles lists a Write of unknown old contents, without a failed one [50.81ms]

Not re-run automatically: web / react-apps-base-check (4), web / react-apps-check are not machine failures.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

red: web / Web status; a job it gathers is red on this change (its line here); do not merge until green; see lane-rules
red: web / react-apps-base-check (4); NEW webview test reds: test/link-cards.test.tsx > a site cmux already has a title for still reads as its address; the card has the favicon and title; test/link-cards.test.tsx > a bare address keeps its text when the site's title and icon come in; the icon fills the globe's square; do not merge until green; see lane-rules
red: web / react-apps-check; a job it gathers is red on this change (its line here); do not merge until green; see lane-rules

Gallery before/after for 4faef99: 16 states rendered, 10 images differ, 2655 more touched states not rendered. Images are in the run's artifact, not on this public PR; the independent review of this head reads them on its own, so the PR needs no hand-made screenshots of these states.

gh run download 41206867612 -R manaflow-ai/cmux -n gallery-advisory-27167-4faef997ace97703211661d517c0ddc413915ec4 (then read gallery-review.json)

States
State Theme Status Diff Picked by
agent-pane.copy-controls/output Apple System Colors Light unchanged 0% own ToolRow.tsx
agent-pane.copy-controls/output Apple System Colors unchanged 0% own ToolRow.tsx
agent-pane.hover-fade/received Apple System Colors Light unchanged 0% own ToolRow.tsx
agent-pane.hover-fade/received Apple System Colors unchanged 0% own ToolRow.tsx
agent-pane.hover-fade/received-fading-in Apple System Colors Light unchanged 0% own ToolRow.tsx
agent-pane.hover-fade/received-fading-in Apple System Colors unchanged 0% own ToolRow.tsx
agent-pane.hover-fade/tool-output Apple System Colors Light changed 0% own ToolRow.tsx
agent-pane.hover-fade/tool-output Apple System Colors unchanged 0% own ToolRow.tsx
agent-pane.hover-fade/tool-output-fading-in Apple System Colors Light changed 0% own ToolRow.tsx
agent-pane.hover-fade/tool-output-fading-in Apple System Colors unchanged 0% own ToolRow.tsx
agent-pane.inline-chips/before Apple System Colors changed 0.47% own LinkChips.tsx
agent-pane.inline-chips/before Apple System Colors Light changed 0.44% own LinkChips.tsx
agent-pane.inline-chips/after-minimal Apple System Colors changed 0.538% own LinkChips.tsx
agent-pane.inline-chips/after-minimal Apple System Colors Light changed 0.513% own LinkChips.tsx
agent-pane.inline-chips/after-accent Apple System Colors changed 0.538% own LinkChips.tsx
agent-pane.inline-chips/after-accent Apple System Colors Light changed 0.513% own LinkChips.tsx
agent-pane.inline-chips/after-hairline Apple System Colors changed 0.538% own LinkChips.tsx
agent-pane.inline-chips/after-hairline Apple System Colors Light changed 0.513% own LinkChips.tsx
agent-pane.open-in-menu/closed Apple System Colors Light unchanged 0% own chips.css
agent-pane.open-in-menu/closed Apple System Colors unchanged 0% own chips.css
agent-pane.open-in-menu/several-browsers Apple System Colors Light unchanged 0% own chips.css
agent-pane.open-in-menu/several-browsers Apple System Colors unchanged 0% own chips.css
agent-pane.path-chip/file Apple System Colors Light unchanged 0% own LinkChips.tsx
agent-pane.path-chip/file Apple System Colors unchanged 0% own LinkChips.tsx
agent-pane.path-chip/folder Apple System Colors Light unchanged 0% own LinkChips.tsx
agent-pane.path-chip/folder Apple System Colors unchanged 0% own LinkChips.tsx
agent-pane.path-chip/outside-lock Apple System Colors Light unchanged 0% own LinkChips.tsx
agent-pane.path-chip/outside-lock Apple System Colors unchanged 0% own LinkChips.tsx
agent-pane.path-chip/outside-text Apple System Colors Light unchanged 0% own LinkChips.tsx
agent-pane.path-chip/outside-text Apple System Colors unchanged 0% own LinkChips.tsx
agent-pane.path-chip/denied Apple System Colors Light unchanged 0% own LinkChips.tsx
agent-pane.path-chip/denied Apple System Colors unchanged 0% own LinkChips.tsx

Pick other states with a Gallery: entry.id, entry.id/variant line in the PR body.

@lucasr1b
lucasr1b temporarily deployed to ci-routing-agent October 29, 2026 02:16 — with GitHub Actions Inactive
@github-actions github-actions Bot added risk:high Risk tier (docs/ci/risk-tiers.md) and removed risk:normal Risk tier (docs/ci/risk-tiers.md) labels Oct 29, 2026
@blacksmith-sh

blacksmith-sh Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Found 7 test failures on Blacksmith runners:

Failures

Test View Logs
test/agent-pane-unknown-write-changed-files.test.ts/
agent-pane-unknown-write-changed-files.test
View Logs
test/agent-pane-unknown-write-changed-files.test.ts/
agent-pane-unknown-write-changed-files.test
View Logs
test/editor-csp.test.ts/editor-csp.test View Logs
test/editor-csp.test.ts/editor-csp.test View Logs
test/pane-english.test.ts/pane-english.test View Logs
test/pane-english.test.ts/pane-english.test View Logs
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)/
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)
View Logs

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

@teamleaderleo

Copy link
Copy Markdown
Collaborator

Bead cx-4yx1li created for this PR by the cmux coordinator.

The host asks the favicon service for a public host name, held to the service and its image servers, redraws the answer as a small PNG (a dark mark on a clear ground gets a light plate), and keeps one answer per host.
Web links draw the site's favicon in a fixed square before the text, the globe until it arrives; pasted URLs in a prompt become links; fetch tool rows show the page's icon.
@lucasr1b
lucasr1b temporarily deployed to ci-routing-agent October 29, 2026 02:32 — with GitHub Actions Inactive
@lucasr1b
lucasr1b temporarily deployed to ci-routing-agent October 29, 2026 02:32 — with GitHub Actions Inactive
@lucasr1b
lucasr1b temporarily deployed to ci-routing-agent October 29, 2026 02:33 — with GitHub Actions Inactive
teamleaderleo added a commit that referenced this pull request Oct 29, 2026
* test(transcript): a quoted or out-of-range bare #N stays text

Leo: "these auto render to the pr instead of just #1 #2...". Red at base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-9131aa102699be06c366

* fix(transcript): a quoted or out-of-range bare #N stays text

Leo: "these auto render to the pr instead of just #1 #2...". A reply quoting
"what is #1 / #2" drew cmux#1 and cmux#2 chips.

- A blockquote links no bare #N (owner/repo#N and addresses still link).
- Rule 4: rules c and d take a candidate only when N is as long as the smallest
  number the chat named in it; out of the folder's range, only the App's pull
  request cache can link N, else it is text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-9131aa102699be06c366

* test(transcript): count only bare chips in the out-of-range case

A prompt's pasted pull request address may draw as a chip too (#27167).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-9131aa102699be06c366

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Resolves PromptText.tsx with the base's version: a prompt holding a web
address now renders through Markdown, whose links are UrlChip, which this
branch draws with the site's mark. linkedUrls stays exported and tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n only as an inline PNG

The merge made prompts link through Markdown (PromptTyped), so linkedUrls
had no caller left: remove it and test the bubble's own path. SiteIcon
draws a link.inspect icon only through the same PNG data URL check
fetched icons get.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@austinywang

Copy link
Copy Markdown
Contributor

Review (independent review subagent, correctness first) of 88f98a1 (the merge of feat-cmux-next, no force push): FIX, now addressed in 38d593d.

Merge: the base renders a prompt that holds an address through Markdown (PromptText.tsx PromptTyped), whose web links are UrlChip. So I took the base's PromptText, and prompts get this PR's site marks via Markdown → UrlChip → SiteIcon.

  1. Fixed: linkedUrls had no caller after the merge, but link-site-icons.test.tsx still tested it. 38d593d removes it, and the prompt test now renders PromptTyped. One behavior change from this PR's body: a path in a prompt is now a chip, as the base draws it, not plain text.
  2. Fixed: a link.inspect icon (known) went into the CSS url() without the PNG check that fetched icons get. SiteIcon now passes both through pngIcon (siteIcons.ts).
  3. Open, minor: the page keeps a host's answer for its whole life (siteIcons.ts:7,83), so an open chat never asks again after the host's 10-minute retry. The "asked again after 10 minutes" in the body holds only for a new page.
  4. Swift side holds up. Main-actor state with no race; decoding is @concurrent; no force unwraps; images capped at 1024 px. Fetches are https only, no cookies or referrer, at most 3 redirects, limited to www.google.com and t0-t3.gstatic.com, 64 KiB, 5 s, 8 at once, 512 hosts kept. Only the host name is sent. IP literals, single labels and reserved or corp names are dropped. remote = never fetches nothing.

risk:high: sending host names to a public favicon service is a trust/privacy policy call, so it waits for Leo or Lawrence.

@austinywang

Copy link
Copy Markdown
Contributor

Review (independent review subagent, correctness first): SHIP at 38d593d

  1. linkedUrls has no reference left anywhere in webviews/, and every LinkChips import is still used.
  2. The new prompt test takes the real path. PROMPT_MARKDOWN matches the URL, so PromptTyped renders Markdown, which turns bare URLs into UrlChip. localhost is private, so only react.dev is asked and the local link keeps the globe.
  3. pngIcon drops nothing real: every known icon the host sends is a PNG data URL (AgentPaneReplyLinks.swift:193 from AgentReplySites.iconPNG, and AgentPaneReplyBrowsers.swift:77).

This is risk:high: it sends link host names to a public favicon service, which is a trust/privacy policy call. Not labeling ready-to-land; Leo or Lawrence decides.

@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent October 31, 2026 13:43 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent October 31, 2026 13:43 Inactive
@teamleaderleo

Copy link
Copy Markdown
Collaborator

For awareness: this PR conflicts with feat-cmux-next, so it can't land until it's rebased or base is merged in. Its current review verdict stays as is; a hand-resolved merge gets a fresh review at the new head.

…avicons

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/AgentPaneReplyLinks.swift
#	Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/AgentPaneReplyRequest.swift
#	webviews/src/agent-session/acpmux/chips/LinkChips.tsx
#	webviews/src/agent-session/acpmux/conversation/ToolRow.tsx

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 65aff845 Deployed Nov 3, 2026 by lucasr1b via cmux-tui tree preflight #105060
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:high Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants