From 8ca19e6a5aa8575b94a08de5855598a89dbca5f5 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Tue, 28 Jul 2026 19:07:48 +0800 Subject: [PATCH] fix(agents): use Pi's own mark for the avatar, not tty7 artwork MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Pi avatar shipped as original artwork on the claim that Pi publishes no symbol. That claim was wrong: Pi's mark is at pi.dev/logo-auto.svg. Swap the drawing for the published one and correct the provenance notes that repeated the claim (the SVG header, the asset-source arm and the changelog entry). The published file is the one mark in this set that arrives with a stylesheet — an 800x800 box whose `prefers-color-scheme` block swaps black for white. usvg renders it anyway (it applies the base rule and ignores the media query), so this is normalisation rather than a fix: geometry rescaled to the 24x24 grid the rest of the set uses, class and media query dropped for the flat sentinel fill, since gpui and the tray both tint these as alpha masks. At this size the mark lands on an exact 4x4 grid of 6-unit cells, so the rescale is lossless. The tray's avatar test now walks the whole roster instead of one branded and one fallback agent, and asserts the disc came back with more than one opaque colour. It is the only test that runs the bundled SVGs through resvg — the asset-source test proves the bytes resolve, not that they parse into visible geometry — and a mark that parses to nothing renders as a bare accent disc that nothing else would catch. Refs #225 --- CHANGELOG.md | 6 +++--- assets/icons/agents/pi.svg | 14 +++++++++----- src/ui/assets.rs | 9 +++++---- src/ui/tray/icon.rs | 28 +++++++++++++++++++++++++--- 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57571881..2e640966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,9 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 robot glyph every unbranded agent shares, so a Pi tab was indistinguishable from an Aider or Qwen one in the sidebar, the tab chip and the tray menu. They now carry their own avatar on the existing sky accent, status dot unchanged. - Pi ships no symbol to transcribe, so the mark is tty7's own geometric Greek - pi, cut to the same 24x24 grid and stroke weight as the bundled brand marks — - nothing trademarked is vendored in. Restoring a Pi pane also resumes its + The mark is Pi's own, from pi.dev, rescaled to the same 24x24 grid as the + other bundled brand marks — its `prefers-color-scheme` stylesheet dropped, + since these avatars are tinted by the app. Restoring a Pi pane also resumes its conversation now: the tty7 extension reports Pi's session id, and the resume command is `pi --session ` (Pi's `--resume` is a boolean that only opens the interactive picker), with `--session` / `--session-id` / `--fork` / diff --git a/assets/icons/agents/pi.svg b/assets/icons/agents/pi.svg index bf6c98a5..89ed68a6 100644 --- a/assets/icons/agents/pi.svg +++ b/assets/icons/agents/pi.svg @@ -1,7 +1,11 @@ - + - + + diff --git a/src/ui/assets.rs b/src/ui/assets.rs index 9e2cdad9..43361596 100644 --- a/src/ui/assets.rs +++ b/src/ui/assets.rs @@ -185,10 +185,11 @@ fn agent_icon(path: &str) -> Option<&'static [u8]> { // silhouette, so this is lobehub/lobe-icons' square transcription (MIT), // drawn for exactly this avatar use. Its notice rides in the SVG. "icons/agents/grok.svg" => include_bytes!("../../assets/icons/agents/grok.svg"), - // The one mark that isn't a vendor's at all: Pi ships no symbol to - // transcribe, so this is tty7's own geometric Greek pi, cut to the same - // 24x24 grid and stroke weight as the rest of the row. The letter is not - // a trademark, so nothing is borrowed here. + // Pi's own mark, from pi.dev — the one file that arrives with theme + // logic attached (`logo-auto.svg` carries a `prefers-color-scheme` + // style block). The geometry is kept as published and rescaled to the + // 24x24 grid; the CSS is dropped, since these avatars are tinted by the + // app and no other mark here brings a stylesheet. Details in the SVG. "icons/agents/pi.svg" => include_bytes!("../../assets/icons/agents/pi.svg"), _ => return None, }; diff --git a/src/ui/tray/icon.rs b/src/ui/tray/icon.rs index ae11b045..a32ea73b 100644 --- a/src/ui/tray/icon.rs +++ b/src/ui/tray/icon.rs @@ -339,11 +339,17 @@ mod tests { assert_ne!(normal.data, attention.data); } - /// The avatar renders for a branded agent, an unbranded (bot-fallback) - /// agent, and with/without the status dot. + /// Every avatar renders, with and without the status dot. Run over the + /// whole roster rather than one branded and one fallback agent, because + /// this is the only test that puts the bundled SVGs through resvg: the + /// asset-source test next door proves the bytes resolve, not that they + /// parse into visible geometry. Vendor marks arrive in whatever shape the + /// vendor publishes — stylesheets, nested groups, features usvg quietly + /// drops — and a mark that parses to nothing shows up as a bare accent + /// disc, which nothing else here would catch. #[test] fn agent_avatar_renders_brand_and_fallback() { - for agent in [CLIAgent::Claude, CLIAgent::Qwen] { + for agent in CLIAgent::ALL { let idle = agent_avatar(agent, AgentStatus::Idle).unwrap(); let waiting = agent_avatar(agent, AgentStatus::Waiting).unwrap(); assert_eq!((idle.width(), idle.height()), (32, 32)); @@ -351,6 +357,22 @@ mod tests { assert_eq!(idle.pixel(0, 0).unwrap().alpha(), 0); // …and the center is covered (disc + glyph). assert!(idle.pixel(16, 16).unwrap().alpha() > 0); + // The glyph actually drew something. The disc under it is a flat + // accent fill, so every opaque pixel shares one colour unless the + // white mark landed on top — one colour means resvg handed back an + // empty canvas, which is what a silently-unsupported SVG feature + // looks like from here. + let shades: std::collections::HashSet<_> = idle + .pixels() + .iter() + .filter(|p| p.alpha() == 0xFF) + .map(|p| (p.red(), p.green(), p.blue())) + .collect(); + assert!( + shades.len() > 1, + "{} rendered as a bare disc — its glyph drew nothing", + agent.display_name() + ); // The status dot changes the bottom-right corner. assert_ne!(idle.data(), waiting.data()); }