+
+
+| Surface | What it shows |
+|---------|---------------|
+| **Masthead** | Connection state (`Connected`, `Reconnecting`, or `Disconnected`) with a live status dot that pulses cyan while the session is active. The kicker reads `HOST CONSOLE · {node}`. When the console was opened from a stack, a small `← {stack-name}` link sits beside the state word so you can return to the stack editor. |
+| **Metadata** | Right side of the masthead: the active **shell** (e.g. `BASH`), the current **viewport** (`{cols}×{rows}` as reported by the fit addon), and **session uptime** since the shell opened. |
+| **Terminal well** | The interactive xterm session, sized to fill the remaining height. |
+| **Chip strip** | Floating bottom-right controls: **Copy** (copies the current selection), **Clear** (wipes the visible scrollback without killing the shell), **Download** (exports the full scrollback as a timestamped `.txt`), and **Reconnect** (closes and reopens the WebSocket without leaving the page). |
+
+
+
+
## Shell type
diff --git a/docs/images/host-console/host-console-chip-strip.png b/docs/images/host-console/host-console-chip-strip.png
new file mode 100644
index 00000000..2cd159bc
Binary files /dev/null and b/docs/images/host-console/host-console-chip-strip.png differ
diff --git a/docs/images/host-console/host-console-masthead.png b/docs/images/host-console/host-console-masthead.png
new file mode 100644
index 00000000..d82af41b
Binary files /dev/null and b/docs/images/host-console/host-console-masthead.png differ
diff --git a/docs/images/host-console/host-console-overview.png b/docs/images/host-console/host-console-overview.png
index 2c2742f0..9cef0ca9 100644
Binary files a/docs/images/host-console/host-console-overview.png and b/docs/images/host-console/host-console-overview.png differ
diff --git a/frontend/src/components/HostConsole.tsx b/frontend/src/components/HostConsole.tsx
index a94ff01a..6f8e93d7 100644
--- a/frontend/src/components/HostConsole.tsx
+++ b/frontend/src/components/HostConsole.tsx
@@ -1,8 +1,10 @@
import { useEffect, useRef, useState, useCallback } from 'react';
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
-import { Terminal as TerminalIcon, X } from 'lucide-react';
+import { SerializeAddon } from '@xterm/addon-serialize';
+import { ArrowLeft, Copy, Trash2, Download, RefreshCw } from 'lucide-react';
import { Button } from './ui/button';
+import { PageMasthead, type MastheadTone } from './ui/PageMasthead';
import '@xterm/xterm/css/xterm.css';
import { useNodes } from '@/context/NodeContext';
@@ -11,6 +13,9 @@ interface HostConsoleProps {
onClose: () => void;
}
+// Window considered "live" for the masthead pulsing dot.
+const LIVE_WINDOW_MS = 5_000;
+
/** Build the xterm theme from CSS custom properties (resolved once per call). */
function getTerminalTheme() {
const s = getComputedStyle(document.documentElement);
@@ -23,25 +28,41 @@ function getTerminalTheme() {
};
}
+function formatUptime(ms: number): string {
+ const totalSeconds = Math.max(0, Math.floor(ms / 1000));
+ const h = Math.floor(totalSeconds / 3600);
+ const m = Math.floor((totalSeconds % 3600) / 60);
+ const s = totalSeconds % 60;
+ if (h > 0) return `${h}H ${m.toString().padStart(2, '0')}M`;
+ return `${m}:${s.toString().padStart(2, '0')} UP`;
+}
+
+type ConnState = 'reconnecting' | 'connected' | 'disconnected';
+
export default function HostConsole({ stackName, onClose }: HostConsoleProps) {
const { activeNode } = useNodes();
const terminalRef = useRef