fix(agents): use Pi's own mark for the avatar, not tty7 artwork

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
This commit is contained in:
l0ng-ai
2026-07-28 19:07:48 +08:00
parent 334896ff47
commit 8ca19e6a5a
4 changed files with 42 additions and 15 deletions
+3 -3
View File
@@ -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 <id>` (Pi's `--resume` is a boolean that only opens
the interactive picker), with `--session` / `--session-id` / `--fork` /
+9 -5
View File
@@ -1,7 +1,11 @@
<!-- Original tty7 artwork: a geometric Greek pi drawn for this avatar, on the
same 24x24 grid and ~3.3 stroke weight as the bundled brand marks. Pi
(@earendil-works/pi) publishes no symbol of its own, and the letter is not
anyone's trademark, so nothing here is transcribed from a vendor mark. -->
<!-- Pi's own brand mark, from https://pi.dev/logo-auto.svg.
Normalized to tty7's 24x24 icon format; geometry unchanged. The published
file is an 800x800 box carrying a `prefers-color-scheme` <style> block that
swaps black for white — no other mark in this set ships theme-conditional
CSS, and the avatars are tinted by the app regardless, so the class and
media query are dropped for the flat sentinel fill the rest of the set
uses. The mark lands on an exact 4x4 grid of 6-unit cells at this size. -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 4h14a1.8 1.8 0 0 1 0 3.6H5A1.8 1.8 0 0 1 5 4Zm1.7 3.6H10v10.75a1.65 1.65 0 0 1-3.3 0V7.6Zm7.3 0h3.3v10.75a1.65 1.65 0 0 1-3.3 0V7.6Z" fill="#FF0000"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H18V12H12V18H6V24H0V0ZM6 6V12H12V6H6Z" fill="#FF0000"/>
<path d="M18 12H24V24H18V12Z" fill="#FF0000"/>
</svg>

Before

Width:  |  Height:  |  Size: 620 B

After

Width:  |  Height:  |  Size: 795 B

+5 -4
View File
@@ -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,
};
+25 -3
View File
@@ -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());
}