fix(terminal): create PTYs at the client viewport size, not 80x24 (#2023)
New terminals were always spawned by the daemon at the hardcoded 80x24 default and only resized once the client's first resize arrived. On a slow first mount that leaves a window -- or a stuck state -- where a new terminal renders far smaller than its pane (e.g. vim in a corner). Seed the PTY with the client's measured viewport size at creation time so it is born at the right size and the race window is gone. - protocol: optional `size` on create_terminal_request. Old daemons ignore it and keep 80x24; old clients send nothing. Fully backward compatible, no capability gate. - client: a small last-measured-size cache keyed by serverId+cwd (every terminal in a workspace shares the pane) with a most-recent fallback; written on fit, read at create. - server: thread rows/cols through the controller, terminal manager, and the worker-thread boundary into the existing size-aware createTerminal. Tests: cache logic, protocol back-compat parse, controller forwarding. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ec93ca866e
commit
2658132384
@@ -60,6 +60,8 @@ export interface TerminalManager {
|
||||
env?: Record<string, string>;
|
||||
command?: string;
|
||||
args?: string[];
|
||||
rows?: number;
|
||||
cols?: number;
|
||||
activityToken?: string;
|
||||
activityUrl?: string | null;
|
||||
}): Promise<TerminalSession>;
|
||||
@@ -315,6 +317,8 @@ export function createTerminalManager(
|
||||
env?: Record<string, string>;
|
||||
command?: string;
|
||||
args?: string[];
|
||||
rows?: number;
|
||||
cols?: number;
|
||||
activityToken?: string;
|
||||
activityUrl?: string | null;
|
||||
}): Promise<TerminalSession> {
|
||||
@@ -348,6 +352,8 @@ export function createTerminalManager(
|
||||
...(options.title ? { title: options.title } : {}),
|
||||
...(options.command ? { command: options.command } : {}),
|
||||
...(options.args ? { args: options.args } : {}),
|
||||
...(options.rows !== undefined ? { rows: options.rows } : {}),
|
||||
...(options.cols !== undefined ? { cols: options.cols } : {}),
|
||||
...(mergedEnv ? { env: mergedEnv } : {}),
|
||||
activityEnv,
|
||||
}),
|
||||
|
||||
@@ -259,6 +259,59 @@ describe("terminal-session-controller legacy terminal creation", () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("forwards the client-provided viewport size to the terminal manager", async () => {
|
||||
const outboundMessages: SessionOutboundMessage[] = [];
|
||||
const createTerminal = vi.fn(
|
||||
async (options: Parameters<TerminalManager["createTerminal"]>[0]) =>
|
||||
listSession({
|
||||
id: "term-1",
|
||||
name: options.name ?? "Terminal 1",
|
||||
cwd: options.cwd,
|
||||
workspaceId: options.workspaceId,
|
||||
}),
|
||||
);
|
||||
const terminalManager: TerminalManager = {
|
||||
getTerminals: vi.fn(),
|
||||
createTerminal,
|
||||
registerCwdEnv: vi.fn(),
|
||||
validateTerminalActivityToken: vi.fn(() => "unknown"),
|
||||
getTerminal: vi.fn(),
|
||||
getTerminalState: vi.fn(),
|
||||
setTerminalTitle: vi.fn(),
|
||||
setTerminalActivity: vi.fn(),
|
||||
clearTerminalAttention: vi.fn(),
|
||||
killTerminal: vi.fn(),
|
||||
killTerminalAndWait: vi.fn(),
|
||||
captureTerminal: vi.fn(),
|
||||
listDirectories: vi.fn(() => []),
|
||||
killAll: vi.fn(),
|
||||
subscribeTerminalsChanged: vi.fn(() => vi.fn()),
|
||||
subscribeTerminalActivity: vi.fn(() => vi.fn()),
|
||||
subscribeTerminalWorkspaceContributionChanged: vi.fn(() => vi.fn()),
|
||||
};
|
||||
const controller = new TerminalSessionController({
|
||||
terminalManager,
|
||||
emit: (message) => outboundMessages.push(message),
|
||||
emitBinary: vi.fn(),
|
||||
hasBinaryChannel: () => true,
|
||||
isPathWithinRoot: isSameOrDescendantPath,
|
||||
sessionLogger: createLogger(),
|
||||
listTerminalWorkspaceRefs: async () => [],
|
||||
});
|
||||
|
||||
await controller.dispatch({
|
||||
type: "create_terminal_request",
|
||||
cwd: "/work/repo",
|
||||
workspaceId: "ws-1",
|
||||
size: { rows: 55, cols: 136 },
|
||||
requestId: "req-size",
|
||||
});
|
||||
|
||||
expect(createTerminal).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ cwd: "/work/repo", workspaceId: "ws-1", rows: 55, cols: 136 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
async function flushMicrotasks(): Promise<void> {
|
||||
|
||||
@@ -549,6 +549,8 @@ export class TerminalSessionController {
|
||||
name: msg.name,
|
||||
command: msg.command,
|
||||
args: msg.args,
|
||||
rows: msg.size?.rows,
|
||||
cols: msg.size?.cols,
|
||||
});
|
||||
this.ensureExitSubscription(session);
|
||||
this.emit({
|
||||
|
||||
@@ -27,6 +27,8 @@ export interface WorkerCreateTerminalOptions {
|
||||
env?: Record<string, string>;
|
||||
command?: string;
|
||||
args?: string[];
|
||||
rows?: number;
|
||||
cols?: number;
|
||||
activityToken?: string;
|
||||
activityUrl?: string | null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user