Ovvi

Program icons

Where program icons show, how a pane gets its icon, when the app asks herdr, and the icon catalog

Every pane, tab and agent row shows the icon of the program that runs in it (D60).

Where icons show

PlaceLook
Title-bar tab capsuleone tile for a one-pane tab; for several panes, up to 5 tiles overlapping, one per pane
Card headerthe glyph alone, 16 pt, in secondary color, then the program's name and the folder
Agent rows in the sidebarthe pane's tile, with no badge
Hover cardthe pane's glyph in each pane row
Palette rows for tabsthe front tile of the tab's stack
  • Tile. A rounded rect, 22 × 18 pt, radius 5, in the catalog's tile color, with the glyph's 24-unit box drawn at 17 pt (the glyphs carry their own padding) in the catalog's glyph color, a 1 px dark outline and a faint highlight at the top, toned down as a whole (saturation 0.82, brightness −0.04: Rex's tiles are quieter than the catalog's colors). An unknown program's tile shows its first letter.
  • Stack. The keyboard card's icon is in front (herdr's focused pane until a card of the tab had the keyboard); the others follow in herdr's order. Each tile behind sits 4 pt to the right of the one in front. A 1 pt ring around each tile in front is cut out of the tiles behind it, so the layers read apart on any background. The capsule's accessibility value lists the icon ids, front first ("icons: vim, sleep").
  • The sidebar has no tab rows, so it shows no tab icons. Space rows show a state icon, not program icons.
  • A tab's tiles carry no badge: its state icon sits at the capsule's end (Status).
  • In the card header, herdr's pane label wins over the program's name when it is set. The name is herdr's agent, else the foreground process's name ("vim /tmp", "zsh ~"); a Node CLI shows its script's name (gemini).

How a pane gets its icon

The first match wins:

  1. herdr's agent kind for the pane (agent).
  2. The foreground process from pane.process_info: its name, then the base name of argv0. A node or bun process is matched by its script first: the end of the script's path (pi-coding-agent/dist/cli.js), then its base name, because a CLI started through its bin link has the link's path (/opt/homebrew/bin/gemini).
  3. The start of the terminal title up to a space (terminal_title_stripped). herdr strips status glyphs such as ✳ and spinners from it, so the catalog matches the words after them ("Claude Code", π).
  4. Only the shell, or nothing known: the terminal icon. An unknown program or agent gets a neutral tile with its first letter.

A leading - (-zsh) and a path's folder are removed before matching. The catalog's match field holds the values for each step. herdr reports no foreground process for a setuid program such as macOS's top, so its pane shows the terminal icon.

When the app asks herdr

The app asks for the foreground process with one pane.process_info call, only at these times:

  • once for every pane when it first appears in a snapshot, also in tabs never shown: at once for the panes of the first snapshot, 1 s later for a new pane or a new terminal under a reused pane id, and again at the next snapshot if it failed. A new shell's startup runs short-lived programs (a .zshrc that runs gh auth token), so no other ask is made in that first second, nor after it while a program other than the shell still runs (every 0.5 s, 3 s at most: a slow startup). The new tab shows the terminal icon until then;
  • each time the pane shows in a card;
  • 1 s after the user presses Return in that card;
  • when a snapshot shows that the pane's agent, its label or the first word of its stripped terminal title changed, for a pane asked before (a progress title changes on and on; its first word does not). herdr sends pane.updated on each change of the stripped title, and the events stream subscribes to it;
  • for a chat launch whose agent process is not known yet, every 0.5 s, 20 times at most;
  • once more 5 s after a plugin pane's answer shows a program other than its placeholder (a slow shell start after a restart).

The same answer gives the argv of every foreground process, where the app finds a chat view launch marker. The chooser also asks once when its agent is ready.

Each ask is its own herdr run, so asks can finish out of order; only the latest answer lands. At most 8 run at once, the others wait in order, a card's ask and Return's first: without the limit, the first snapshot of a 400-pane session shows no card for over 30 s. An ask that a newer one replaced while it waited makes no herdr run.

There is no other polling. The result stays on the pane in the HostStore, so the views only read it.

The catalog

  • resources/icons/ in the repository holds the icons: svg/ (one SVG per program), catalog.json (id, name, match values, tile colors, source and license of each icon) and SOURCES.md (the source of each file and the license texts).
  • 105 icons, all under open licenses: Simple Icons (CC0), Lobe Icons (MIT), Phosphor Icons fill (MIT), Devicon (MIT), a few from the programs' own repositories (MIT, Apache-2.0) and a few drawn for Ovvi (CC0).
  • One style for the whole set, so a row of tiles reads as one family: every glyph is one solid filled path in the current color (no strokes, no wordmarks, no full-color art), fitted to about 16 to 18 pt of a 24 pt box by its visual weight. Generic glyphs (terminal, monitor, database, folder) come from one family, Phosphor fill.
  • One rule colors every tile: the brand color (saturation capped) with a white glyph, or #111111 on the few light brands. Black or white brands get #2b2d31 with a white glyph; shells and generic tools get #2b2d31 with a #7ee787 glyph. No tile is white.
  • Brand marks belong to their owners and only name the program.
  • resources/icons/tools/ rebuilds the whole set (README.md there), including the app's image sets (Ovvi/Assets.xcassets/program.<id>, templates with their vector data). The app bundles catalog.json as it is.
  • No icon art comes from Rex. Ovvi's own About window (Ovvi ▸ About Ovvi) shows the app icon, the name and the version, and one "Acknowledgements" button. The button opens Acknowledgements.txt, a file shipped inside the app, in the default text editor (TextEdit). The file lists every library, font and icon source, with its home page and its license text in full, including the libraries built into Ghostty (FreeType, Oniguruma, libpng, zlib and others). A new library or icon source gets its entry there; make test fails when an icon license is missing from it.

How it works: herdr calls.

On this page