Add viewport-based PTY resize from dashboard

This commit is contained in:
Jared Miller 2026-01-30 08:19:09 -05:00
parent 19917ef614
commit 5948dcaed1
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C
2 changed files with 118 additions and 0 deletions

View file

@ -1134,6 +1134,14 @@
// Reset rendered length when collapsing or expanding (element gets recreated) // Reset rendered length when collapsing or expanding (element gets recreated)
session.outputRenderedLength = 0; session.outputRenderedLength = 0;
renderSessions(); renderSessions();
// If expanding, resize the PTY to fit the viewport
if (session.expanded) {
// Wait for DOM to update before measuring
setTimeout(() => {
handleSessionResize(Number(sessionId));
}, 0);
}
} }
}; };
@ -1978,6 +1986,74 @@
// Auto-scroll control // Auto-scroll control
const scrollListeners = new Map(); const scrollListeners = new Map();
// Viewport-based PTY resize
let resizeDebounceTimer = null;
function measureTerminalCols() {
// Create a hidden span with monospace font to measure character width
const $measure = document.createElement('span');
$measure.style.fontFamily = '"SF Mono", Monaco, "Cascadia Code", "Courier New", monospace';
$measure.style.fontSize = `${state.settings.fontSize}px`;
$measure.style.lineHeight = '1.5';
$measure.style.visibility = 'hidden';
$measure.style.position = 'absolute';
$measure.textContent = 'X'.repeat(100); // Measure 100 chars
document.body.appendChild($measure);
const charWidth = $measure.offsetWidth / 100;
document.body.removeChild($measure);
// Get terminal container width (accounting for padding)
const $container = document.querySelector('.session-output');
if (!$container) {
return 80; // Default fallback
}
const containerWidth = $container.clientWidth - 32; // 16px padding on each side
const cols = Math.floor(containerWidth / charWidth);
// Clamp to reasonable bounds
return Math.max(40, Math.min(cols, 300));
}
async function resizeSessionPTY(sessionId, cols, rows) {
try {
const res = await fetch(`/api/sessions/${sessionId}/resize`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ cols, rows }),
});
if (!res.ok) {
console.error('Failed to resize session PTY:', await res.text());
}
} catch (error) {
console.error('Error resizing session PTY:', error);
}
}
function handleSessionResize(sessionId) {
const cols = measureTerminalCols();
const rows = 24; // Reasonable default row count
resizeSessionPTY(sessionId, cols, rows);
}
function handleWindowResize() {
if (resizeDebounceTimer) {
clearTimeout(resizeDebounceTimer);
}
resizeDebounceTimer = setTimeout(() => {
// Resize all expanded sessions
state.sessions.forEach((session) => {
if (session.expanded) {
handleSessionResize(session.id);
}
});
}, 300); // Debounce for 300ms
}
function attachScrollListener(sessionId, $outputContainer) { function attachScrollListener(sessionId, $outputContainer) {
// Remove existing listener if present // Remove existing listener if present
const existing = scrollListeners.get(sessionId); const existing = scrollListeners.get(sessionId);
@ -2041,6 +2117,9 @@
loadSettings(); loadSettings();
applySettings(); applySettings();
connectSSE(); connectSSE();
// Set up window resize listener for PTY viewport resize
window.addEventListener('resize', handleWindowResize);
</script> </script>
</body> </body>
</html> </html>

View file

@ -141,6 +141,45 @@ const server = Bun.serve<SessionData>({
return Response.json(sessions); return Response.json(sessions);
} }
// Resize endpoint for dashboard to resize CLI PTY
if (
url.pathname.match(/^\/api\/sessions\/\d+\/resize$/) &&
req.method === "POST"
) {
const parts = url.pathname.split("/");
const sessionId = Number.parseInt(parts[3] || "", 10);
if (Number.isNaN(sessionId)) {
return new Response("Invalid session ID", { status: 400 });
}
const body = (await req.json()) as { cols?: unknown; rows?: unknown };
if (
typeof body.cols !== "number" ||
typeof body.rows !== "number" ||
body.cols <= 0 ||
body.rows <= 0
) {
return new Response("Missing or invalid cols/rows", { status: 400 });
}
// Get WebSocket connection for this session
const ws = sessionWebSockets.get(sessionId);
if (!ws) {
return new Response("Session WebSocket not found", { status: 404 });
}
// Send resize command to CLI
const message: ServerMessage = {
type: "resize",
cols: body.cols,
rows: body.rows,
};
ws.send(JSON.stringify(message));
return Response.json({ success: true });
}
// Create prompt for a session // Create prompt for a session
if ( if (
url.pathname.match(/^\/api\/sessions\/\d+\/prompts$/) && url.pathname.match(/^\/api\/sessions\/\d+\/prompts$/) &&