diff --git a/src/app/components/SerialConsolePage.tsx b/src/app/components/SerialConsolePage.tsx index 535382e..7ddc6c0 100644 --- a/src/app/components/SerialConsolePage.tsx +++ b/src/app/components/SerialConsolePage.tsx @@ -13,13 +13,9 @@ export default function SerialConsolePage({ onBack }: Props) { const fitRef = useRef(null); const lineBuffer = useRef(''); const echoQueue = useRef([]); + const { status, send, subscribe } = useWs(); - // Keep a stable ref to `send` so the xterm onData closure never goes stale - const sendRef = useRef(send); - useEffect(() => { sendRef.current = send; }, [send]); - - // Initialize xterm once on mount useEffect(() => { const el = containerRef.current; if (!el) return; @@ -28,12 +24,7 @@ export default function SerialConsolePage({ onBack }: Props) { cursorBlink: true, fontSize: 14, fontFamily: '"Cascadia Code","Fira Code",Menlo,Monaco,"Courier New",monospace', - theme: { - background: '#0a0a0a', - foreground: '#d4d4d4', - cursor: '#d4d4d4', - selectionBackground: '#264f78', - }, + theme: { background: '#0a0a0a', foreground: '#d4d4d4', cursor: '#d4d4d4', selectionBackground: '#264f78' }, scrollback: 5000, convertEol: true, }); @@ -45,28 +36,29 @@ export default function SerialConsolePage({ onBack }: Props) { termRef.current = term; fitRef.current = fit; - term.writeln('\x1b[2m── Meatloaf Serial Console ──\x1b[0m'); - term.writeln(''); + term.write('\x1b[2m── Meatloaf Serial Console ──\x1b[0m\r\n\r\n'); - term.onData((data) => { - for (const char of data) { - if (char === '\r' || char === '\n') { - const line = lineBuffer.current + '\r'; + term.onData(data => { + for (const ch of data) { + if (ch === '\r' || ch === '\n') { + const line = lineBuffer.current; lineBuffer.current = ''; term.write('\r\n'); - echoQueue.current.push(line); - sendRef.current(line); - } else if (char === '\x7f' || char === '\b') { + if (line) { + echoQueue.current.push(line); + send(line); + } + } else if (ch === '\x7f' || ch === '\b') { if (lineBuffer.current.length > 0) { lineBuffer.current = lineBuffer.current.slice(0, -1); term.write('\b \b'); } - } else if (char === '\x03') { + } else if (ch === '\x03') { lineBuffer.current = ''; term.write('^C\r\n'); - } else if (char >= ' ') { - lineBuffer.current += char; - term.write(char); + } else if (ch >= ' ') { + lineBuffer.current += ch; + term.write(ch); } } }); @@ -82,21 +74,19 @@ export default function SerialConsolePage({ onBack }: Props) { }; }, []); - // Forward all incoming WS messages to the terminal, suppressing our own echoes useEffect(() => { - return subscribe((msg) => { + return subscribe((msg: string) => { + const term = termRef.current; + if (!term) return; const idx = echoQueue.current.indexOf(msg); - if (idx !== -1) { - echoQueue.current.splice(idx, 1); - return; - } - termRef.current?.write(msg); + if (idx !== -1) { echoQueue.current.splice(idx, 1); return; } + term.write(msg.endsWith('\n') ? msg : msg + '\r\n'); }); }, [subscribe]); const statusColor = - status === 'connected' ? 'text-green-400' : - status === 'connecting' ? 'text-amber-400' : 'text-neutral-500'; + status === 'connected' ? 'text-green-400' : + status === 'connecting' ? 'text-amber-400' : 'text-neutral-500'; const statusLabel = status === 'connected' ? 'Connected' : @@ -105,7 +95,6 @@ export default function SerialConsolePage({ onBack }: Props) { return (
- {/* Header */}
@@ -129,13 +117,12 @@ export default function SerialConsolePage({ onBack }: Props) {
- {/* Terminal */}