diff --git a/assets/icons/circle-info.svg b/assets/icons/circle-info.svg index ec30e25e..3da25cb1 100644 --- a/assets/icons/circle-info.svg +++ b/assets/icons/circle-info.svg @@ -1 +1 @@ - + diff --git a/assets/icons/copy.svg b/assets/icons/copy.svg index 55af1b1f..0cc21942 100644 --- a/assets/icons/copy.svg +++ b/assets/icons/copy.svg @@ -1 +1 @@ - + diff --git a/assets/icons/ellipsis.svg b/assets/icons/ellipsis.svg index 2cbbc2be..96688451 100644 --- a/assets/icons/ellipsis.svg +++ b/assets/icons/ellipsis.svg @@ -1 +1 @@ - + diff --git a/assets/icons/eye.svg b/assets/icons/eye.svg index 6b0f0238..fdd8524f 100644 --- a/assets/icons/eye.svg +++ b/assets/icons/eye.svg @@ -1 +1 @@ - + diff --git a/assets/icons/file.svg b/assets/icons/file.svg index fab17eac..4ebcbb67 100644 --- a/assets/icons/file.svg +++ b/assets/icons/file.svg @@ -1 +1 @@ - + diff --git a/assets/icons/folder-closed.svg b/assets/icons/folder-closed.svg index be3c5989..53308f04 100644 --- a/assets/icons/folder-closed.svg +++ b/assets/icons/folder-closed.svg @@ -1 +1 @@ - + diff --git a/assets/icons/folder-open.svg b/assets/icons/folder-open.svg index 2c0a9527..b0d5acbf 100644 --- a/assets/icons/folder-open.svg +++ b/assets/icons/folder-open.svg @@ -1 +1 @@ - + diff --git a/assets/icons/folder.svg b/assets/icons/folder.svg index be3c5989..cd3dc093 100644 --- a/assets/icons/folder.svg +++ b/assets/icons/folder.svg @@ -1 +1 @@ - + diff --git a/assets/icons/git-branch.svg b/assets/icons/git-branch.svg index c85b0a16..ca2cd2bd 100644 --- a/assets/icons/git-branch.svg +++ b/assets/icons/git-branch.svg @@ -1 +1 @@ - + diff --git a/assets/icons/info.svg b/assets/icons/info.svg index 2f5b329c..63ac0e3c 100644 --- a/assets/icons/info.svg +++ b/assets/icons/info.svg @@ -1 +1 @@ - + diff --git a/assets/icons/list.svg b/assets/icons/list.svg index f840375b..c61de25a 100644 --- a/assets/icons/list.svg +++ b/assets/icons/list.svg @@ -1 +1 @@ - + diff --git a/assets/icons/panel-left.svg b/assets/icons/panel-left.svg index 2d61872f..e176eaca 100644 --- a/assets/icons/panel-left.svg +++ b/assets/icons/panel-left.svg @@ -1 +1 @@ - + diff --git a/assets/icons/panel-right.svg b/assets/icons/panel-right.svg index a975c9ec..29964e74 100644 --- a/assets/icons/panel-right.svg +++ b/assets/icons/panel-right.svg @@ -1 +1 @@ - + diff --git a/assets/icons/plus.svg b/assets/icons/plus.svg index b9457a10..8fc4fee3 100644 --- a/assets/icons/plus.svg +++ b/assets/icons/plus.svg @@ -1 +1 @@ - + diff --git a/assets/icons/refresh.svg b/assets/icons/refresh.svg index 81178e79..0678b784 100644 --- a/assets/icons/refresh.svg +++ b/assets/icons/refresh.svg @@ -1 +1 @@ - + diff --git a/assets/icons/search.svg b/assets/icons/search.svg index 4fcdc073..45da2dfa 100644 --- a/assets/icons/search.svg +++ b/assets/icons/search.svg @@ -1 +1 @@ - + diff --git a/assets/icons/terminal.svg b/assets/icons/terminal.svg index 47dab739..17fde759 100644 --- a/assets/icons/terminal.svg +++ b/assets/icons/terminal.svg @@ -1 +1 @@ - + diff --git a/src/ui/assets.rs b/src/ui/assets.rs index d2dd4cbf..2d88903f 100644 --- a/src/ui/assets.rs +++ b/src/ui/assets.rs @@ -68,53 +68,77 @@ fn agent_icon(path: &str) -> Option<&'static [u8]> { "icons/terminal.svg" => include_bytes!("../../assets/icons/terminal.svg"), // A git glyph on the detail-panel spec below (gpui-component bundles // none). Serves both the sidebar row's branch line and the Changes tab. - // Drawn as an actual commit graph — a trunk with a node at each end, - // branching once — rather than lucide's long arc slung between two - // floating rings: that one is the loosest, most lopsided shape in a row - // of four, and at the sidebar's 11px a stroked ring's hole collapses into - // a blur anyway. Nodes are filled here. + // Drawn as a commit graph — a trunk with a node at each end, branching + // once — rather than lucide's long arc slung between two floating rings: + // that one is the loosest, most lopsided shape in a row of four. Nodes + // are filled, because at the sidebar's 11px a stroked ring's hole + // collapses into a blur. "icons/git-branch.svg" => include_bytes!("../../assets/icons/git-branch.svg"), - // Filled-panel dock glyphs for the window chrome: an outlined frame with - // one inset solid block marking which dock is open (the "commercial-app" - // look). gpui-component only ships the hollow line `panel-left/right`, so - // tty7 carries its own filled variants for the title bar controls. + // Dock glyphs for the window chrome: a rounded frame with one inset solid + // block marking which dock is open. gpui-component only ships the hollow + // line `panel-left/right`, which says a panel exists but not which one is + // showing, so tty7 carries its own. The block is a flat fill, not a wash — + // nothing in this set relies on partial alpha surviving rasterisation. "icons/panel-left.svg" => include_bytes!("../../assets/icons/panel-left.svg"), "icons/panel-right.svg" => include_bytes!("../../assets/icons/panel-right.svg"), - // The chrome glyphs that sit beside those dock tiles, redrawn to share - // their weight and their solid-mass vocabulary. Lucide's stock `plus` is - // a thin bare cross that reads far lighter than the framed dock icon - // next to it, and its `ellipsis` strokes three `r=1` circles — at 18px - // the cap overlaps its own fill and the dots blur into grey smudges. - // - // `plus` is the one deliberate departure from the spec below: a bare - // cross has no enclosing shape to carry weight, so it runs stroke 2.4 on - // a tighter 6→18 span. Matching the spec exactly would leave it visibly - // frailer than the framed tile beside it. + // The chrome glyphs beside those dock tiles. Lucide's `ellipsis` strokes + // three `r=1` circles, and at 18px the cap overlaps its own fill and the + // dots blur into grey smudges, so these are filled to the node rule below. "icons/plus.svg" => include_bytes!("../../assets/icons/plus.svg"), "icons/ellipsis.svg" => include_bytes!("../../assets/icons/ellipsis.svg"), // The detail panel's own set: four tab tiles at 18px and four controls at // 13px, all in one panel, so they're drawn to one spec instead of taken // from lucide as-is. // - // stroke 1.9 (except `plus` — see above) - // radius 2.6–3.2, never 2.0 - // span 3.2→20.8, both axes; circles widen to ~2.5→21.5, since a - // round shape reads smaller at equal geometry - // nodes always filled, r ≥ 1.35 — a stroked dot hazes below 16px + // The spec is "humanist": full, rounded, near-square. Every glyph here + // draws the *conventional* thing — a magnifier is a magnifier, a folder + // is a folder, a sheet has a dog-ear. Several rounds of this set tried + // swapping those metaphors for cleverer ones (terminal-native objects, + // brand-derived panels, deliberately unclosed forms) and every one of + // them cost more recognition than it bought character. What carries the + // set is how the familiar shape is *drawn*, not which shape it is. // - // The stock glyphs share none of that: they mix stroke weights, sit a - // 21-wide circle next to an 18-wide folder, and leave so much dead space - // inside the frame that the row reads as four glyphs from four sets. + // stroke 2.1, flat across the set, round caps and joins + // radius 3.4–4.4 — generous, matching the app's own 10px panels + // span 3.4→20.6, near-square bounding box; circles widen ~4%, + // since a round shape reads smaller at equal geometry + // curvature one step fuller than a mechanical arc would give (`eye`, + // `folder`'s shoulder), which is what makes the set feel + // drawn rather than constructed + // nodes filled, r ≥ 1.6 — a stroked dot hazes below 16px // - // Shape choices worth keeping: `info` is a panel with two lines written - // in it — a picture of what the tab actually opens (cwd, shell, branch, - // changes) — rather than the circled `i`, which is the most-drawn icon - // there is and says "help" as readily as "details"; Outline's last row is - // cut short, because three full-width rules read as a hamburger menu - // rather than a list; - // `folder-closed` drops its lid rule, which at 18px only crowded the top - // of the box; `copy`'s back sheet wraps three sides and stops on its own - // curves instead of poking two raw stubs out of an L. + // The five craft rules the set is held to, in the order they're usually + // violated: + // + // 1. Optical weight, not geometric: round shapes are drawn ~4% larger + // than square ones so a row of mixed glyphs reads level. + // 2. Tangent, never intrusion: `search`'s handle leaves the circle at + // its 45° tangent point rather than aiming at the centre — an + // overlap there shows as a lump at 64px. + // 3. Even interior rhythm: within one glyph, gaps between strokes are + // equal (`file`'s two text rules are spaced as far apart as each is + // from the frame). Uneven interior air is the single biggest source + // of "drawn sloppily". + // 4. One terminal treatment: every end is round, every corner shares + // the radius band. Don't mix a hard-folded corner into this set. + // 5. Equal apparent size, not equal bounds: `folder` is wide and short + // so it's drawn wider; `file` is narrow and tall so it's drawn + // taller. They occupy the same visual area, not the same rectangle. + // + // The stock lucide glyphs share none of that: they mix stroke weights, + // sit a 21-wide circle next to an 18-wide folder, and leave so much dead + // space inside the frame that a row reads as four glyphs from four sets. + // + // Shape choices worth keeping: `info` is a panel with a title bar ruled + // across the top and one content line under it — a picture of what the + // tab actually opens (cwd, shell, branch, changes) — rather than the + // circled `i`, which is the most-drawn icon there is and says "help" as + // readily as "details"; the title rule is also what keeps it from + // colliding with `panel-left`, which is the same frame with a block in + // it. Outline's last row is cut short, because three full-width rules + // read as a hamburger menu rather than a list. `copy`'s back sheet wraps + // three sides and stops on its own curves instead of poking two raw stubs + // out of an L. "icons/list.svg" => include_bytes!("../../assets/icons/list.svg"), "icons/folder-closed.svg" => include_bytes!("../../assets/icons/folder-closed.svg"), "icons/folder-open.svg" => include_bytes!("../../assets/icons/folder-open.svg"), @@ -125,9 +149,13 @@ fn agent_icon(path: &str) -> Option<&'static [u8]> { // `folder` and `file` carry no detail-panel role of their own — they're // here because overriding `folder-open` above would otherwise split the // file tree down the middle, drawing expanded rows on this spec and - // collapsed ones (and every file) from stock lucide. Same shape as - // `folder-closed`: upstream's only difference between the two is the lid - // rule this set drops anyway. + // collapsed ones (and every file) from stock lucide. + // + // `folder` is the file tree's collapsed row; `folder-closed` is the + // Files *tab* label, and carries one inner rule so the two names stop + // resolving to one identical drawing. The rule is legible there because + // the tab renders at 18px — at the tree's 13px it would only crowd the + // box, which is why the collapsed row doesn't wear it. "icons/folder.svg" => include_bytes!("../../assets/icons/folder.svg"), "icons/file.svg" => include_bytes!("../../assets/icons/file.svg"), // The circled `i` that `info.svg` used to be, kept under its own name for @@ -139,8 +167,9 @@ fn agent_icon(path: &str) -> Option<&'static [u8]> { // The Files tab's remote (SFTP) mode needs a refresh it doesn't need // locally: the local tree runs a recursive filesystem watcher and // invalidates itself, a remote listing has nothing watching it. Drawn to - // the circle rule above (2.7→21.3) so it sits level with the `eye` beside - // it rather than lucide's r=9 `rotate-cw`, which reads a step small. + // the circle rule above (r 8.6) so it sits level with the `eye` beside it + // rather than lucide's r=9 `rotate-cw`, whose arrow head is also a size + // step heavier than anything else in the row. "icons/refresh.svg" => include_bytes!("../../assets/icons/refresh.svg"), "icons/agents/claude.svg" => include_bytes!("../../assets/icons/agents/claude.svg"), "icons/agents/codex.svg" => include_bytes!("../../assets/icons/agents/codex.svg"),