mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
fix(ui): theme stateful light surfaces
This commit is contained in:
@@ -212,7 +212,7 @@ Codeman is a Claude Code session manager with web interface and autonomous Ralph
|
||||
|
||||
Frontend JS modules have `@fileoverview` with `@dependency`/`@loadorder` tags. Load order: `constants.js`(1) → `i18n.js`(1.5) → `mobile-handlers.js`(2) → `voice-input.js`(3) → `notification-manager.js`(4) → `keyboard-accessory.js`(5) → `input-cjk.js`(5.5) → `sanitize-html.js`(5.6) → `app.js`(6) → `terminal-ui.js`(7) → `respawn-ui.js`(8) → `ralph-panel.js`(9) → `orchestrator-panel.js`(9.5) → `cron-ui.js`(9.7) → `settings-ui.js`(10) → `panels-ui.js`(11) → `ultracode-panel.js`(11.5) → `admin-ui.js`(11.7) → `session-ui.js`(12) → `ralph-wizard.js`(13) → `api-client.js`(14) → `subagent-windows.js`(15) → `ultracode-windows.js`(15.5) → `image-input.js`(16). `i18n.js` translates static + newly inserted application DOM while skipping terminal/response/file/user-name surfaces; `input-cjk.js` handles CJK IME composition via an always-visible textarea below the terminal (`window.cjkActive` blocks xterm's onData).
|
||||
|
||||
**Skins**: App chrome tokens live at the top of `public/styles.css`; the matching xterm ANSI palettes and light-skin contrast policy live in `public/terminal-ui.js`. Every new skin must also be added to the pre-paint allowlist and Settings picker in `public/index.html`. `test/skin-themes.test.ts` guards that four-way parity so reloads do not flash/fall back and teammate terminals match the main terminal.
|
||||
**Skins**: App chrome tokens live at the top of `public/styles.css`; the matching xterm ANSI palettes and light-skin contrast policy live in `public/terminal-ui.js`. Every new skin must also be added to the pre-paint allowlist and Settings picker in `public/index.html`. `test/skin-themes.test.ts` guards that four-way parity so reloads do not flash/fall back and teammate terminals match the main terminal. Stateful surfaces must use the same tokens too: the response viewer/code blocks, file/subagent previews, CJK/paste inputs, and the zero-lag input overlay. `applyTerminalSkin()` must call the overlay's `refreshFont()` because it caches the terminal foreground/background.
|
||||
|
||||
**Command palette + shortcut registry** (COD-151/153/157/192, #146): `Ctrl/Cmd/Alt+K` opens the session palette (fuzzy search over live sessions; "Browse all sessions" → the Session Manager modal backed by `GET /api/sessions/unified`); the quick-start case `<select>` is fronted by a searchable picker (`buildCasePickerOptions`/`formatCasePickerLabel` — remote cases render `name @ hostId`). Shortcuts live in a rebindable registry (`DEFAULT_SHORTCUTS`/`getShortcutRegistry()`/`matchesShortcutEvent()` in app.js; overrides persist under `settings.shortcutOverrides` via `saveAppSettingsToStorage`); App Settings → Shortcuts renders capture/disable rows; `Ctrl+?` opens the registry-driven overlay (footer links to the full `#helpModal` reference). ⚠️ Palette-chord keys must ALSO be swallowed in `attachCustomKeyEventHandler` (terminal-ui.js) or xterm writes the control byte (0x0B) into the PTY. ⚠️ `saveAppSettings()` rebuilds settings from the DOM — keys edited elsewhere (`shortcutOverrides`, `showTokenCount`, `showCost`) need explicit `_prev` carry-over.
|
||||
|
||||
@@ -230,7 +230,7 @@ Frontend JS modules have `@fileoverview` with `@dependency`/`@loadorder` tags. L
|
||||
|
||||
**Gesture-control source lives in-repo** at `packages/gesture-control/` (workspace package `codeman-gesture-control`, was the standalone `Ark0N/codeman-gesture-control` repo). The transport-agnostic core is `src/gesture/*` (MediaPipe GestureRecognizer → One-Euro-filtered cursor → pinch state machine); `src/codeman/entry.ts` is the Codeman _consumer_ that maps grab/drag/drop onto real `.session-tab`/toolbar buttons and is the bundle entry. **Edit there, then run `npm run build:gesture`** (`scripts/build-gesture-bundle.mjs` → esbuild bundles `entry.ts`, MediaPipe JS included, into `src/web/public/gesture/gesture-codeman.js`) and **commit the regenerated bundle** — the committed bundle is what dev/`tsx` serves (no bundler at runtime), and `scripts/build.mjs` reruns the same step so prod always reflects current source. The MediaPipe **wasm + model** are NOT bundled — loaded at runtime from same-origin `/gesture/wasm` + `/gesture/gesture_recognizer.task`, fetched by `scripts/fetch-gesture-assets.mjs` (gitignored, see Gotchas). `entry.ts` mounts `window.__codemanGesture = new GestureBridge()` idempotently at module-eval. A standalone vite playground (`npm run dev` in the package — fake tabs, no Codeman) lets you iterate on gesture _feel_ in isolation. ⚠️ Keep `MP_VERSION` in `fetch-gesture-assets.mjs` in sync with `@mediapipe/tasks-vision` in `packages/gesture-control/package.json`.
|
||||
|
||||
**Theme skins** (App Settings → Display): the `skin` setting selects a palette via a `data-skin` attribute on `<html>`. Values: `daylight-blue` (default), `daylight-green`, `og` (OG Codeman). CSS lives under `[data-skin="…"]` blocks in `styles.css`. To avoid a flash-of-wrong-theme, an **inline pre-paint script** in `index.html` (`<head>`) reads `localStorage['codeman:skin']` and sets `data-skin` before first paint; `settings-ui.js` `applySkin()` applies it live on save (sets `html[data-skin]` + `window.__codemanSkin`, syncs the standalone `codeman:skin` key with the settings blob, and calls terminal-ui.js `applyTerminalSkin()` to re-theme live terminals). `skin` is a **per-device/client-only** setting — it's destructured OUT of the server payload (settings-ui.js, alongside `localEchoEnabled`/`cjkInputEnabled`/`extendedKeyboardBar`), so it does NOT sync across devices.
|
||||
**Theme skins** (App Settings → Display): the `skin` setting selects a palette via a `data-skin` attribute on `<html>`. Values: `daylight-blue` (default), `daylight-green`, `og` (OG Codeman), plus the light palettes `paper-gray`, `solarized-light`, `catppuccin-latte`, and `rose-pine-dawn`. CSS lives under `[data-skin="…"]` blocks in `styles.css`. To avoid a flash-of-wrong-theme, an **inline pre-paint script** in `index.html` (`<head>`) reads `localStorage['codeman:skin']` and sets `data-skin` before first paint; `settings-ui.js` `applySkin()` applies it live on save (sets `html[data-skin]` + `window.__codemanSkin`, syncs the standalone `codeman:skin` key with the settings blob, and calls terminal-ui.js `applyTerminalSkin()` to re-theme live terminals and refresh the cached local-echo colors). `skin` is a **per-device/client-only** setting — it's destructured OUT of the server payload (settings-ui.js, alongside `localEchoEnabled`/`cjkInputEnabled`/`extendedKeyboardBar`), so it does NOT sync across devices.
|
||||
|
||||
**Custom branding + UI language** (App Settings → Display → Branding & Language): `displayName` is schema-validated (trimmed, 1–40 chars), server-synced, and changes user-facing browser branding/window titles only — NEVER rename npm package/CLI/API/storage/CSS/protocol identifiers. `language` is a per-device `en`/`zh-CN` display key, stripped from the server payload. `i18n.js` keeps English as the canonical source/fallback, observes newly inserted application DOM for dynamic copy, preserves source strings so live EN↔ZH switching is reversible, and skips terminal/response/file/session-name/user-content surfaces. User display names flow through `textContent`/attribute APIs and the server title's HTML escaper, never `innerHTML`.
|
||||
|
||||
|
||||
+130
-117
@@ -92,6 +92,22 @@
|
||||
--run-hover-a: #5cc4f5;
|
||||
--run-hover-b: #3aa3e2;
|
||||
--gear-hover: #2a93d0;
|
||||
|
||||
/* Compatibility aliases used by newer panels and overlays. Keep these
|
||||
references dynamic so every skin, including light skins, inherits the
|
||||
same semantic surface and text colors. */
|
||||
--bg-primary: var(--bg-dark);
|
||||
--bg-secondary: var(--bg-card);
|
||||
--bg-tertiary: var(--bg-input);
|
||||
--text-primary: var(--text);
|
||||
--text-secondary: var(--text-dim);
|
||||
--border-color: var(--border);
|
||||
--accent-color: var(--accent);
|
||||
--success: var(--green);
|
||||
--error: var(--red);
|
||||
--danger: var(--red);
|
||||
--font-mono: 'Fira Code', 'JetBrains Mono', 'SF Mono', Menlo, Monaco, monospace;
|
||||
--shadow-lg: var(--elevated-shadow);
|
||||
}
|
||||
|
||||
/* ===== Skin: Daylight Blue (current default) ===== */
|
||||
@@ -7650,7 +7666,7 @@ kbd {
|
||||
.subagent-id {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.75rem;
|
||||
color: white;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.subagent-status {
|
||||
@@ -7953,7 +7969,7 @@ kbd {
|
||||
.subagent-window-title .id {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.8rem;
|
||||
color: white;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.subagent-window-title .status {
|
||||
@@ -8002,8 +8018,8 @@ kbd {
|
||||
padding: 0.5rem;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.75rem;
|
||||
background: #111;
|
||||
color: #c8c8c8;
|
||||
background: var(--bg-dark);
|
||||
color: var(--text);
|
||||
min-height: 0; /* Allow flex child to shrink below content size */
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
@@ -8599,7 +8615,7 @@ kbd {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 0;
|
||||
background: #111;
|
||||
background: var(--bg-dark);
|
||||
}
|
||||
|
||||
.file-preview-body pre {
|
||||
@@ -8608,7 +8624,7 @@ kbd {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.5;
|
||||
color: #d4d4d4;
|
||||
color: var(--text);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
@@ -8695,7 +8711,7 @@ kbd {
|
||||
.log-viewer-window-title .filename {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.8rem;
|
||||
color: white;
|
||||
color: var(--text);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -9692,7 +9708,7 @@ kbd {
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.75rem;
|
||||
color: var(--green);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* Advanced Options */
|
||||
@@ -10223,10 +10239,10 @@ kbd {
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-height: 88vh;
|
||||
background: #14141f;
|
||||
border-top: 1px solid #2a2a3a;
|
||||
background: var(--floating-bg);
|
||||
border-top: 1px solid var(--border);
|
||||
border-radius: 14px 14px 0 0;
|
||||
box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.45);
|
||||
box-shadow: var(--elevated-shadow);
|
||||
z-index: 5000;
|
||||
flex-direction: column;
|
||||
transform: translateY(100%);
|
||||
@@ -10243,11 +10259,11 @@ kbd {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 20px;
|
||||
border-bottom: 1px solid #2a2a3a;
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #e8e8ec;
|
||||
color: var(--text);
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
@@ -10258,9 +10274,9 @@ kbd {
|
||||
}
|
||||
|
||||
.response-viewer-more {
|
||||
background: #2a2a4a;
|
||||
border: 1px solid #444;
|
||||
color: #aaa;
|
||||
background: var(--control-bg);
|
||||
border: 1px solid var(--control-border);
|
||||
color: var(--text-dim);
|
||||
font-size: 12px;
|
||||
padding: 3px 10px;
|
||||
border-radius: 4px;
|
||||
@@ -10268,13 +10284,13 @@ kbd {
|
||||
}
|
||||
|
||||
.response-viewer-more:active {
|
||||
background: #3a3a5a;
|
||||
background: var(--control-bg-hover);
|
||||
}
|
||||
|
||||
.response-viewer-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #888;
|
||||
color: var(--text-muted);
|
||||
font-size: 22px;
|
||||
cursor: pointer;
|
||||
padding: 0 4px;
|
||||
@@ -10286,9 +10302,9 @@ kbd {
|
||||
margin: 0 0 18px;
|
||||
padding: 14px 16px 16px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #252538;
|
||||
border: 1px solid var(--border);
|
||||
border-left-width: 3px;
|
||||
background: #181826;
|
||||
background: var(--bg-card);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -10299,14 +10315,14 @@ kbd {
|
||||
/* Distinct accent per role so threads are scannable at a glance */
|
||||
.rv-message:has(.rv-role-user),
|
||||
.rv-message.rv-msg-user {
|
||||
border-left-color: #7aa2ff;
|
||||
background: #16182a;
|
||||
border-left-color: var(--accent);
|
||||
background: rgba(var(--accent-rgb), 0.055);
|
||||
}
|
||||
|
||||
.rv-message:has(.rv-role-assistant),
|
||||
.rv-message.rv-msg-assistant {
|
||||
border-left-color: #6ddb7f;
|
||||
background: #161f1a;
|
||||
border-left-color: var(--green);
|
||||
background: color-mix(in srgb, var(--green) 6%, var(--bg-card));
|
||||
}
|
||||
|
||||
.rv-role {
|
||||
@@ -10318,17 +10334,17 @@ kbd {
|
||||
margin-bottom: 10px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
background: var(--control-bg);
|
||||
}
|
||||
|
||||
.rv-role-user {
|
||||
color: #7aa2ff;
|
||||
background: rgba(122, 162, 255, 0.12);
|
||||
color: var(--accent);
|
||||
background: rgba(var(--accent-rgb), 0.12);
|
||||
}
|
||||
|
||||
.rv-role-assistant {
|
||||
color: #6ddb7f;
|
||||
background: rgba(109, 219, 127, 0.12);
|
||||
color: var(--green);
|
||||
background: color-mix(in srgb, var(--green) 12%, transparent);
|
||||
}
|
||||
|
||||
/* Markdown rendered content inside response viewer.
|
||||
@@ -10353,7 +10369,7 @@ kbd {
|
||||
.rv-text h1, .rv-text h2, .rv-text h3, .rv-text h4,
|
||||
.response-viewer-body > h1, .response-viewer-body > h2,
|
||||
.response-viewer-body > h3, .response-viewer-body > h4 {
|
||||
color: #f2f2f6;
|
||||
color: var(--text);
|
||||
margin: 1.4em 0 0.5em;
|
||||
line-height: 1.3;
|
||||
font-weight: 700;
|
||||
@@ -10368,27 +10384,27 @@ kbd {
|
||||
.rv-text h1, .response-viewer-body > h1 {
|
||||
font-size: 1.55em;
|
||||
padding-bottom: 0.3em;
|
||||
border-bottom: 1px solid #2d2d40;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.rv-text h2, .response-viewer-body > h2 {
|
||||
font-size: 1.3em;
|
||||
color: #ffd27a;
|
||||
color: var(--yellow);
|
||||
}
|
||||
.rv-text h3, .response-viewer-body > h3 {
|
||||
font-size: 1.13em;
|
||||
color: #bfc8ff;
|
||||
color: var(--accent);
|
||||
}
|
||||
.rv-text h4, .response-viewer-body > h4 {
|
||||
font-size: 1em;
|
||||
color: #c9c9d5;
|
||||
color: var(--text-dim);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.rv-text code,
|
||||
.response-viewer-body > :not(pre) code {
|
||||
background: #262638;
|
||||
color: #ffb4a2;
|
||||
background: var(--bg-input);
|
||||
color: var(--red);
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Fira Code', 'JetBrains Mono', 'SF Mono', Menlo, Monaco, monospace;
|
||||
@@ -10401,14 +10417,14 @@ kbd {
|
||||
already matched via descendant; keep both in lockstep. */
|
||||
.rv-text pre,
|
||||
.response-viewer-body pre {
|
||||
background: #0f0f1a;
|
||||
border: 1px solid #2a2a3d;
|
||||
background: var(--bg-dark);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 14px 16px;
|
||||
overflow-x: auto;
|
||||
margin: 1em 0;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.02);
|
||||
box-shadow: inset 0 0 0 1px var(--control-border);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -10418,7 +10434,7 @@ kbd {
|
||||
.rv-text pre code,
|
||||
.response-viewer-body pre code {
|
||||
background: none;
|
||||
color: #e6e6f0;
|
||||
color: var(--text);
|
||||
padding: 0;
|
||||
font-family: 'Fira Code', 'JetBrains Mono', 'SF Mono', Menlo, Monaco, monospace;
|
||||
font-size: 12.5px;
|
||||
@@ -10468,10 +10484,7 @@ kbd {
|
||||
|
||||
.rv-text pre.rv-diagram.rv-nowrap,
|
||||
.response-viewer-body pre.rv-diagram.rv-nowrap {
|
||||
background:
|
||||
linear-gradient(to left, #0f0f1a 0, rgba(15, 15, 26, 0) 28px) right / 28px 100% no-repeat,
|
||||
linear-gradient(to left, rgba(122, 162, 255, 0.18) 0, rgba(15, 15, 26, 0) 28px) right / 28px 100% no-repeat,
|
||||
#0f0f1a;
|
||||
background: var(--bg-dark);
|
||||
}
|
||||
|
||||
/* Toggle button — pinned to the wrapper's top-right, NOT affected by <pre>'s
|
||||
@@ -10483,10 +10496,10 @@ kbd {
|
||||
width: 28px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
border: 1px solid #2f2f45;
|
||||
border: 1px solid var(--control-border);
|
||||
border-radius: 5px;
|
||||
background: rgba(20, 20, 32, 0.92);
|
||||
color: #8b8b97;
|
||||
background: var(--floating-bg);
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
@@ -10499,8 +10512,8 @@ kbd {
|
||||
|
||||
.rv-wrap-toggle:hover,
|
||||
.rv-wrap-toggle:active {
|
||||
color: #e0e0ec;
|
||||
border-color: #4a4a65;
|
||||
color: var(--text);
|
||||
border-color: var(--control-border-hover);
|
||||
}
|
||||
|
||||
/* Default icon = "return" (wrap is active). Clicking switches to expand/scroll. */
|
||||
@@ -10557,10 +10570,10 @@ kbd {
|
||||
width: 28px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
border: 1px solid #2f2f45;
|
||||
border: 1px solid var(--control-border);
|
||||
border-radius: 5px;
|
||||
background: rgba(20, 20, 32, 0.92);
|
||||
color: #8b8b97;
|
||||
background: var(--floating-bg);
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
@@ -10572,14 +10585,14 @@ kbd {
|
||||
|
||||
.rv-copy-btn:hover,
|
||||
.rv-copy-btn:active {
|
||||
color: #e0e0ec;
|
||||
border-color: #4a4a65;
|
||||
color: var(--text);
|
||||
border-color: var(--control-border-hover);
|
||||
}
|
||||
|
||||
.rv-copy-btn::before { content: '\2398'; } /* ⎘ — matches file-preview copy */
|
||||
.rv-copy-btn.rv-copied { color: #9ece6a; border-color: #3a5a3a; }
|
||||
.rv-copy-btn.rv-copied { color: var(--green); border-color: var(--green); }
|
||||
.rv-copy-btn.rv-copied::before { content: '\2713'; } /* ✓ */
|
||||
.rv-copy-btn.rv-copy-failed { color: #f7768e; border-color: #5a3a3a; }
|
||||
.rv-copy-btn.rv-copy-failed { color: var(--red); border-color: var(--red); }
|
||||
.rv-copy-btn.rv-copy-failed::before { content: '\2715'; } /* ✕ */
|
||||
|
||||
.rv-text ul, .rv-text ol,
|
||||
@@ -10597,36 +10610,36 @@ kbd {
|
||||
|
||||
.rv-text blockquote,
|
||||
.response-viewer-body > blockquote {
|
||||
border-left: 3px solid #5c7cfa;
|
||||
background: rgba(92, 124, 250, 0.06);
|
||||
border-left: 3px solid var(--accent);
|
||||
background: rgba(var(--accent-rgb), 0.06);
|
||||
margin: 0.8em 0;
|
||||
padding: 0.5em 14px;
|
||||
color: #b8b8c8;
|
||||
color: var(--text-dim);
|
||||
border-radius: 0 6px 6px 0;
|
||||
}
|
||||
|
||||
.rv-text strong,
|
||||
.response-viewer-body > p strong,
|
||||
.response-viewer-body > li strong {
|
||||
color: #ffffff;
|
||||
color: var(--text);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.rv-text em,
|
||||
.response-viewer-body em {
|
||||
color: #e0e0ec;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.rv-text a,
|
||||
.response-viewer-body a {
|
||||
color: #7aa2ff;
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid rgba(122, 162, 255, 0.35);
|
||||
border-bottom: 1px solid rgba(var(--accent-rgb), 0.35);
|
||||
}
|
||||
|
||||
.rv-text a:hover,
|
||||
.response-viewer-body a:hover {
|
||||
border-bottom-color: #7aa2ff;
|
||||
border-bottom-color: var(--accent);
|
||||
}
|
||||
|
||||
/* Tables — scroll wrapper keeps table proper while allowing horizontal overflow */
|
||||
@@ -10634,9 +10647,9 @@ kbd {
|
||||
margin: 1em 0;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
border: 1px solid #2a2a3d;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: #12121d;
|
||||
background: var(--bg-card);
|
||||
}
|
||||
|
||||
.rv-text table,
|
||||
@@ -10653,8 +10666,8 @@ kbd {
|
||||
.response-viewer-body > table th,
|
||||
.response-viewer-body > table td,
|
||||
.rv-table-wrap th, .rv-table-wrap td {
|
||||
border-bottom: 1px solid #252538;
|
||||
border-right: 1px solid #252538;
|
||||
border-bottom: 1px solid var(--border);
|
||||
border-right: 1px solid var(--border);
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
@@ -10677,29 +10690,29 @@ kbd {
|
||||
.rv-text th,
|
||||
.response-viewer-body > table th,
|
||||
.rv-table-wrap th {
|
||||
background: #20202e;
|
||||
color: #f0f0f5;
|
||||
background: var(--bg-input);
|
||||
color: var(--text);
|
||||
font-weight: 600;
|
||||
border-bottom: 2px solid #2f2f45;
|
||||
border-bottom: 2px solid var(--border-light);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.rv-text tbody tr:nth-child(even) td,
|
||||
.response-viewer-body > table tbody tr:nth-child(even) td,
|
||||
.rv-table-wrap tbody tr:nth-child(even) td {
|
||||
background: rgba(255, 255, 255, 0.022);
|
||||
background: var(--control-bg);
|
||||
}
|
||||
|
||||
.rv-text tbody tr:hover td,
|
||||
.response-viewer-body > table tbody tr:hover td,
|
||||
.rv-table-wrap tbody tr:hover td {
|
||||
background: rgba(122, 162, 255, 0.06);
|
||||
background: rgba(var(--accent-rgb), 0.06);
|
||||
}
|
||||
|
||||
.rv-text hr,
|
||||
.response-viewer-body > hr {
|
||||
border: none;
|
||||
border-top: 1px solid #2d2d40;
|
||||
border-top: 1px solid var(--border);
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
|
||||
@@ -10715,7 +10728,7 @@ kbd {
|
||||
'Noto Sans CJK SC', sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 1.7;
|
||||
color: #d8d8e0;
|
||||
color: var(--text);
|
||||
/* Comfortable reading width on wider viewports */
|
||||
--rv-content-max: 720px;
|
||||
}
|
||||
@@ -10734,7 +10747,7 @@ kbd {
|
||||
|
||||
.response-viewer-body:empty::after {
|
||||
content: 'No response yet';
|
||||
color: #555;
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -10742,7 +10755,7 @@ kbd {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
background: var(--modal-backdrop);
|
||||
z-index: 4999;
|
||||
}
|
||||
|
||||
@@ -10760,9 +10773,9 @@ kbd {
|
||||
width: 100%;
|
||||
font-family: 'Fira Code', 'Cascadia Code', 'JetBrains Mono', 'SF Mono', Monaco, monospace;
|
||||
font-size: 14px;
|
||||
background: #1a1a2e;
|
||||
color: #e0e0e0;
|
||||
border: 1px solid #333;
|
||||
background: var(--bg-input);
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
border-top: none;
|
||||
padding: 6px 10px;
|
||||
outline: none;
|
||||
@@ -10772,12 +10785,12 @@ kbd {
|
||||
}
|
||||
|
||||
#cjkInput:focus {
|
||||
border-color: #339af0;
|
||||
background: #111;
|
||||
border-color: var(--accent);
|
||||
background: var(--bg-card);
|
||||
}
|
||||
|
||||
#cjkInput::placeholder {
|
||||
color: #495057;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@@ -10791,12 +10804,12 @@ kbd {
|
||||
min-height: 34px;
|
||||
max-height: 68px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid rgba(80, 120, 190, 0.55);
|
||||
border: 1px solid var(--control-border-hover);
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
background: #101827;
|
||||
color: #f3f4f6;
|
||||
box-shadow: 0 -8px 20px rgba(0, 0, 0, 0.35);
|
||||
background: var(--floating-bg);
|
||||
color: var(--text);
|
||||
box-shadow: var(--subtle-shadow);
|
||||
transition: transform 0.15s ease-out;
|
||||
will-change: transform;
|
||||
}
|
||||
@@ -10856,10 +10869,10 @@ body.touch-device.cjk-input-visible .main {
|
||||
flex-shrink: 0;
|
||||
gap: 4px;
|
||||
padding: 6px 12px;
|
||||
background: #2a2a2a;
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
background: var(--control-bg);
|
||||
border: 1px solid var(--control-border);
|
||||
border-radius: 6px;
|
||||
color: #e5e5e5;
|
||||
color: var(--text-dim);
|
||||
font-size: 0.65rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
@@ -10867,13 +10880,13 @@ body.touch-device.cjk-input-visible .main {
|
||||
}
|
||||
|
||||
.accessory-btn.confirming {
|
||||
background: #6b4f00;
|
||||
border-color: #b8860b;
|
||||
color: #ffd54f;
|
||||
background: color-mix(in srgb, var(--yellow) 18%, var(--bg-input));
|
||||
border-color: var(--yellow);
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
.accessory-btn:active {
|
||||
background: #3a3a3a;
|
||||
background: var(--control-bg-hover);
|
||||
}
|
||||
|
||||
.accessory-btn svg {
|
||||
@@ -10883,13 +10896,13 @@ body.touch-device.cjk-input-visible .main {
|
||||
|
||||
.accessory-btn-arrow {
|
||||
padding: 6px 10px;
|
||||
background: #2563eb;
|
||||
border-color: rgba(59, 130, 246, 0.5);
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent-ink);
|
||||
}
|
||||
|
||||
.accessory-btn-arrow:active {
|
||||
background: #1d4ed8;
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
|
||||
.accessory-btn-dismiss {
|
||||
@@ -10897,9 +10910,9 @@ body.touch-device.cjk-input-visible .main {
|
||||
flex: 1 1 0;
|
||||
max-width: 100px;
|
||||
padding: 10px 8px;
|
||||
background: #2563eb;
|
||||
border-color: rgba(59, 130, 246, 0.5);
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent-ink);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -10909,7 +10922,7 @@ body.touch-device.cjk-input-visible .main {
|
||||
}
|
||||
|
||||
.accessory-btn-dismiss:active {
|
||||
background: #1d4ed8;
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════
|
||||
@@ -10929,8 +10942,8 @@ body.touch-device.cjk-input-visible .main {
|
||||
}
|
||||
|
||||
.paste-dialog {
|
||||
background: var(--bg-secondary, #1e1e2e);
|
||||
border: 1px solid var(--border-color, #444);
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
width: calc(100% - 24px);
|
||||
@@ -10941,9 +10954,9 @@ body.touch-device.cjk-input-visible .main {
|
||||
width: 100%;
|
||||
min-height: 80px;
|
||||
max-height: 200px;
|
||||
background: var(--bg-primary, #0d0d14);
|
||||
color: var(--text-primary, #e0e0e0);
|
||||
border: 1px solid var(--border-color, #444);
|
||||
background: var(--bg-input);
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
font-family: inherit;
|
||||
@@ -10954,7 +10967,7 @@ body.touch-device.cjk-input-visible .main {
|
||||
|
||||
.paste-textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color, #7aa2f7);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.paste-actions {
|
||||
@@ -10974,24 +10987,24 @@ body.touch-device.cjk-input-visible .main {
|
||||
|
||||
.paste-image {
|
||||
margin-right: auto;
|
||||
background: var(--bg-tertiary, #333);
|
||||
color: var(--accent-color, #7aa2f7);
|
||||
border: 1px solid var(--accent-color, #7aa2f7);
|
||||
background: var(--bg-input);
|
||||
color: var(--accent);
|
||||
border: 1px solid var(--accent);
|
||||
}
|
||||
|
||||
.paste-cancel {
|
||||
background: var(--bg-tertiary, #333);
|
||||
color: var(--text-secondary, #aaa);
|
||||
background: var(--bg-input);
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.paste-new {
|
||||
background: var(--bg-tertiary, #333);
|
||||
color: var(--accent-color, #7aa2f7);
|
||||
border: 1px solid var(--accent-color, #7aa2f7);
|
||||
background: var(--bg-input);
|
||||
color: var(--accent);
|
||||
border: 1px solid var(--accent);
|
||||
}
|
||||
|
||||
.paste-send {
|
||||
background: var(--accent-color, #7aa2f7);
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -2863,6 +2863,10 @@ Object.assign(CodemanApp.prototype, {
|
||||
if (this.terminal) {
|
||||
this.terminal.options.minimumContrastRatio = minimumContrastRatio;
|
||||
this.terminal.options.theme = theme;
|
||||
// The zero-lag typing overlay caches the xterm foreground/background.
|
||||
// Refresh it on live skin changes so typed text never keeps the prior
|
||||
// theme's dark backing surface or foreground color.
|
||||
this._localEchoOverlay?.refreshFont();
|
||||
try {
|
||||
this.terminal.refresh(0, this.terminal.rows - 1);
|
||||
} catch {}
|
||||
|
||||
@@ -74,20 +74,33 @@ describe('Codeman light skins', () => {
|
||||
it('switches live terminals between light and dark contrast policies', () => {
|
||||
const main = { options: {} as Record<string, unknown>, rows: 24, refresh: vi.fn() };
|
||||
const teammate = { options: {} as Record<string, unknown>, rows: 12, refresh: vi.fn() };
|
||||
const refreshFont = vi.fn();
|
||||
const app = {
|
||||
terminal: main,
|
||||
teammateTerminals: new Map([['agent-1', { terminal: teammate }]]),
|
||||
_localEchoOverlay: { refreshFont },
|
||||
};
|
||||
|
||||
terminal.mixin.applyTerminalSkin.call(app, 'paper-gray');
|
||||
expect(main.options.minimumContrastRatio).toBe(4.5);
|
||||
expect(teammate.options.minimumContrastRatio).toBe(4.5);
|
||||
expect((main.options.theme as Record<string, string>).background).toBe('#f6f8fa');
|
||||
expect(refreshFont).toHaveBeenCalledTimes(1);
|
||||
|
||||
terminal.mixin.applyTerminalSkin.call(app, 'daylight-blue');
|
||||
expect(main.options.minimumContrastRatio).toBe(1);
|
||||
expect(teammate.options.minimumContrastRatio).toBe(1);
|
||||
expect((main.options.theme as Record<string, string>).background).toBe('#161b23');
|
||||
expect(refreshFont).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('themes stateful input and response surfaces instead of pinning dark colors', () => {
|
||||
expect(stylesSource).toContain('background: var(--bg-input);\n color: var(--text);');
|
||||
expect(stylesSource).toMatch(/#cjkInput \{[\s\S]*?background: var\(--bg-input\);[\s\S]*?color: var\(--text\);/);
|
||||
expect(stylesSource).toMatch(/\.response-viewer \{[\s\S]*?background: var\(--floating-bg\);/);
|
||||
expect(stylesSource).toMatch(/\.response-viewer-body pre \{[\s\S]*?background: var\(--bg-dark\);/);
|
||||
expect(stylesSource).toMatch(/\.response-viewer-body pre code \{[\s\S]*?color: var\(--text\);/);
|
||||
expect(stylesSource).toMatch(/\.file-preview-body \{[\s\S]*?background: var\(--bg-dark\);/);
|
||||
});
|
||||
|
||||
it('uses skin variables for the pre-paint skeleton and native controls', () => {
|
||||
|
||||
Reference in New Issue
Block a user