feat(web): show each CLI's logo in the Run menus instead of a colour dot

The Run menus (toolbar dropdown, phone overview picker, Custom Endpoint rows,
model picker) marked every backend with an 8px colour dot, so telling Codex
from DeepSeek meant reading the label. Each known backend now draws its own
logo in that slot. It is CSS only: every surface already renders
`.run-mode-dot <id>`, so no markup changes.

- Brand-coloured marks (Claude, Gemini, Antigravity, DeepSeek, OMP) paint as a
  background image; monochrome ones (Codex, OpenCode, Pi, Grok, plus Shell and
  web URLs) are masks over the row's text colour, so they follow every skin.
- Logos are inline SVG data URIs (img-src already allows data:), from
  @lobehub/icons-static-svg 1.95.1 (MIT); the OMP mark is omp.sh's own.
- Drops the non-og skin overrides that re-tinted four dots with a
  `background:` shorthand, which would have wiped the logo.
- An id with no logo (a clis.json addition) keeps a dot, now in --text-dim
  instead of being transparent.
- test/run-menu-cli-logos.test.ts pins that every stock agent plus shell/web
  has a logo in exactly one paint group and that nothing resets the slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-10-04 23:52:31 +02:00
parent 9240493c43
commit d00229ee29
2 changed files with 149 additions and 24 deletions
File diff suppressed because one or more lines are too long
+85
View File
@@ -0,0 +1,85 @@
/**
* @fileoverview Static guard for the Run menus' per-CLI logos (styles.css).
*
* Every launch surface (toolbar Run menu, phone overview picker, Custom Endpoint
* rows, model picker) renders `<span class="run-mode-dot <id>">`, and styles.css
* turns that slot into the CLI's logo through a `--run-mode-logo` data URI plus
* one of two paint groups: brand-coloured marks paint as a background image,
* monochrome marks as a mask over the row's text colour. Three ways that breaks
* silently, each pinned here:
* - a new stock CLI lands with no logo rule (its row shows a bare grey dot);
* - a logo rule exists but the id is in neither paint group (the variable is
* set and never painted, so the slot is a blank 15px gap);
* - a later `background:` shorthand on `.run-mode-dot.<id>` (e.g. a skin
* override like the ones the non-og block used to carry) resets
* background-image and wipes the logo.
*
* Port: none (pure static analysis).
*/
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { describe, expect, it } from 'vitest';
import { STOCK_CLIS } from '../src/config/cli-registry/stock.js';
// Comments stripped: they sit between rules and carry commas, which would bleed
// into the selector lists split below.
const styles = readFileSync(resolve('src/web/public/styles.css'), 'utf8').replace(/\/\*[\s\S]*?\*\//g, '');
/** Every id a Run menu renders as `.run-mode-dot <id>`: the stock agents, Shell, and web tabs. */
const RUN_MENU_IDS = [
...STOCK_CLIS.filter((cli) => cli.kind === 'agent').map((cli) => cli.id as string),
'shell',
'web',
];
/** The selector list of the one rule whose body contains `marker`. */
function groupIds(marker: string): string[] {
const rule = [...styles.matchAll(/([^{}]+)\{([^{}]*)\}/g)].filter((m) => m[2].includes(marker));
expect(rule, `exactly one rule paints via "${marker}"`).toHaveLength(1);
return rule[0][1]
.split(',')
.map((s) => s.trim().match(/^\.run-mode-dot\.([a-z0-9-]+)$/)?.[1])
.filter((id): id is string => Boolean(id));
}
function logoSvg(id: string): string {
const m = styles.match(
new RegExp(`\\.run-mode-dot\\.${id} \\{ --run-mode-logo: url\\("data:image/svg\\+xml,([^"]+)"\\); \\}`)
);
expect(m, `.run-mode-dot.${id} has a --run-mode-logo rule`).not.toBeNull();
return decodeURIComponent(m![1]);
}
describe('Run menu CLI logos', () => {
const colourIds = groupIds('background: var(--run-mode-logo)');
const maskIds = groupIds('mask: var(--run-mode-logo)');
it.each(RUN_MENU_IDS)('%s has a logo that one paint group draws', (id) => {
const svg = logoSvg(id);
expect(svg.startsWith('<svg')).toBe(true);
expect(svg).toMatch(/viewBox='[\d. ]+'/);
expect(colourIds.includes(id) !== maskIds.includes(id), `${id} is in exactly one paint group`).toBe(true);
});
it('defines no logo that no surface renders, and no group member without a logo', () => {
expect([...colourIds, ...maskIds].sort()).toEqual([...RUN_MENU_IDS].sort());
});
it('carries no script or event handler inside a data URI', () => {
for (const id of RUN_MENU_IDS) {
const svg = logoSvg(id);
expect(svg).not.toMatch(/<script|\son[a-z]+=|javascript:|href=/i);
}
});
it('never resets a logo slot with a background shorthand outside the colour group', () => {
const offenders = [...styles.matchAll(/([^{}]+)\{([^{}]*)\}/g)].filter(
([, selector, body]) =>
/\.run-mode-dot\.[a-z]/.test(selector) &&
/(^|[;\s])background\s*:/.test(body) &&
!body.includes('var(--run-mode-logo)')
);
expect(offenders.map((m) => m[1].trim())).toEqual([]);
});
});