mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 18:39:33 +01:00
chore(web): remove the teammate terminal windows nothing can open
A subagent window could host an xterm attached to a teammate's tmux pane, but only for a teammate listed in teammatePanesByName, and the SSE listeners that filled that map were removed in February. Since then the branch could not be taken, and the code behind it was unreachable: - initTeammateTerminal and the lazy dispose / restore pair that parked a teammate terminal while its window was minimized or hidden; - the two maps (teammatePanesByName, teammateTerminals) and every loop over them: window close and reconnect cleanup, the live font-weight save and the skin switch; - the two early returns in renderSubagentWindowContent that protected a terminal which could not exist. Subagent windows render their activity list exactly as before, and a teammate still gets its name and badge from the agent description. The server side is untouched: TeamWatcher still holds respawn back while teammates are active, and the teammate-pane routes remain. Tests and docs that described "Agent Teams panes" now name what a font or skin change really reaches: the main terminal, the tiles and the split pane. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
1 parent
259cabd738
commit
d44b049c51
9 files changed
+14
-344
No files matched your search
+1
-1
@@ -62,7 +62,7 @@ Frontend JS modules have `@fileoverview` with `@dependency`/`@loadorder` tags. L
|
||||
|
||||
**Header button visibility**: most header controls are opt-in and hidden by a marker class (`btn-multimonitor--hidden`, `btn-response-viewer-header--hidden`, `btn-file-viewer--hidden`, `btn-cron--hidden`, and the desktop-gated `btn-split--hidden` / `btn-tile-grid--hidden`, which also need a `@media (max-width: 1179px)` backstop) that `applyHeaderVisibilitySettings()` (settings-ui.js) toggles after settings load; the multi-monitor button is instead stripped at render by `renderIndexHtml`. ⚠️ Hiding must go through the marker class: the base rules are `display:inline-flex !important`, so an inline style cannot override them. Current desktop default is WS/CPU/MEM + File Viewer + Tiles (1180px and wider) + gear, with the token chip and lifecycle-log button OFF. ⚠️ New header controls must not leak onto phones; `test/mobile-header-buttons-policy.test.ts` is the static guard. The one opt-in exception is File Viewer (#759): `mobile.css` no longer hides it at phone width, so it stays off a phone only through the handheld default in `getDefaultSettings()` and the `html.mobile-init` startup rule (both pinned by that test), and shows once the setting is switched on on that phone. → [architecture-invariants#header-button-visibility-multi-monitor-response-viewer-file-viewer-cron](../../docs/architecture-invariants.md#header-button-visibility-multi-monitor-response-viewer-file-viewer-cron)
|
||||
|
||||
**Terminal font weight** (`terminalFontWeight` / `terminalFontWeightBold`, per-device, default = xterm's own `normal`/`bold`): Claude Code's markdown bold is a bare `ESC[1m`, so the weight step is its only cue. `CodemanTerminalFont.resolveWeights()` (constants.js, pure) resolves each slot against **its own** xterm default. ⚠️ The `@font-face` for `fonts/jetbrains-mono-variable.woff2` must stay declared `100 800` (the browser synthesizes from the descriptor, not the file); narrowing it silently makes the setting a no-op. ⚠️ A live save must reach both echo overlays (`refreshFont()`) and open Agent Teams panes. ⚠️ Leave `_awaitTerminalFont()` untouched. Test: `test/terminal-font-weight.test.ts`. → [architecture-invariants#terminal-font-weight](../../docs/architecture-invariants.md#terminal-font-weight)
|
||||
**Terminal font weight** (`terminalFontWeight` / `terminalFontWeightBold`, per-device, default = xterm's own `normal`/`bold`): Claude Code's markdown bold is a bare `ESC[1m`, so the weight step is its only cue. `CodemanTerminalFont.resolveWeights()` (constants.js, pure) resolves each slot against **its own** xterm default. ⚠️ The `@font-face` for `fonts/jetbrains-mono-variable.woff2` must stay declared `100 800` (the browser synthesizes from the descriptor, not the file); narrowing it silently makes the setting a no-op. ⚠️ A live save must reach both echo overlays (`refreshFont()`) and every open tile and split pane (`_forEachTile`). ⚠️ Leave `_awaitTerminalFont()` untouched. Test: `test/terminal-font-weight.test.ts`. → [architecture-invariants#terminal-font-weight](../../docs/architecture-invariants.md#terminal-font-weight)
|
||||
|
||||
**Theme skins / branding / i18n**: `skin` selects a palette via `data-skin` on `<html>`, applied by an **inline pre-paint script** in `index.html` reading `localStorage['codeman:skin']` to avoid a flash of wrong theme. ⚠️ A skin is **four things that must stay in sync**, and missing any one degrades silently: the `html[data-skin="…"]` token block in `styles.css`, the xterm ANSI palette in `terminal-ui.js`, the pre-paint allowlist, and the Settings picker (both in `index.html`). `test/skin-themes.test.ts` is the static guard. Light skins additionally need `color-scheme: light` and xterm `minimumContrastRatio: 4.5`, and `applyTerminalSkin()` must call the local-echo overlay's `refreshFont()` because it caches the terminal fg/bg. `displayName` changes user-facing browser branding only and must NEVER rename npm package, CLI, API, storage, CSS, or protocol identifiers. `language` (`en`/`zh-CN`) keeps English as the canonical source so live switching stays reversible. User display names flow through `textContent`/attribute APIs and the server title's HTML escaper, never `innerHTML`. → [architecture-invariants#theme-skins](../../docs/architecture-invariants.md#theme-skins)
|
||||
|
||||
|
||||
@@ -855,10 +855,6 @@ class CodemanApp {
|
||||
// Once set, never recalculated. Persisted to localStorage and server.
|
||||
this.subagentParentMap = new Map();
|
||||
|
||||
// Teammate tmux pane terminals (Agent Teams feature)
|
||||
this.teammatePanesByName = new Map(); // Map<name, { paneTarget, sessionId, color }>
|
||||
this.teammateTerminals = new Map(); // Map<agentId, { terminal, fitAddon, paneTarget, sessionId, resizeObserver }>
|
||||
|
||||
this.terminalBufferCache = new Map(); // Map<sessionId, string> — client-side cache for instant tab re-visits (max 20)
|
||||
|
||||
this.ralphStatePanelCollapsed = true; // Default to collapsed
|
||||
|
||||
+2
-160
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @fileoverview Subagent panel (discovery, detail view, kill), subagent parent tracking,
|
||||
* agent teams (tasks panel, teammate badges, terminals), project insights (bash tool tracking),
|
||||
* agent teams (teammate badges), project insights (bash tool tracking),
|
||||
* file browser (directory tree, preview), log viewer (floating file streamers),
|
||||
* image popups (auto-popup for screenshots), mux sessions, monitor panel,
|
||||
* token statistics, toast notifications, and system stats.
|
||||
@@ -2135,16 +2135,10 @@ Object.assign(CodemanApp.prototype, {
|
||||
// Show window (unless it was minimized by user)
|
||||
if (!windowInfo.minimized) {
|
||||
windowInfo.element.style.display = 'flex';
|
||||
// Lazily re-create teammate terminal if it was disposed when hidden
|
||||
if (windowInfo._lazyTerminal) {
|
||||
this._restoreTeammateTerminalFromLazy(agentId);
|
||||
}
|
||||
}
|
||||
windowInfo.hidden = false;
|
||||
} else {
|
||||
// Hide window (but don't close it)
|
||||
// Dispose teammate terminal to free memory while hidden on inactive tab
|
||||
this._disposeTeammateTerminalForMinimize(agentId);
|
||||
windowInfo.element.style.display = 'none';
|
||||
windowInfo.hidden = true;
|
||||
}
|
||||
@@ -2210,23 +2204,12 @@ Object.assign(CodemanApp.prototype, {
|
||||
windowData.element.remove();
|
||||
this.subagentWindows.delete(agentId);
|
||||
}
|
||||
// Clean up teammate terminal if present
|
||||
const termData = this.teammateTerminals.get(agentId);
|
||||
if (termData) {
|
||||
if (termData.resizeObserver) {
|
||||
termData.resizeObserver.disconnect();
|
||||
}
|
||||
if (termData.terminal) {
|
||||
try { termData.terminal.dispose(); } catch {}
|
||||
}
|
||||
this.teammateTerminals.delete(agentId);
|
||||
}
|
||||
},
|
||||
|
||||
// Debounced wrapper — coalesces rapid subagent events (tool_call, progress,
|
||||
// message) into a single DOM update per 100ms per agent window.
|
||||
scheduleSubagentWindowRender(agentId) {
|
||||
// Skip DOM updates for windows with lazy (disposed) terminals — they're minimized
|
||||
// Skip DOM updates for minimized windows
|
||||
const windowData = this.subagentWindows.get(agentId);
|
||||
if (windowData?.minimized) return;
|
||||
|
||||
@@ -2241,12 +2224,6 @@ Object.assign(CodemanApp.prototype, {
|
||||
},
|
||||
|
||||
renderSubagentWindowContent(agentId) {
|
||||
// Skip if this window has a live terminal (don't overwrite xterm with activity HTML)
|
||||
if (this.teammateTerminals.has(agentId)) return;
|
||||
// Skip if this window has a lazy (disposed) terminal — it will be re-created on restore
|
||||
const windowData = this.subagentWindows.get(agentId);
|
||||
if (windowData?._lazyTerminal) return;
|
||||
|
||||
const body = document.getElementById(`subagent-window-body-${agentId}`);
|
||||
if (!body) return;
|
||||
|
||||
@@ -2399,141 +2376,6 @@ Object.assign(CodemanApp.prototype, {
|
||||
// Agent Teams
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/** Initialize an xterm.js terminal for a teammate's tmux pane */
|
||||
initTeammateTerminal(agentId, paneInfo, windowElement) {
|
||||
const body = windowElement.querySelector('.subagent-window-body');
|
||||
if (!body) return;
|
||||
|
||||
// Clear the activity log content
|
||||
body.innerHTML = '';
|
||||
body.classList.add('teammate-terminal-body');
|
||||
windowElement.classList.add('has-terminal');
|
||||
|
||||
const sessionId = paneInfo.sessionId;
|
||||
|
||||
// Buffer incoming terminal data until xterm is ready
|
||||
const pendingData = [];
|
||||
this.teammateTerminals.set(agentId, {
|
||||
terminal: null,
|
||||
fitAddon: null,
|
||||
paneTarget: paneInfo.paneTarget,
|
||||
sessionId,
|
||||
resizeObserver: null,
|
||||
pendingData,
|
||||
});
|
||||
|
||||
// Defer terminal creation to next frame so the body element has computed dimensions
|
||||
requestAnimationFrame(() => {
|
||||
// Safety: if window was closed before we got here, bail out
|
||||
if (!document.contains(body)) {
|
||||
this.teammateTerminals.delete(agentId);
|
||||
return;
|
||||
}
|
||||
|
||||
const fontSettings = this.loadAppSettingsFromStorage?.() || {};
|
||||
const terminal = new Terminal({
|
||||
theme: { ...window.codemanCurrentXtermTheme() },
|
||||
minimumContrastRatio: window.codemanCurrentSkinIsLight() ? 4.5 : 1,
|
||||
fontFamily: window.CodemanTerminalFont.resolve(fontSettings.terminalFontFamily),
|
||||
// A pane opened after a weight change must match the main terminal;
|
||||
// one open across the change is repainted by applyTerminalFontWeights().
|
||||
...window.CodemanTerminalFont.resolveWeights(fontSettings),
|
||||
fontSize: 12,
|
||||
lineHeight: 1.2,
|
||||
cursorBlink: true,
|
||||
cursorStyle: 'block',
|
||||
scrollback: DEFAULT_SCROLLBACK,
|
||||
allowTransparency: true,
|
||||
allowProposedApi: true,
|
||||
});
|
||||
|
||||
const fitAddon = new FitAddon.FitAddon();
|
||||
terminal.loadAddon(fitAddon);
|
||||
|
||||
if (typeof Unicode11Addon !== 'undefined') {
|
||||
try {
|
||||
const unicode11Addon = new Unicode11Addon.Unicode11Addon();
|
||||
terminal.loadAddon(unicode11Addon);
|
||||
terminal.unicode.activeVersion = '11';
|
||||
} catch (_e) { /* Unicode11 addon failed */ }
|
||||
}
|
||||
|
||||
try {
|
||||
terminal.open(body);
|
||||
} catch (err) {
|
||||
console.warn('[TeammateTerminal] Failed to open terminal:', err);
|
||||
this.teammateTerminals.delete(agentId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+Z (SIGTSTP/job-control suspend): a teammate/subagent pane is always
|
||||
// running an agent CLI (Task-tool dispatched, never a plain shell), so
|
||||
// unlike the main terminal's mode-gated trap this one is unconditional.
|
||||
// Mirrors the main terminal's guard in terminal-ui.js's
|
||||
// attachCustomKeyEventHandler — case-insensitive so Caps Lock (which
|
||||
// flips ev.key to 'Z' without setting shiftKey) can't slip a suspend past it.
|
||||
terminal.attachCustomKeyEventHandler((ev) => {
|
||||
if (
|
||||
ev.type === 'keydown' &&
|
||||
ev.key.toLowerCase() === 'z' &&
|
||||
ev.ctrlKey &&
|
||||
!ev.altKey &&
|
||||
!ev.metaKey &&
|
||||
!ev.shiftKey
|
||||
) {
|
||||
ev.preventDefault();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Wait for terminal renderer to fully initialize before any writes.
|
||||
// xterm.js needs a few frames after open() before write() is safe.
|
||||
setTimeout(() => {
|
||||
try { fitAddon.fit(); } catch {}
|
||||
|
||||
// Fetch initial pane buffer
|
||||
fetch(`/api/sessions/${sessionId}/teammate-pane-buffer/${encodeURIComponent(paneInfo.paneTarget)}`)
|
||||
.then(r => r.json())
|
||||
.then(resp => {
|
||||
if (resp.success && resp.data?.buffer) {
|
||||
try { terminal.write(resp.data.buffer); } catch {}
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('[TeammateTerminal] Failed to fetch buffer:', err));
|
||||
|
||||
// Flush any data that arrived while terminal was initializing
|
||||
for (const chunk of pendingData) {
|
||||
try { terminal.write(chunk); } catch {}
|
||||
}
|
||||
pendingData.length = 0;
|
||||
}, 100);
|
||||
|
||||
// Forward keyboard input to the teammate's pane
|
||||
terminal.onData((data) => {
|
||||
fetch(`/api/sessions/${sessionId}/teammate-pane-input`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ paneTarget: paneInfo.paneTarget, input: data }),
|
||||
}).catch(err => console.error('[TeammateTerminal] Failed to send input:', err));
|
||||
});
|
||||
|
||||
// Resize observer to refit terminal when window is resized
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
requestAnimationFrame(() => { try { fitAddon.fit(); } catch {} });
|
||||
});
|
||||
resizeObserver.observe(body);
|
||||
|
||||
// Update the stored entry with the real terminal
|
||||
const entry = this.teammateTerminals.get(agentId);
|
||||
if (entry) {
|
||||
entry.terminal = terminal;
|
||||
entry.fitAddon = fitAddon;
|
||||
entry.resizeObserver = resizeObserver;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Check if a subagent is a teammate and return its info */
|
||||
getTeammateInfo(agent) {
|
||||
if (!agent?.description) return null;
|
||||
|
||||
@@ -328,83 +328,6 @@ Object.assign(CodemanApp.prototype, {
|
||||
this._applyLineEntrances?.(svg);
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Lazy Terminal Lifecycle
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
//
|
||||
// Teammate terminal windows use xterm.js Terminal instances that consume
|
||||
// ~75KB of DOM memory each. With 50 agents minimized, that's ~3.75MB of
|
||||
// invisible terminals. To avoid this, we dispose the Terminal when a
|
||||
// window is minimized and lazily re-create it when restored.
|
||||
//
|
||||
// Flow:
|
||||
// minimize → _disposeTeammateTerminalForMinimize() → sets _lazyTerminal flag
|
||||
// restore → _restoreTeammateTerminalFromLazy() → re-creates Terminal
|
||||
// create (hidden/minimized) → skip initTeammateTerminal, set _lazyTerminal
|
||||
//
|
||||
// The tmux pane buffer is re-fetched from the API on restore. Regular
|
||||
// (non-teammate) subagent windows use activity HTML and are unaffected
|
||||
// by this optimization.
|
||||
|
||||
/**
|
||||
* Dispose a teammate terminal when its window is minimized.
|
||||
* Saves pane metadata so the terminal can be re-created on restore.
|
||||
* No-op if the window has no teammate terminal.
|
||||
*/
|
||||
_disposeTeammateTerminalForMinimize(agentId) {
|
||||
const termData = this.teammateTerminals.get(agentId);
|
||||
if (!termData) return; // Not a teammate terminal window
|
||||
|
||||
const windowData = this.subagentWindows.get(agentId);
|
||||
|
||||
// Save pane metadata needed to re-create the terminal on restore
|
||||
if (windowData) {
|
||||
windowData._lazyTerminal = true;
|
||||
windowData._lazyPaneTarget = termData.paneTarget;
|
||||
windowData._lazySessionId = termData.sessionId;
|
||||
}
|
||||
|
||||
// Dispose the resize observer
|
||||
if (termData.resizeObserver) {
|
||||
termData.resizeObserver.disconnect();
|
||||
}
|
||||
|
||||
// Dispose the xterm.js Terminal instance (frees DOM nodes and internal buffers)
|
||||
if (termData.terminal) {
|
||||
try {
|
||||
termData.terminal.dispose();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// Remove from teammateTerminals map so renderSubagentWindowContent won't skip this window
|
||||
// (the activity HTML can serve as a lightweight placeholder while minimized)
|
||||
this.teammateTerminals.delete(agentId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Re-create a teammate terminal when its window is restored from minimized state.
|
||||
* Fetches the current pane buffer from the API (tmux is the source of truth).
|
||||
* No-op if the window doesn't have the _lazyTerminal flag.
|
||||
*/
|
||||
_restoreTeammateTerminalFromLazy(agentId) {
|
||||
const windowData = this.subagentWindows.get(agentId);
|
||||
if (!windowData || !windowData._lazyTerminal) return;
|
||||
|
||||
const paneTarget = windowData._lazyPaneTarget;
|
||||
const sessionId = windowData._lazySessionId;
|
||||
|
||||
// Clear lazy state
|
||||
windowData._lazyTerminal = false;
|
||||
windowData._lazyPaneTarget = null;
|
||||
windowData._lazySessionId = null;
|
||||
|
||||
if (!paneTarget || !sessionId) return;
|
||||
|
||||
// Re-create the terminal using the same initTeammateTerminal flow
|
||||
const paneInfo = { paneTarget, sessionId };
|
||||
this.initTeammateTerminal(agentId, paneInfo, windowData.element);
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Subagent Floating Windows
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
@@ -612,24 +535,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
win.style.display = 'none';
|
||||
}
|
||||
|
||||
// Render content — check if this teammate has a tmux pane
|
||||
const paneInfo = teammateInfo ? this.teammatePanesByName.get(teammateInfo.name) : null;
|
||||
if (paneInfo) {
|
||||
if (shouldHide) {
|
||||
// Window starts hidden — defer terminal creation until visible (lazy init).
|
||||
// Saves ~75KB of DOM memory per hidden teammate terminal window.
|
||||
const windowEntry = this.subagentWindows.get(agentId);
|
||||
if (windowEntry) {
|
||||
windowEntry._lazyTerminal = true;
|
||||
windowEntry._lazyPaneTarget = paneInfo.paneTarget;
|
||||
windowEntry._lazySessionId = paneInfo.sessionId;
|
||||
}
|
||||
} else {
|
||||
this.initTeammateTerminal(agentId, paneInfo, win);
|
||||
}
|
||||
} else {
|
||||
this.renderSubagentWindowContent(agentId);
|
||||
}
|
||||
this.renderSubagentWindowContent(agentId);
|
||||
|
||||
// Focus on click
|
||||
win.addEventListener('mousedown', () => {
|
||||
@@ -701,10 +607,6 @@ Object.assign(CodemanApp.prototype, {
|
||||
this.setAgentParentSessionId(agentId, parentSessionId);
|
||||
}
|
||||
|
||||
// Dispose teammate terminal on minimize to free DOM/memory (~75KB per instance).
|
||||
// The terminal will be lazily re-created on restore via initTeammateTerminal().
|
||||
this._disposeTeammateTerminalForMinimize(agentId);
|
||||
|
||||
// Always minimize to tab
|
||||
windowData.element.style.display = 'none';
|
||||
windowData.minimized = true;
|
||||
@@ -775,18 +677,6 @@ Object.assign(CodemanApp.prototype, {
|
||||
}
|
||||
this.subagentWindows.clear();
|
||||
|
||||
// Clean up all teammate terminals
|
||||
for (const [, termData] of this.teammateTerminals) {
|
||||
if (termData.resizeObserver) termData.resizeObserver.disconnect();
|
||||
if (termData.terminal) {
|
||||
try {
|
||||
termData.terminal.dispose();
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
this.teammateTerminals.clear();
|
||||
this.teammatePanesByName.clear();
|
||||
|
||||
// Clean up all log viewer windows with their EventSources and drag listeners
|
||||
for (const data of this.logViewerWindows.values()) {
|
||||
if (data.eventSource) {
|
||||
@@ -868,12 +758,6 @@ Object.assign(CodemanApp.prototype, {
|
||||
}
|
||||
windowData.minimized = false;
|
||||
|
||||
// Lazily re-create teammate terminal if it was disposed on minimize.
|
||||
// Only re-create when the window is actually becoming visible.
|
||||
if (shouldShow && windowData._lazyTerminal) {
|
||||
this._restoreTeammateTerminalFromLazy(agentId);
|
||||
}
|
||||
|
||||
this.updateConnectionLines();
|
||||
// Restack all visible mobile windows so restored ones don't overlap
|
||||
this.relayoutMobileSubagentWindows();
|
||||
|
||||
@@ -428,8 +428,8 @@
|
||||
|
||||
// Per-skin xterm.js palettes. The 'daylight-blue' object equals the legacy hardcoded
|
||||
// theme, so default behavior is unchanged. Shared at module scope and exported on the
|
||||
// global so both terminal-ui.js (main terminal) and panels-ui.js (teammate terminals,
|
||||
// a separate IIFE) can read the current skin's palette.
|
||||
// global so the main terminal and every other module that builds a terminal (tiles,
|
||||
// the split pane) can read the current skin's palette.
|
||||
//
|
||||
// ⚠️ The selection key is `selectionBackground`, xterm's name for it since v5 (#360).
|
||||
// An ITheme is a plain object handed straight to xterm, so an unknown key is not an
|
||||
@@ -6585,16 +6585,6 @@ Object.assign(CodemanApp.prototype, {
|
||||
this._refitAfterCellSizeChange();
|
||||
this._localEchoOverlay?.refreshFont();
|
||||
this._predictiveEcho?.refreshFont();
|
||||
for (const [, entry] of this.teammateTerminals || []) {
|
||||
if (!entry?.terminal) continue;
|
||||
entry.terminal.options.fontWeight = fontWeight;
|
||||
entry.terminal.options.fontWeightBold = fontWeightBold;
|
||||
try {
|
||||
entry.fitAddon?.fit();
|
||||
} catch {
|
||||
/* pane not laid out yet — its own resize observer refits it */
|
||||
}
|
||||
}
|
||||
this._forEachTile?.((tile) => {
|
||||
tile.terminal.options.fontWeight = fontWeight;
|
||||
tile.terminal.options.fontWeightBold = fontWeightBold;
|
||||
@@ -7158,7 +7148,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
}, 100);
|
||||
},
|
||||
|
||||
// Re-theme all live xterm terminals (main + teammate) to the given skin's palette.
|
||||
// Re-theme all live xterm terminals (main pane and tiles) to the given skin's palette.
|
||||
// Uses the xterm v5+ live setter (full object assignment triggers a repaint for both
|
||||
// DOM and WebGL renderers) plus a belt-and-suspenders refresh().
|
||||
applyTerminalSkin(skin) {
|
||||
@@ -7176,17 +7166,6 @@ Object.assign(CodemanApp.prototype, {
|
||||
this.terminal.refresh(0, this.terminal.rows - 1);
|
||||
} catch {}
|
||||
}
|
||||
if (this.teammateTerminals) {
|
||||
for (const [, entry] of this.teammateTerminals) {
|
||||
if (entry && entry.terminal) {
|
||||
entry.terminal.options.minimumContrastRatio = minimumContrastRatio;
|
||||
entry.terminal.options.theme = { ...theme };
|
||||
try {
|
||||
entry.terminal.refresh(0, entry.terminal.rows - 1);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
}
|
||||
this._forEachTile?.((tile) => {
|
||||
tile.terminal.options.minimumContrastRatio = minimumContrastRatio;
|
||||
tile.terminal.options.theme = { ...theme };
|
||||
|
||||
Reference in new issue
Block a user