mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 23:19:43 +02:00
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:
@@ -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([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user