fix(cli-registry): merge-time fixes for the run-button accents (#463)

- mobile.css: gemini and antigravity run/gear rules get `!important` like
  pi/omp/grok/deepseek, so the gear half no longer keeps the skin accent
  while the body takes the mode colour (two-tone button on the default skin).
- test/skin-themes.test.ts: static guard that every run mode with a base
  `.btn-toolbar.btn-run.mode-<id>` rule also has a resting rule inside the
  `html:not([data-skin="og"])` block; ids are derived from the stylesheet.
- stock.ts: grok's accent comment names zinc-300 (border/badge colour);
  gemini's accent is #8ab4f8 to match its tab badge and run-mode dot, noted
  as the one exception to the border-colour method.
- types.ts: "(below)" -> "(above)".
- docs/cli-registry.md, CLAUDE.md: `accent` is now measured, not transcribed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-09-23 11:40:03 +02:00
parent 13e652e43f
commit 10f87428c3
6 changed files with 58 additions and 24 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -151,7 +151,7 @@ This matters because it is invisible when it is wrong. `capabilities.privilegedP
`shortBadge`, `accent`, `capabilities.echo`, `capabilities.wheelForward`, `capabilities.keyboardAccessory` and `capabilities.maxFrameBytes` are **declared but not yet read**. They all describe frontend behaviour, and the frontend is deliberately untouched here: `app.js`, `terminal-ui.js` and `styles.css` keep their own hand-authored per-CLI rules, and moving them is its own piece of work verified by a browser/mobile suite the CI gate cannot see.
Treat those values as **transcribed, not authoritative** — nothing enforces that `echo.policy` matches `_updateLocalEchoState`'s fallthrough, or that `accent` matches the gradient CSS paints, so re-measure before wiring one up. A field that is both wrong and unread is worse than an absent one, because the next reader trusts it; `test/cli-registry-no-id-branching.test.ts` pins the list so it cannot quietly grow, and wiring one up makes its line there fail, which is the direction you want.
Treat those values as **transcribed, not authoritative** — nothing enforces that `echo.policy` matches `_updateLocalEchoState`'s fallthrough, so re-measure before wiring one up. `accent` is the one exception: it was measured against styles.css on 2026-09-21 (method in the comment above `CLAUDE` in `stock.ts`), though nothing keeps it in step with the CSS either. A field that is both wrong and unread is worse than an absent one, because the next reader trusts it; `test/cli-registry-no-id-branching.test.ts` pins the list so it cannot quietly grow, and wiring one up makes its line there fail, which is the direction you want.
`overlays.credStore` is in the same category, for a sharper reason: the Docker credential-seeding path still reads its own `CRED_STORES` table, because this shape allows ONE store per CLI and the live table needs two for gemini (`.gemini` for the CLI's own auth plus `.config/gcloud` for Vertex), while deepseek declares none here even though `.dsh` is seeded. Wiring it means making the field an array and correcting those two entries — a change to credential seeding, which is simultaneously the worst thing here to get wrong and the least covered by tests, since every docker IO path is no-op'd under vitest.
+9 -5
View File
@@ -83,7 +83,10 @@ function agentDefaults(): Pick<
// (e.g. claude was registered as Anthropic's brand orange, `#d97757`, but the
// button renders blue): `docs/cli-registry.md`'s own "transcribed, not
// authoritative, re-measure before wiring one up" warning for this
// DECLARED-FOR-LATER field, taken literally. This is a data-accuracy fix only —
// DECLARED-FOR-LATER field, taken literally. The one exception is GEMINI, whose
// run-button border (#60a5fa) is the only one that disagrees with its own tab badge
// and run-mode dot (#8ab4f8); it takes the badge colour, so every accent names the
// same hex the frontend uses as that CLI's flat identity. This is a data-accuracy fix only —
// `accent` still has no reader, so nothing rendered changes because of it.
const CLAUDE: CliEntry = {
id: 'claude' as CliEntry['id'],
@@ -630,7 +633,8 @@ const GEMINI: CliEntry = {
id: 'gemini' as CliEntry['id'],
label: 'Gemini',
shortBadge: 'GM',
accent: '#60a5fa',
// The tab badge / run-mode-dot colour, not the run-button border (see the note above CLAUDE).
accent: '#8ab4f8',
enabled: true,
stock: true,
order: 30,
@@ -916,9 +920,9 @@ const GROK: CliEntry = {
shortBadge: 'GK',
// Upstream hand-authored a charcoal GRADIENT across 4+ CSS spots (welcome button, tab
// badge, run-mode dot, mobile skin overrides) rather than one flat colour; our registry's
// `accent` is a single hex, so this is the closest single value (the run-mode-dot colour,
// zinc-400). Nothing reads `accent` yet — the frontend is untouched in this change and
// keeps its own hand-authored CSS; the field is here so the entry is complete.
// `accent` is a single hex, so this is the closest single value (zinc-300, the run-button
// border and tab-badge colour). Nothing reads `accent` yet: the frontend keeps its own
// hand-authored CSS; the field is here so the entry is complete.
accent: '#d4d4d8',
enabled: true,
stock: true,
+1 -1
View File
@@ -695,7 +695,7 @@ export interface CliEntry {
/**
* Single hex colour, measured from the CLI's actual `.btn-toolbar.btn-run.mode-<id>`
* gradient in styles.css (see stock.ts's comment above `CLAUDE` for the exact
* methodology). DECLARED-FOR-LATER (below) — no code reads this yet; styles.css's
* methodology). DECLARED-FOR-LATER (above) — no code reads this yet; styles.css's
* gradients are still hand-authored per id, not derived from this field via any
* CSS custom property. There is no `--cli-accent` variable in the codebase.
*/
+17 -16
View File
@@ -949,40 +949,41 @@ html.mobile-init .file-browser-panel {
border-color: rgba(16, 185, 129, 0.5);
}
/* Gemini mode colors on mobile */
/* Gemini mode colors on mobile. Same `!important` rationale as the pi block below. */
.btn-toolbar.btn-run.mode-gemini,
.btn-toolbar.btn-run-gear.mode-gemini {
background: #10243f;
border-color: rgba(96, 165, 250, 0.3);
color: #dbeafe;
background: #10243f !important;
border-color: rgba(96, 165, 250, 0.3) !important;
color: #dbeafe !important;
}
.btn-toolbar.btn-run.mode-gemini:active,
.btn-toolbar.btn-run-gear.mode-gemini:active {
background: #174ea6;
border-color: rgba(96, 165, 250, 0.5);
background: #174ea6 !important;
border-color: rgba(96, 165, 250, 0.5) !important;
}
/* Antigravity mode colors on mobile */
/* Antigravity mode colors on mobile. Same `!important` rationale as the pi block below. */
.btn-toolbar.btn-run.mode-antigravity,
.btn-toolbar.btn-run-gear.mode-antigravity {
background: #0b2b33;
border-color: rgba(34, 211, 238, 0.3);
color: #cffafe;
background: #0b2b33 !important;
border-color: rgba(34, 211, 238, 0.3) !important;
color: #cffafe !important;
}
.btn-toolbar.btn-run.mode-antigravity:active,
.btn-toolbar.btn-run-gear.mode-antigravity:active {
background: #0e7490;
border-color: rgba(34, 211, 238, 0.5);
background: #0e7490 !important;
border-color: rgba(34, 211, 238, 0.5) !important;
}
/* Pi mode colors on mobile.
`!important` is load-bearing here, not noise: styles.css nests its skin rules
inside `html:not([data-skin="og"])`, so a bare `.btn-toolbar.btn-run` in there
resolves to (0,2,1) and outranks this (0,2,0) `.mode-pi` pair regardless of
load order. The antigravity block right above omits it and is consequently
dead on every non-og skin (i.e. on the default) — do not copy that. */
inside `html:not([data-skin="og"])`, so a bare `.btn-toolbar.btn-run` or
`.btn-toolbar.btn-run-gear` in there outranks this `.mode-pi` pair regardless
of load order. Without it the gear half keeps the skin accent while the body
takes the nested block's mode colour, so the split button renders two-tone
(gemini and antigravity shipped that way until they got `!important` too). */
.btn-toolbar.btn-run.mode-pi,
.btn-toolbar.btn-run-gear.mode-pi {
background: #33121f !important;
+29
View File
@@ -176,4 +176,33 @@ describe('Codeman light skins', () => {
expect(mobileStylesSource).toContain(':is(.header, .toolbar, .keyboard-accessory-bar)');
expect(mobileStylesSource).toContain(':is(.case-settings-popover-mobile, .mobile-case-picker-sheet)');
});
it('re-declares every run-mode colour inside the non-og skin block', () => {
// The skin block nests under `html:not([data-skin="og"])`, so its generic
// `.btn-toolbar.btn-run` outranks a base-sheet `.mode-<id>` pair. A mode with no
// resting rule of its own in there (a `:hover` alone does not count) renders as generic claude blue on the DEFAULT skin
// (gemini, antigravity and omp all shipped that way). Ids come from the sheet.
const css = stylesSource.replace(/\/\*[\s\S]*?\*\//g, '');
const opener = 'html:not([data-skin="og"]) {';
const start = css.indexOf(opener);
expect(start).toBeGreaterThan(-1);
let depth = 0;
let end = -1;
for (let i = start + opener.length - 1; i < css.length; i++) {
if (css[i] === '{') depth++;
else if (css[i] === '}' && --depth === 0) {
end = i;
break;
}
}
expect(end).toBeGreaterThan(start);
const nested = css.slice(start, end);
const base = css.slice(0, start) + css.slice(end);
const ids = (text: string) =>
new Set([...text.matchAll(/\.btn-toolbar\.btn-run\.mode-([\w-]+)(?![\w-]|:)/g)].map((m) => m[1]));
const baseIds = [...ids(base)];
expect(baseIds.length).toBeGreaterThanOrEqual(5);
const nestedIds = ids(nested);
expect(baseIds.filter((id) => !nestedIds.has(id))).toEqual([]);
});
});