Stop shell terminal width growth

This commit is contained in:
2026-07-31 21:39:02 +02:00
parent 25fda7aaef
commit 34b31c4caf
2 changed files with 30 additions and 12 deletions
+24 -7
View File
@@ -16,6 +16,7 @@ export function ShellTerminal({ csrfToken }: { csrfToken: string }) {
useEffect(() => { useEffect(() => {
if (!host.current) return; if (!host.current) return;
const hostElement = host.current;
const terminal = new Terminal({ const terminal = new Terminal({
cursorBlink: true, cursorBlink: true,
cursorStyle: "bar", cursorStyle: "bar",
@@ -36,8 +37,7 @@ export function ShellTerminal({ csrfToken }: { csrfToken: string }) {
}); });
const fit = new FitAddon(); const fit = new FitAddon();
terminal.loadAddon(fit); terminal.loadAddon(fit);
terminal.open(host.current); terminal.open(hostElement);
fit.fit();
terminal.writeln("\x1b[38;2;185;231;105mPi Car Companion shell\x1b[0m"); terminal.writeln("\x1b[38;2;185;231;105mPi Car Companion shell\x1b[0m");
terminal.writeln("Restricted local session running as pi-companion.\r\n"); terminal.writeln("Restricted local session running as pi-companion.\r\n");
@@ -48,6 +48,24 @@ export function ShellTerminal({ csrfToken }: { csrfToken: string }) {
socket.send(JSON.stringify({ type: "resize", cols: terminal.cols, rows: terminal.rows })); socket.send(JSON.stringify({ type: "resize", cols: terminal.cols, rows: terminal.rows }));
} }
}; };
let resizeFrame: number | null = null;
let fittedWidth = -1;
let fittedHeight = -1;
const fitToHost = () => {
resizeFrame = null;
const width = hostElement.clientWidth;
const height = hostElement.clientHeight;
if (width <= 0 || height <= 0 || (width === fittedWidth && height === fittedHeight)) return;
fittedWidth = width;
fittedHeight = height;
fit.fit();
sendSize();
};
const scheduleFit = () => {
if (resizeFrame !== null) window.cancelAnimationFrame(resizeFrame);
resizeFrame = window.requestAnimationFrame(fitToHost);
};
fitToHost();
socket.addEventListener("open", () => { socket.addEventListener("open", () => {
socket.send(JSON.stringify({ type: "authenticate", csrfToken })); socket.send(JSON.stringify({ type: "authenticate", csrfToken }));
}); });
@@ -56,6 +74,7 @@ export function ShellTerminal({ csrfToken }: { csrfToken: string }) {
const message = JSON.parse(String(event.data)) as ServerMessage; const message = JSON.parse(String(event.data)) as ServerMessage;
if (message.type === "ready") { if (message.type === "ready") {
setState("ready"); setState("ready");
fitToHost();
sendSize(); sendSize();
terminal.focus(); terminal.focus();
} else if (message.type === "output") terminal.write(message.data); } else if (message.type === "output") terminal.write(message.data);
@@ -80,14 +99,12 @@ export function ShellTerminal({ csrfToken }: { csrfToken: string }) {
const input = terminal.onData((data) => { const input = terminal.onData((data) => {
if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ type: "input", data })); if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ type: "input", data }));
}); });
const resize = new ResizeObserver(() => { const resize = new ResizeObserver(scheduleFit);
fit.fit(); resize.observe(hostElement);
sendSize();
});
resize.observe(host.current);
return () => { return () => {
resize.disconnect(); resize.disconnect();
if (resizeFrame !== null) window.cancelAnimationFrame(resizeFrame);
input.dispose(); input.dispose();
socket.close(1000, "Terminal tab closed"); socket.close(1000, "Terminal tab closed");
terminal.dispose(); terminal.dispose();
+6 -5
View File
@@ -137,7 +137,8 @@ button:disabled { opacity: 0.55; cursor: not-allowed; }
.activity-empty { min-height: 74px; display: grid; place-items: center; padding: 18px; color: var(--muted); font-size: 0.8rem; text-align: center; } .activity-empty { min-height: 74px; display: grid; place-items: center; padding: 18px; color: var(--muted); font-size: 0.8rem; text-align: center; }
.activity-privacy { margin: 0; color: var(--muted); font-size: 0.72rem; text-align: right; } .activity-privacy { margin: 0; color: var(--muted); font-size: 0.72rem; text-align: right; }
.settings-page { display: grid; gap: 18px; } .settings-page { min-width: 0; width: 100%; display: grid; gap: 18px; }
.settings-page > [role="tabpanel"] { min-width: 0; width: 100%; max-width: 100%; }
.settings-tabs { width: fit-content; display: flex; gap: 5px; padding: 5px; background: #121610; border: 1px solid var(--line); border-radius: 12px; } .settings-tabs { width: fit-content; display: flex; gap: 5px; padding: 5px; background: #121610; border: 1px solid var(--line); border-radius: 12px; }
.settings-tabs button { min-height: 48px; display: inline-flex; align-items: center; gap: 9px; padding: 0 19px; color: var(--muted); background: transparent; border: 0; border-radius: 8px; font-weight: 700; cursor: pointer; transition: color 160ms ease, background 160ms ease, transform 120ms ease; } .settings-tabs button { min-height: 48px; display: inline-flex; align-items: center; gap: 9px; padding: 0 19px; color: var(--muted); background: transparent; border: 0; border-radius: 8px; font-weight: 700; cursor: pointer; transition: color 160ms ease, background 160ms ease, transform 120ms ease; }
.settings-tabs button:hover { color: var(--text); background: var(--surface-raised); } .settings-tabs button:hover { color: var(--text); background: var(--surface-raised); }
@@ -189,8 +190,8 @@ button:disabled { opacity: 0.55; cursor: not-allowed; }
.behavior-list { display: grid; gap: 24px; margin: 26px 0 0; } .behavior-list { display: grid; gap: 24px; margin: 26px 0 0; }
.behavior-list div { display: grid; gap: 7px; } .behavior-list div { display: grid; gap: 7px; }
.behavior-list dd { color: #dce4d6; } .behavior-list dd { color: #dce4d6; }
.shell-panel { overflow: hidden; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); } .shell-panel { min-width: 0; width: 100%; max-width: 100%; overflow: hidden; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); }
.shell-loading { min-height: 420px; display: grid; place-items: center; color: var(--muted); background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); } .shell-loading { min-width: 0; width: 100%; max-width: 100%; min-height: 420px; display: grid; place-items: center; color: var(--muted); background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); }
.shell-toolbar { min-height: 68px; display: flex; align-items: center; justify-content: space-between; gap: 18px; padding: 10px 14px 10px 20px; border-bottom: 1px solid var(--line); } .shell-toolbar { min-height: 68px; display: flex; align-items: center; justify-content: space-between; gap: 18px; padding: 10px 14px 10px 20px; border-bottom: 1px solid var(--line); }
.shell-toolbar > div { display: grid; grid-template-columns: auto auto; align-items: center; gap: 3px 9px; } .shell-toolbar > div { display: grid; grid-template-columns: auto auto; align-items: center; gap: 3px 9px; }
.shell-toolbar strong { font-size: 0.87rem; } .shell-toolbar strong { font-size: 0.87rem; }
@@ -198,8 +199,8 @@ button:disabled { opacity: 0.55; cursor: not-allowed; }
.shell-state { grid-row: 1 / 3; width: 9px; height: 9px; border-radius: 50%; background: #7c8477; } .shell-state { grid-row: 1 / 3; width: 9px; height: 9px; border-radius: 50%; background: #7c8477; }
.shell-state.ready { background: var(--accent); box-shadow: 0 0 0 5px rgba(185, 231, 105, 0.08); } .shell-state.ready { background: var(--accent); box-shadow: 0 0 0 5px rgba(185, 231, 105, 0.08); }
.shell-state.error { background: var(--danger); } .shell-state.error { background: var(--danger); }
.terminal-host { height: min(590px, calc(100dvh - 290px)); min-height: 360px; padding: 16px 12px; background: #0b0e0a; } .terminal-host { contain: inline-size; min-width: 0; width: 100%; max-width: 100%; height: min(590px, calc(100dvh - 290px)); min-height: 360px; overflow: hidden; padding: 16px 12px; background: #0b0e0a; }
.terminal-host .xterm { height: 100%; } .terminal-host .xterm { width: 100%; max-width: 100%; height: 100%; }
.shell-panel > .settings-note { margin: 0; padding: 14px 20px 16px; border-top: 1px solid var(--line); } .shell-panel > .settings-note { margin: 0; padding: 14px 20px 16px; border-top: 1px solid var(--line); }
.shell-panel code { color: #dce4d6; } .shell-panel code { color: #dce4d6; }
.modal-backdrop { position: fixed; inset: 0; z-index: 20; display: grid; place-items: center; padding: 24px; background: rgba(8, 10, 8, 0.78); } .modal-backdrop { position: fixed; inset: 0; z-index: 20; display: grid; place-items: center; padding: 24px; background: rgba(8, 10, 8, 0.78); }