From 22f736ce2079a1092f727ae3d4d131abcf933dd6 Mon Sep 17 00:00:00 2001 From: Fusion Date: Sun, 24 May 2026 22:46:48 +0200 Subject: [PATCH 1/4] debug: add console logging to trace mobile terminal black screen issue --- apps/web/src/components/terminal.tsx | 35 +++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/apps/web/src/components/terminal.tsx b/apps/web/src/components/terminal.tsx index 6403cc6..75258c1 100644 --- a/apps/web/src/components/terminal.tsx +++ b/apps/web/src/components/terminal.tsx @@ -74,6 +74,7 @@ export const TerminalComponent: React.FC = ({ wsRef.current = ws; ws.onopen = () => { + console.log("[Terminal] WebSocket connected"); setStatus("connected"); setError(null); reconnectAttemptsRef.current = 0; @@ -106,13 +107,16 @@ export const TerminalComponent: React.FC = ({ if (!termRef.current) return; if (event.data instanceof Blob) { + console.log("[Terminal] Received blob data:", event.data.size, "bytes"); event.data.arrayBuffer().then((buffer) => { const data = new Uint8Array(buffer); + console.log("[Terminal] Writing", data.length, "bytes to terminal"); termRef.current?.write(data); }); } else if (typeof event.data === "string") { try { const msg = JSON.parse(event.data); + console.log("[Terminal] Received JSON message:", msg); if (msg.type === "status") { if (msg.status === "connected") { setStatus("connected"); @@ -184,7 +188,12 @@ export const TerminalComponent: React.FC = ({ }, [instanceId]); useEffect(() => { - if (!terminalRef.current) return; + if (!terminalRef.current) { + console.error("[Terminal] terminalRef is null"); + return; + } + + console.log("[Terminal] Initializing terminal for instance", instanceId, "isMobile:", isMobile); // Initialize terminal const currentFontSize = calculateFontSize(); @@ -228,18 +237,25 @@ export const TerminalComponent: React.FC = ({ // Define fitTerminal before connectWebSocket so it's available in onmessage const fitTerminal = () => { - if (!fitAddonRef.current || !termRef.current) return; + if (!fitAddonRef.current || !termRef.current) { + console.log("[Terminal] fitTerminal: missing refs"); + return; + } // Ensure terminal is opened and has valid dimensions - if (termRef.current.cols === 0 || termRef.current.rows === 0) return; + if (termRef.current.cols === 0 || termRef.current.rows === 0) { + console.log("[Terminal] fitTerminal: zero dimensions", termRef.current.cols, termRef.current.rows); + return; + } const oldCols = termRef.current.cols; const oldRows = termRef.current.rows; try { fitAddonRef.current.fit(); - } catch { - // Ignore fit errors during initialization + } catch (err) { + console.error("[Terminal] fit error:", err); return; } const { cols, rows } = termRef.current; + console.log("[Terminal] fitTerminal:", oldCols, "x", oldRows, "->", cols, "x", rows); // Force refresh if dimensions changed and are valid if ((cols !== oldCols || rows !== oldRows) && cols > 0 && rows > 0) { try { @@ -255,17 +271,24 @@ export const TerminalComponent: React.FC = ({ }; // Open xterm first (must happen before fit) + console.log("[Terminal] Opening xterm in container", container.clientWidth, "x", container.clientHeight); term.open(container); ws = connectWebSocket(); // Initial fit after layout settles (terminal must be opened first) const doInitialFit = () => { - if (!container.isConnected) return; + if (!container.isConnected) { + console.log("[Terminal] Container not connected yet"); + return; + } // Ensure container has dimensions before fitting + console.log("[Terminal] Container dimensions:", container.clientWidth, "x", container.clientHeight); if (container.clientWidth > 0 && container.clientHeight > 0) { + console.log("[Terminal] Calling initial fit"); fitTerminal(); } else { // Container not ready yet, try again + console.log("[Terminal] Container has no dimensions, retrying..."); requestAnimationFrame(doInitialFit); } }; From 612217ad89ece27c801842e403f48ace50e886ba Mon Sep 17 00:00:00 2001 From: Fusion Date: Sun, 24 May 2026 22:51:31 +0200 Subject: [PATCH 2/4] fix: mobile terminal black screen - grid cell had 0 height - Root cause: .mobile-terminal-wrapper used height: 100vh inside flex parent - Fix: Use flex: 1 instead so grid properly allocates 1fr height to content - Add .shell.mobile-terminal-shell CSS to ensure full viewport coverage - Remove debug logging --- apps/web/src/components/terminal.tsx | 35 +++++----------------------- apps/web/src/styles.css | 20 ++++++++++++---- 2 files changed, 21 insertions(+), 34 deletions(-) diff --git a/apps/web/src/components/terminal.tsx b/apps/web/src/components/terminal.tsx index 75258c1..6403cc6 100644 --- a/apps/web/src/components/terminal.tsx +++ b/apps/web/src/components/terminal.tsx @@ -74,7 +74,6 @@ export const TerminalComponent: React.FC = ({ wsRef.current = ws; ws.onopen = () => { - console.log("[Terminal] WebSocket connected"); setStatus("connected"); setError(null); reconnectAttemptsRef.current = 0; @@ -107,16 +106,13 @@ export const TerminalComponent: React.FC = ({ if (!termRef.current) return; if (event.data instanceof Blob) { - console.log("[Terminal] Received blob data:", event.data.size, "bytes"); event.data.arrayBuffer().then((buffer) => { const data = new Uint8Array(buffer); - console.log("[Terminal] Writing", data.length, "bytes to terminal"); termRef.current?.write(data); }); } else if (typeof event.data === "string") { try { const msg = JSON.parse(event.data); - console.log("[Terminal] Received JSON message:", msg); if (msg.type === "status") { if (msg.status === "connected") { setStatus("connected"); @@ -188,12 +184,7 @@ export const TerminalComponent: React.FC = ({ }, [instanceId]); useEffect(() => { - if (!terminalRef.current) { - console.error("[Terminal] terminalRef is null"); - return; - } - - console.log("[Terminal] Initializing terminal for instance", instanceId, "isMobile:", isMobile); + if (!terminalRef.current) return; // Initialize terminal const currentFontSize = calculateFontSize(); @@ -237,25 +228,18 @@ export const TerminalComponent: React.FC = ({ // Define fitTerminal before connectWebSocket so it's available in onmessage const fitTerminal = () => { - if (!fitAddonRef.current || !termRef.current) { - console.log("[Terminal] fitTerminal: missing refs"); - return; - } + if (!fitAddonRef.current || !termRef.current) return; // Ensure terminal is opened and has valid dimensions - if (termRef.current.cols === 0 || termRef.current.rows === 0) { - console.log("[Terminal] fitTerminal: zero dimensions", termRef.current.cols, termRef.current.rows); - return; - } + if (termRef.current.cols === 0 || termRef.current.rows === 0) return; const oldCols = termRef.current.cols; const oldRows = termRef.current.rows; try { fitAddonRef.current.fit(); - } catch (err) { - console.error("[Terminal] fit error:", err); + } catch { + // Ignore fit errors during initialization return; } const { cols, rows } = termRef.current; - console.log("[Terminal] fitTerminal:", oldCols, "x", oldRows, "->", cols, "x", rows); // Force refresh if dimensions changed and are valid if ((cols !== oldCols || rows !== oldRows) && cols > 0 && rows > 0) { try { @@ -271,24 +255,17 @@ export const TerminalComponent: React.FC = ({ }; // Open xterm first (must happen before fit) - console.log("[Terminal] Opening xterm in container", container.clientWidth, "x", container.clientHeight); term.open(container); ws = connectWebSocket(); // Initial fit after layout settles (terminal must be opened first) const doInitialFit = () => { - if (!container.isConnected) { - console.log("[Terminal] Container not connected yet"); - return; - } + if (!container.isConnected) return; // Ensure container has dimensions before fitting - console.log("[Terminal] Container dimensions:", container.clientWidth, "x", container.clientHeight); if (container.clientWidth > 0 && container.clientHeight > 0) { - console.log("[Terminal] Calling initial fit"); fitTerminal(); } else { // Container not ready yet, try again - console.log("[Terminal] Container has no dimensions, retrying..."); requestAnimationFrame(doInitialFit); } }; diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index baccc9d..4b0564a 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -3112,17 +3112,27 @@ a.nav-item, Mobile Terminal Styles ============================================ */ - .mobile-terminal-wrapper { +/* Mobile terminal shell - fills viewport */ +.shell.mobile-terminal-shell { + height: 100vh; + height: 100dvh; + overflow: hidden; +} + +.shell.mobile-terminal-shell > * { + flex: 1; + min-height: 0; +} + +.mobile-terminal-wrapper { display: grid; grid-template-rows: auto 1fr auto; grid-template-areas: "header" "content" "keys"; - height: 100vh; - height: 100dvh; /* Dynamic viewport height for mobile */ - max-height: 100vh; - max-height: 100dvh; + flex: 1; + min-height: 0; background: #1e1e1e; position: relative; overflow: hidden; From 9751b65dce437b85f7a616a18864348639ba90c3 Mon Sep 17 00:00:00 2001 From: Fusion Date: Sun, 24 May 2026 22:56:02 +0200 Subject: [PATCH 3/4] debug: add more logging to trace mobile terminal black screen --- apps/web/src/components/terminal.tsx | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/apps/web/src/components/terminal.tsx b/apps/web/src/components/terminal.tsx index 6403cc6..7d8fa0e 100644 --- a/apps/web/src/components/terminal.tsx +++ b/apps/web/src/components/terminal.tsx @@ -74,6 +74,7 @@ export const TerminalComponent: React.FC = ({ wsRef.current = ws; ws.onopen = () => { + console.log("[Terminal] WebSocket connected"); setStatus("connected"); setError(null); reconnectAttemptsRef.current = 0; @@ -106,8 +107,10 @@ export const TerminalComponent: React.FC = ({ if (!termRef.current) return; if (event.data instanceof Blob) { + console.log("[Terminal] Received blob:", event.data.size, "bytes"); event.data.arrayBuffer().then((buffer) => { const data = new Uint8Array(buffer); + console.log("[Terminal] Writing data to terminal"); termRef.current?.write(data); }); } else if (typeof event.data === "string") { @@ -228,18 +231,25 @@ export const TerminalComponent: React.FC = ({ // Define fitTerminal before connectWebSocket so it's available in onmessage const fitTerminal = () => { - if (!fitAddonRef.current || !termRef.current) return; + if (!fitAddonRef.current || !termRef.current) { + console.log("[Terminal] fitTerminal: missing refs"); + return; + } // Ensure terminal is opened and has valid dimensions - if (termRef.current.cols === 0 || termRef.current.rows === 0) return; + if (termRef.current.cols === 0 || termRef.current.rows === 0) { + console.log("[Terminal] fitTerminal: zero dimensions", termRef.current.cols, termRef.current.rows); + return; + } const oldCols = termRef.current.cols; const oldRows = termRef.current.rows; try { fitAddonRef.current.fit(); - } catch { - // Ignore fit errors during initialization + } catch (err) { + console.error("[Terminal] fit error:", err); return; } const { cols, rows } = termRef.current; + console.log("[Terminal] fitTerminal:", oldCols, "x", oldRows, "->", cols, "x", rows); // Force refresh if dimensions changed and are valid if ((cols !== oldCols || rows !== oldRows) && cols > 0 && rows > 0) { try { @@ -255,7 +265,10 @@ export const TerminalComponent: React.FC = ({ }; // Open xterm first (must happen before fit) + console.log("[Terminal] Before open - container:", container.clientWidth, "x", container.clientHeight); term.open(container); + console.log("[Terminal] After open - container:", container.clientWidth, "x", container.clientHeight); + console.log("[Terminal] Term dimensions after open:", term.cols, "x", term.rows); ws = connectWebSocket(); // Initial fit after layout settles (terminal must be opened first) From 21285498aeeaf2a150fef4435a604f62ecda8f6e Mon Sep 17 00:00:00 2001 From: Fusion Date: Sun, 24 May 2026 23:00:32 +0200 Subject: [PATCH 4/4] fix: mobile terminal black screen - replace grid with flexbox layout - Root cause: CSS Grid 1fr row got 0 height inside flex parent - Fix: Replace grid layout with flexbox column for mobile terminal wrapper - Header and keys strip use flex-shrink: 0 - Content area uses flex: 1 to fill remaining space - Remove debug logging --- apps/web/src/components/terminal.tsx | 21 ++++----------------- apps/web/src/styles.css | 17 ++++++----------- 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/apps/web/src/components/terminal.tsx b/apps/web/src/components/terminal.tsx index 7d8fa0e..6403cc6 100644 --- a/apps/web/src/components/terminal.tsx +++ b/apps/web/src/components/terminal.tsx @@ -74,7 +74,6 @@ export const TerminalComponent: React.FC = ({ wsRef.current = ws; ws.onopen = () => { - console.log("[Terminal] WebSocket connected"); setStatus("connected"); setError(null); reconnectAttemptsRef.current = 0; @@ -107,10 +106,8 @@ export const TerminalComponent: React.FC = ({ if (!termRef.current) return; if (event.data instanceof Blob) { - console.log("[Terminal] Received blob:", event.data.size, "bytes"); event.data.arrayBuffer().then((buffer) => { const data = new Uint8Array(buffer); - console.log("[Terminal] Writing data to terminal"); termRef.current?.write(data); }); } else if (typeof event.data === "string") { @@ -231,25 +228,18 @@ export const TerminalComponent: React.FC = ({ // Define fitTerminal before connectWebSocket so it's available in onmessage const fitTerminal = () => { - if (!fitAddonRef.current || !termRef.current) { - console.log("[Terminal] fitTerminal: missing refs"); - return; - } + if (!fitAddonRef.current || !termRef.current) return; // Ensure terminal is opened and has valid dimensions - if (termRef.current.cols === 0 || termRef.current.rows === 0) { - console.log("[Terminal] fitTerminal: zero dimensions", termRef.current.cols, termRef.current.rows); - return; - } + if (termRef.current.cols === 0 || termRef.current.rows === 0) return; const oldCols = termRef.current.cols; const oldRows = termRef.current.rows; try { fitAddonRef.current.fit(); - } catch (err) { - console.error("[Terminal] fit error:", err); + } catch { + // Ignore fit errors during initialization return; } const { cols, rows } = termRef.current; - console.log("[Terminal] fitTerminal:", oldCols, "x", oldRows, "->", cols, "x", rows); // Force refresh if dimensions changed and are valid if ((cols !== oldCols || rows !== oldRows) && cols > 0 && rows > 0) { try { @@ -265,10 +255,7 @@ export const TerminalComponent: React.FC = ({ }; // Open xterm first (must happen before fit) - console.log("[Terminal] Before open - container:", container.clientWidth, "x", container.clientHeight); term.open(container); - console.log("[Terminal] After open - container:", container.clientWidth, "x", container.clientHeight); - console.log("[Terminal] Term dimensions after open:", term.cols, "x", term.rows); ws = connectWebSocket(); // Initial fit after layout settles (terminal must be opened first) diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index 4b0564a..bf9b56a 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -3125,12 +3125,8 @@ a.nav-item, } .mobile-terminal-wrapper { - display: grid; - grid-template-rows: auto 1fr auto; - grid-template-areas: - "header" - "content" - "keys"; + display: flex; + flex-direction: column; flex: 1; min-height: 0; background: #1e1e1e; @@ -3140,7 +3136,7 @@ a.nav-item, /* Mobile Terminal Header */ .mobile-terminal-header { - grid-area: header; + flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; @@ -3241,12 +3237,11 @@ a.nav-item, /* Mobile Terminal Content */ .mobile-terminal-content { - grid-area: content; + flex: 1; + min-height: 0; overflow: hidden; position: relative; background: #1e1e1e; - min-height: 0; - max-height: 100%; } /* Terminal wrapper - fills content area */ @@ -3280,7 +3275,7 @@ a.nav-item, /* Special Keys Strip */ .special-keys-strip { - grid-area: keys; + flex-shrink: 0; display: flex; align-items: center; gap: 2px;