fix(session-sharing): synchronize shared SSH terminal dimensions (#1502)

This commit is contained in:
ZacharyZcR authored and GitHub committed 2026-09-30 09:46:35 -05:00
1 parent c265758cdd
commit 0f6e48a95c
7 files changed
+341 -15

No files matched your search

@@ -444,6 +444,15 @@ export class TerminalSessionManager {
tabInstanceId: opts.tabInstanceId,
joinedViaShareId: opts.shareId,
});
if (ws.readyState === WebSocket.OPEN) {
ws.send(
JSON.stringify({
type: "resized",
cols: session.cols,
rows: session.rows,
}),
);
}
this.broadcastParticipants(sessionId);
this.log.info("Participant joined shared session", {
@@ -812,6 +821,15 @@ export class TerminalSessionManager {
this.recordSessionEvent(session, "i", data);
}
resizeSession(sessionId: string, cols: number, rows: number): void {
const session = this.sessions.get(sessionId);
if (!session) return;
session.cols = cols;
session.rows = rows;
this.broadcast(sessionId, { type: "resized", cols, rows });
this.bufferResize(sessionId, cols, rows);
}
bufferResize(sessionId: string, cols: number, rows: number): void {
const session = this.sessions.get(sessionId);
if (!session) return;
@@ -568,8 +568,11 @@ export function createTerminalSocket(deps: TerminalSocketDeps) {
attachRows,
attachCols,
);
session.cols = attachCols;
session.rows = attachRows;
sessionManager.resizeSession(
session.id,
attachCols,
attachRows,
);
}
ws.send(
@@ -2871,11 +2874,8 @@ export function createTerminalSocket(deps: TerminalSocketDeps) {
resizeStream.setWindow(rows, cols, rows, cols);
const session = sessionManager.getSession(currentSessionId);
if (session) {
session.cols = cols;
session.rows = rows;
sessionManager.bufferResize(session.id, cols, rows);
sessionManager.resizeSession(session.id, cols, rows);
}
ws.send(JSON.stringify({ type: "resized", cols, rows }));
}
}
@@ -910,6 +910,33 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
}
}
const sharedSizeRef = useRef<{ cols: number; rows: number } | null>(null);
function applySharedSize(msg: { cols?: unknown; rows?: unknown }) {
if (!hostConfig.joinShareId || !terminal) return;
const { cols, rows } = msg;
if (
typeof cols !== "number" ||
typeof rows !== "number" ||
!Number.isInteger(cols) ||
!Number.isInteger(rows) ||
cols < 1 ||
rows < 1
)
return;
sharedSizeRef.current = { cols, rows };
terminal.resize(cols, rows);
}
function fitTerminal() {
const size = sharedSizeRef.current;
if (hostConfig.joinShareId && size) {
terminal?.resize(size.cols, size.rows);
} else {
fitAddonRef.current?.fit();
}
}
function performFit() {
if (
!fitAddonRef.current ||
@@ -923,7 +950,7 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
isFittingRef.current = true;
try {
fitAddonRef.current.fit();
fitTerminal();
if (terminal && terminal.cols > 0 && terminal.rows > 0) {
const lastSize = lastFittedSizeRef.current;
if (
@@ -1180,6 +1207,7 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
}
function scheduleNotify(cols: number, rows: number) {
if (hostConfig.joinShareId) return;
if (!(cols > 0 && rows > 0)) return;
pendingSizeRef.current = { cols, rows };
if (notifyTimerRef.current) clearTimeout(notifyTimerRef.current);
@@ -1390,7 +1418,7 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
if (!fitAddonRef.current || !terminal || isFittingRef.current) return;
isFittingRef.current = true;
try {
fitAddonRef.current.fit();
fitTerminal();
if (terminal.cols > 0 && terminal.rows > 0) {
const lastSize = lastFittedSizeRef.current;
if (
@@ -1839,7 +1867,9 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
pongReceivedRef.current = true;
return;
}
if (msg.type === "data") {
if (msg.type === "resized") {
applySharedSize(msg);
} else if (msg.type === "data") {
if (typeof msg.data === "string") {
outputListenersRef.current.forEach((listener) =>
listener(msg.data),
@@ -2674,7 +2704,7 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
});
document.fonts.ready.then(() => {
terminal.refresh(0, terminal.rows - 1);
fitAddon.fit();
fitTerminal();
});
terminal.attachCustomWheelEventHandler((ev) => {
@@ -2699,12 +2729,12 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
return true;
});
fitAddonRef.current?.fit();
fitTerminal();
// Double-rAF ensures layout is fully settled (fonts, flexbox, etc.) before
// committing the fitted size, preventing the "terminal too short" glitch.
requestAnimationFrame(() => {
requestAnimationFrame(() => {
fitAddonRef.current?.fit();
fitTerminal();
setIsFitted(true);
});
});
@@ -3383,10 +3413,10 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
}
setIsConnecting(true);
fitAddonRef.current?.fit();
fitTerminal();
requestAnimationFrame(() => {
requestAnimationFrame(() => {
fitAddonRef.current?.fit();
fitTerminal();
if (terminal.cols > 0 && terminal.rows > 0) {
scheduleNotify(terminal.cols, terminal.rows);
connectToHost(terminal.cols, terminal.rows);