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:
Codeman maintainer committed 2026-11-13 20:35:32 +01:00
1 parent 259cabd738
commit d44b049c51
9 files changed
+14 -344

No files matched your search

-1
View File
@@ -7,7 +7,6 @@
* only session:terminal is filtered by subscription)
* - Tab switching: terminal buffer loading, session creation + switch
* - Terminal data cap / backpressure recovery
* - Lazy teammate terminal lifecycle
* - SSE padding strategy (only latency-sensitive events)
*/
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
-4
View File
@@ -138,23 +138,19 @@ 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);
});
+4 -30
View File
@@ -11,11 +11,9 @@
*
* Three things are pinned here because each fails silently:
*
* - A live save reaches the echo overlays and the Agent Teams panes. Both
* cache the weight (the overlays paint it into their spans, the panes read
* their options at construction), so without the propagation the characters
* being typed, or a pane left open across the save, keep the old weight
* beside a repainted terminal.
* - A live save reaches the echo overlays. They cache the weight and paint
* it into their spans, so without the propagation the characters being
* typed keep the old weight beside a repainted terminal.
* - An unchanged save is a no-op, so opening and closing App Settings does not
* churn the terminal.
* - The bundled face is declared over its full axis. The `@font-face`
@@ -65,19 +63,11 @@ function fakeTerminal(options: Record<string, unknown> = {}) {
function makeApp(
opts: {
teammates?: number;
terminal?: ReturnType<typeof fakeTerminal> | null;
splitPane?: { terminal: ReturnType<typeof fakeTerminal>; fit: () => void } | null;
} = {}
) {
const fit = vi.fn();
const teammateFits: ReturnType<typeof vi.fn>[] = [];
const teammateTerminals = new Map<string, { terminal: ReturnType<typeof fakeTerminal>; fitAddon: unknown }>();
for (let i = 0; i < (opts.teammates ?? 0); i++) {
const teammateFit = vi.fn();
teammateFits.push(teammateFit);
teammateTerminals.set(`agent-${i}`, { terminal: fakeTerminal(), fitAddon: { fit: teammateFit } });
}
const app = {
applyTerminalFontWeights: mixin.applyTerminalFontWeights,
// Secondary panes (the split pane's second terminal) are reached through
@@ -99,12 +89,11 @@ function makeApp(
_awaitTerminalFont: vi.fn(() => Promise.resolve()),
terminal: opts.terminal === undefined ? fakeTerminal() : opts.terminal,
fitAddon: { fit },
teammateTerminals,
_localEchoOverlay: { refreshFont: vi.fn() },
_predictiveEcho: { refreshFont: vi.fn() },
_terminalFontReady: null as unknown,
};
return { app, fit, teammateFits, teammateTerminals };
return { app, fit };
}
describe('applyTerminalFontWeights', () => {
@@ -152,21 +141,6 @@ describe('applyTerminalFontWeights', () => {
expect(app._predictiveEcho.refreshFont).toHaveBeenCalledTimes(1);
});
it('reaches Agent Teams panes, which read their options at construction', () => {
const { app, teammateTerminals, teammateFits } = makeApp({ teammates: 2 });
(app as unknown as { applyTerminalFontWeights: (s: unknown) => void }).applyTerminalFontWeights({
terminalFontWeight: '300',
terminalFontWeightBold: '800',
});
for (const [, entry] of teammateTerminals) {
expect(entry.terminal.options.fontWeight).toBe(300);
expect(entry.terminal.options.fontWeightBold).toBe(800);
}
for (const teammateFit of teammateFits) expect(teammateFit).toHaveBeenCalled();
});
it('restores xterm’s own defaults when the setting is cleared', () => {
const { app } = makeApp({ terminal: fakeTerminal({ fontWeight: 300, fontWeightBold: 800 }) });