From 6e496e102d4c30780cfaf4afaf4fac00be6f2cd1 Mon Sep 17 00:00:00 2001
From: Fusion
Date: Sun, 24 May 2026 14:14:45 +0200
Subject: [PATCH 1/3] fix: terminal sizing and newline rendering issues
- Add ResizeObserver to terminal container for responsive sizing
(catches keyboard open/close, header auto-hide, layout changes)
- Remove padding from mobile terminal container to maximize space
- Fix CSS: ensure xterm viewport fills container height properly
- Fix session-card.tsx TypeScript error (removed non-existent port field)
- Remove explicit xterm-viewport/xterm-screen width overrides that
interfered with xterm.js canvas sizing
---
apps/web/src/components/session-card.tsx | 4 +---
apps/web/src/components/terminal.tsx | 22 ++++++++++++++++++
apps/web/src/styles.css | 29 +++++++++++++-----------
3 files changed, 39 insertions(+), 16 deletions(-)
diff --git a/apps/web/src/components/session-card.tsx b/apps/web/src/components/session-card.tsx
index a1e6c88..a84686e 100644
--- a/apps/web/src/components/session-card.tsx
+++ b/apps/web/src/components/session-card.tsx
@@ -110,9 +110,7 @@ export function SessionCard({
)}
- {session.port && session.status === "running" && (
- Port: {session.port}
- )}
+
{session.created_at && (
Created: {new Date(session.created_at).toLocaleDateString()}
diff --git a/apps/web/src/components/terminal.tsx b/apps/web/src/components/terminal.tsx
index a273930..5040fd4 100644
--- a/apps/web/src/components/terminal.tsx
+++ b/apps/web/src/components/terminal.tsx
@@ -242,6 +242,25 @@ export const TerminalComponent: React.FC = ({
window.addEventListener("resize", handleResize);
+ // Watch container size changes (keyboard, header auto-hide, etc.)
+ let resizeObserver: ResizeObserver | null = null;
+ if (terminalRef.current && typeof ResizeObserver !== "undefined") {
+ resizeObserver = new ResizeObserver(() => {
+ fitAddon.fit();
+ const { cols, rows } = term;
+ if (ws.readyState === WebSocket.OPEN) {
+ ws.send(
+ JSON.stringify({
+ type: "resize",
+ cols,
+ rows,
+ })
+ );
+ }
+ });
+ resizeObserver.observe(terminalRef.current);
+ }
+
// Notify parent about terminal readiness
if (onTerminalReadyRef.current) {
const sendData = (data: string) => {
@@ -270,6 +289,9 @@ export const TerminalComponent: React.FC = ({
return () => {
clearTimeout(resizeTimeout);
window.removeEventListener("resize", handleResize);
+ if (resizeObserver) {
+ resizeObserver.disconnect();
+ }
document.removeEventListener("visibilitychange", handleVisibilityChange);
ws.close();
term.dispose();
diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css
index 95b9933..3ab60b6 100644
--- a/apps/web/src/styles.css
+++ b/apps/web/src/styles.css
@@ -2594,31 +2594,34 @@ a.nav-item,
padding: var(--space-2);
display: flex;
flex-direction: column;
+ overflow: hidden;
}
.terminal-container .xterm {
flex: 1;
min-height: 0;
width: 100%;
-}
-
-.terminal-container .xterm-viewport {
- width: 100% !important;
- height: 100% !important;
-}
-
-.terminal-container .xterm-screen {
- width: 100% !important;
-}
-
-.terminal-container .xterm-viewport {
- background: #1e1e1e !important;
+ height: 100%;
}
.terminal-container canvas {
display: block;
}
+/* Mobile terminal container - no padding to maximize space */
+.terminal-wrapper.mobile .terminal-container {
+ padding: 0;
+}
+
+/* Ensure xterm viewport fills container properly */
+.terminal-wrapper.mobile .xterm {
+ height: 100% !important;
+}
+
+.terminal-wrapper.mobile .xterm-viewport {
+ height: 100% !important;
+}
+
/* Responsive terminal */
@media (max-width: 767px) {
.terminal-page {
From c49bb028c44d8776e196b7de1d9ff76ffa4d918a Mon Sep 17 00:00:00 2001
From: Fusion
Date: Sun, 24 May 2026 14:23:34 +0200
Subject: [PATCH 2/3] fix: remove ResizeObserver to prevent infinite resize
loop
The ResizeObserver triggered fit() which changed canvas dimensions,
triggering the observer again in an infinite loop. We already have
window resize handling and delayed fit() calls, so the observer was
redundant.
---
apps/web/src/components/terminal.tsx | 22 ----------------------
1 file changed, 22 deletions(-)
diff --git a/apps/web/src/components/terminal.tsx b/apps/web/src/components/terminal.tsx
index 5040fd4..a273930 100644
--- a/apps/web/src/components/terminal.tsx
+++ b/apps/web/src/components/terminal.tsx
@@ -242,25 +242,6 @@ export const TerminalComponent: React.FC = ({
window.addEventListener("resize", handleResize);
- // Watch container size changes (keyboard, header auto-hide, etc.)
- let resizeObserver: ResizeObserver | null = null;
- if (terminalRef.current && typeof ResizeObserver !== "undefined") {
- resizeObserver = new ResizeObserver(() => {
- fitAddon.fit();
- const { cols, rows } = term;
- if (ws.readyState === WebSocket.OPEN) {
- ws.send(
- JSON.stringify({
- type: "resize",
- cols,
- rows,
- })
- );
- }
- });
- resizeObserver.observe(terminalRef.current);
- }
-
// Notify parent about terminal readiness
if (onTerminalReadyRef.current) {
const sendData = (data: string) => {
@@ -289,9 +270,6 @@ export const TerminalComponent: React.FC = ({
return () => {
clearTimeout(resizeTimeout);
window.removeEventListener("resize", handleResize);
- if (resizeObserver) {
- resizeObserver.disconnect();
- }
document.removeEventListener("visibilitychange", handleVisibilityChange);
ws.close();
term.dispose();
From 39cf01c3c9d5390e838815c21fba8c4f44b48cff Mon Sep 17 00:00:00 2001
From: Fusion
Date: Sun, 24 May 2026 14:28:03 +0200
Subject: [PATCH 3/3] fix: use absolute positioning for xterm.js to fill
container
- Make .terminal-container position: relative with overflow: hidden
- Make xterm element absolutely positioned to fill container
- This ensures xterm.js always has concrete dimensions for fitAddon
- Remove conflicting height: 100% !important overrides
- Terminal now properly fills available space and calculates correct rows
---
apps/web/src/styles.css | 39 +++++++++++++++++++++++++++++++--------
1 file changed, 31 insertions(+), 8 deletions(-)
diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css
index 346f07a..cdf5f91 100644
--- a/apps/web/src/styles.css
+++ b/apps/web/src/styles.css
@@ -2614,14 +2614,6 @@ a.nav-item,
}
/* Ensure xterm viewport fills container properly */
-.terminal-wrapper.mobile .xterm {
- height: 100% !important;
-}
-
-.terminal-wrapper.mobile .xterm-viewport {
- height: 100% !important;
-}
-
/* Responsive terminal */
@media (max-width: 767px) {
.terminal-page {
@@ -3195,6 +3187,37 @@ a.nav-item,
flex-direction: column;
}
+/* Terminal wrapper - explicit height for xterm.js */
+.terminal-wrapper.mobile {
+ border: none;
+ border-radius: 0;
+ flex: 1;
+ min-height: 0;
+ width: 100%;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+}
+
+.terminal-wrapper.mobile .terminal-container {
+ flex: 1;
+ min-height: 0;
+ padding: 0;
+ overflow: hidden;
+ position: relative;
+}
+
+/* Ensure xterm fills container */
+.terminal-wrapper.mobile .terminal-container .xterm {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ width: 100%;
+ height: 100%;
+}
+
/* Special Keys Strip */
.special-keys-strip {
display: flex;