terminal auto-reconnect and connection indicator in panel header
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import { useWorkspace } from '../../components/Workspace';
|
import { useWorkspace } from '../../components/Workspace';
|
||||||
import { useDashboardState } from 'state/useDashboardState';
|
import { useDashboardState } from 'state/useDashboardState';
|
||||||
|
import { useGlobal } from 'hooks/useGlobal';
|
||||||
|
import type { TerminalConnectionState } from './Terminal';
|
||||||
import { TerminalView } from './Terminal';
|
import { TerminalView } from './Terminal';
|
||||||
|
|
||||||
const EMPTY_TERMINALS: Record<string, string> = {};
|
const EMPTY_TERMINALS: Record<string, string> = {};
|
||||||
@@ -36,10 +38,22 @@ export const CommandTerminalWrapper = ({ panelId, command, statePrefix, onPanelR
|
|||||||
};
|
};
|
||||||
}, [panelId]);
|
}, [panelId]);
|
||||||
|
|
||||||
|
const [, setConnState] = useGlobal<TerminalConnectionState>(`terminal-conn-${panelId}`, 'disconnected');
|
||||||
|
const onConnectionChange = useCallback((state: TerminalConnectionState) => setConnState(state), [setConnState]);
|
||||||
|
|
||||||
if (!sessionId) return null;
|
if (!sessionId) return null;
|
||||||
|
|
||||||
const cwdPath = cwd && cwd !== '~' ? (cwd.startsWith('~') ? cwd : `~/${cwd.replace(/^\//, '')}`) : null;
|
const cwdPath = cwd && cwd !== '~' ? (cwd.startsWith('~') ? cwd : `~/${cwd.replace(/^\//, '')}`) : null;
|
||||||
const fullCommand = cwdPath ? `cd ${cwdPath} && ${command}` : command;
|
const fullCommand = cwdPath ? `cd ${cwdPath} && ${command}` : command;
|
||||||
|
|
||||||
return <TerminalView className="h-full w-full p-2" sessionId={sessionId} cwd={cwd} initialInput={fullCommand} onPanelRefresh={onPanelRefresh} />;
|
return (
|
||||||
|
<TerminalView
|
||||||
|
className="h-full w-full p-2"
|
||||||
|
sessionId={sessionId}
|
||||||
|
cwd={cwd}
|
||||||
|
initialInput={fullCommand}
|
||||||
|
onPanelRefresh={onPanelRefresh}
|
||||||
|
onConnectionChange={onConnectionChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,57 +1,78 @@
|
|||||||
import { TerminalSquare, Monitor, Columns2, PenLine, Sparkles } from 'lucide-react';
|
import { TerminalSquare, Monitor, Columns2, PenLine, Sparkles, Circle, Loader2 } from 'lucide-react';
|
||||||
import { useWorkspace } from '../../components/Workspace';
|
import { useWorkspace } from '../../components/Workspace';
|
||||||
|
import { useGlobal } from 'hooks/useGlobal';
|
||||||
|
import type { TerminalConnectionState } from './Terminal';
|
||||||
|
|
||||||
export const TerminalHeader = () => {
|
const ConnectionIndicator = ({ panelId }: { panelId: string }) => {
|
||||||
|
const [state] = useGlobal<TerminalConnectionState>(`terminal-conn-${panelId}`, 'disconnected');
|
||||||
|
|
||||||
|
if (state === 'connected') {
|
||||||
|
return <Circle className="h-2 w-2 shrink-0 fill-emerald-500 text-emerald-500" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state === 'reconnecting') {
|
||||||
|
return <Loader2 className="h-3 w-3 shrink-0 text-yellow-500 animate-spin" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Circle className="h-2 w-2 shrink-0 fill-red-500 text-red-500" />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TerminalHeader = ({ panelId }: { panelId: string }) => {
|
||||||
const { cwd } = useWorkspace();
|
const { cwd } = useWorkspace();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TerminalSquare className="h-3.5 w-3.5 shrink-0" />
|
<TerminalSquare className="h-3.5 w-3.5 shrink-0" />
|
||||||
<span className="text-xs font-medium shrink-0">Terminal</span>
|
<span className="text-xs font-medium shrink-0">Terminal</span>
|
||||||
|
<ConnectionIndicator panelId={panelId} />
|
||||||
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HostTerminalHeader = () => {
|
export const HostTerminalHeader = ({ panelId }: { panelId: string }) => {
|
||||||
const { cwd } = useWorkspace();
|
const { cwd } = useWorkspace();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Monitor className="h-3.5 w-3.5 shrink-0" />
|
<Monitor className="h-3.5 w-3.5 shrink-0" />
|
||||||
<span className="text-xs font-medium shrink-0">Terminal</span>
|
<span className="text-xs font-medium shrink-0">Terminal</span>
|
||||||
|
<ConnectionIndicator panelId={panelId} />
|
||||||
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TmuxHeader = () => {
|
export const TmuxHeader = ({ panelId }: { panelId: string }) => {
|
||||||
const { cwd } = useWorkspace();
|
const { cwd } = useWorkspace();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Columns2 className="h-3.5 w-3.5 shrink-0" />
|
<Columns2 className="h-3.5 w-3.5 shrink-0" />
|
||||||
<span className="text-xs font-medium shrink-0">Tmux</span>
|
<span className="text-xs font-medium shrink-0">Tmux</span>
|
||||||
|
<ConnectionIndicator panelId={panelId} />
|
||||||
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NvimHeader = () => {
|
export const NvimHeader = ({ panelId }: { panelId: string }) => {
|
||||||
const { cwd } = useWorkspace();
|
const { cwd } = useWorkspace();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PenLine className="h-3.5 w-3.5 shrink-0" />
|
<PenLine className="h-3.5 w-3.5 shrink-0" />
|
||||||
<span className="text-xs font-medium shrink-0">Neovim</span>
|
<span className="text-xs font-medium shrink-0">Neovim</span>
|
||||||
|
<ConnectionIndicator panelId={panelId} />
|
||||||
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ClaudeCodeHeader = () => {
|
export const ClaudeCodeHeader = ({ panelId }: { panelId: string }) => {
|
||||||
const { cwd } = useWorkspace();
|
const { cwd } = useWorkspace();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Sparkles className="h-3.5 w-3.5 shrink-0" />
|
<Sparkles className="h-3.5 w-3.5 shrink-0" />
|
||||||
<span className="text-xs font-medium shrink-0">Claude Code</span>
|
<span className="text-xs font-medium shrink-0">Claude Code</span>
|
||||||
|
<ConnectionIndicator panelId={panelId} />
|
||||||
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import { useWorkspace } from '../../components/Workspace';
|
import { useWorkspace } from '../../components/Workspace';
|
||||||
import { useAuth } from 'hooks/useAuth';
|
import { useAuth } from 'hooks/useAuth';
|
||||||
import { useDashboardState } from 'state/useDashboardState';
|
import { useDashboardState } from 'state/useDashboardState';
|
||||||
|
import { useGlobal } from 'hooks/useGlobal';
|
||||||
|
import type { TerminalConnectionState } from './Terminal';
|
||||||
import { TerminalView } from './Terminal';
|
import { TerminalView } from './Terminal';
|
||||||
|
|
||||||
const EMPTY_TERMINALS: Record<string, string> = {};
|
const EMPTY_TERMINALS: Record<string, string> = {};
|
||||||
@@ -39,7 +41,18 @@ export const HostTerminalWrapper = ({ panelId }: { panelId: string }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [, setConnState] = useGlobal<TerminalConnectionState>(`terminal-conn-${panelId}`, 'disconnected');
|
||||||
|
const onConnectionChange = useCallback((state: TerminalConnectionState) => setConnState(state), [setConnState]);
|
||||||
|
|
||||||
if (!sessionId) return null;
|
if (!sessionId) return null;
|
||||||
|
|
||||||
return <TerminalView className="h-full w-full p-2" sessionId={sessionId} sandboxed={false} cwd={cwd} />;
|
return (
|
||||||
|
<TerminalView
|
||||||
|
className="h-full w-full p-2"
|
||||||
|
sessionId={sessionId}
|
||||||
|
sandboxed={false}
|
||||||
|
cwd={cwd}
|
||||||
|
onConnectionChange={onConnectionChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ type TerminalTheme = {
|
|||||||
selectionBackground?: string;
|
selectionBackground?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TerminalConnectionState = 'connected' | 'disconnected' | 'reconnecting';
|
||||||
|
|
||||||
export type TerminalViewProps = {
|
export type TerminalViewProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
@@ -30,6 +32,7 @@ export type TerminalViewProps = {
|
|||||||
onCommandDone?: (exitCode: number, output: string) => void;
|
onCommandDone?: (exitCode: number, output: string) => void;
|
||||||
onDisconnect?: () => void;
|
onDisconnect?: () => void;
|
||||||
onPanelRefresh?: () => void;
|
onPanelRefresh?: () => void;
|
||||||
|
onConnectionChange?: (state: TerminalConnectionState) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_THEME: Required<TerminalTheme> = {
|
const DEFAULT_THEME: Required<TerminalTheme> = {
|
||||||
@@ -71,6 +74,7 @@ export const TerminalView = ({
|
|||||||
onCommandDone,
|
onCommandDone,
|
||||||
onDisconnect,
|
onDisconnect,
|
||||||
onPanelRefresh,
|
onPanelRefresh,
|
||||||
|
onConnectionChange,
|
||||||
}: TerminalViewProps) => {
|
}: TerminalViewProps) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const termRef = useRef<XTerm | null>(null);
|
const termRef = useRef<XTerm | null>(null);
|
||||||
@@ -81,6 +85,7 @@ export const TerminalView = ({
|
|||||||
const onCommandDoneRef = useRef<TerminalViewProps['onCommandDone']>(onCommandDone);
|
const onCommandDoneRef = useRef<TerminalViewProps['onCommandDone']>(onCommandDone);
|
||||||
const onDisconnectRef = useRef<TerminalViewProps['onDisconnect']>(onDisconnect);
|
const onDisconnectRef = useRef<TerminalViewProps['onDisconnect']>(onDisconnect);
|
||||||
const onPanelRefreshRef = useRef<TerminalViewProps['onPanelRefresh']>(onPanelRefresh);
|
const onPanelRefreshRef = useRef<TerminalViewProps['onPanelRefresh']>(onPanelRefresh);
|
||||||
|
const onConnectionChangeRef = useRef<TerminalViewProps['onConnectionChange']>(onConnectionChange);
|
||||||
const commandRef = useRef(command);
|
const commandRef = useRef(command);
|
||||||
const initialInputRef = useRef(initialInput);
|
const initialInputRef = useRef(initialInput);
|
||||||
|
|
||||||
@@ -89,6 +94,7 @@ export const TerminalView = ({
|
|||||||
onCommandDoneRef.current = onCommandDone;
|
onCommandDoneRef.current = onCommandDone;
|
||||||
onDisconnectRef.current = onDisconnect;
|
onDisconnectRef.current = onDisconnect;
|
||||||
onPanelRefreshRef.current = onPanelRefresh;
|
onPanelRefreshRef.current = onPanelRefresh;
|
||||||
|
onConnectionChangeRef.current = onConnectionChange;
|
||||||
commandRef.current = command;
|
commandRef.current = command;
|
||||||
initialInputRef.current = initialInput;
|
initialInputRef.current = initialInput;
|
||||||
|
|
||||||
@@ -127,17 +133,13 @@ export const TerminalView = ({
|
|||||||
termRef.current = term;
|
termRef.current = term;
|
||||||
onReadyRef.current?.(term);
|
onReadyRef.current?.(term);
|
||||||
|
|
||||||
// Wait for layout to fully settle (double rAF), then fit + connect
|
// Reconnect state
|
||||||
requestAnimationFrame(() => {
|
let reconnectAttempts = 0;
|
||||||
requestAnimationFrame(() => {
|
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
if (disposed) return;
|
let processExited = false;
|
||||||
|
const MAX_RECONNECT_ATTEMPTS = 5;
|
||||||
|
const RECONNECT_DELAYS = [1000, 2000, 3000, 5000, 5000];
|
||||||
|
|
||||||
fitAddon.fit();
|
|
||||||
const cols = term.cols;
|
|
||||||
const rows = term.rows;
|
|
||||||
|
|
||||||
const ws = new WebSocket(buildWsUrl(wsPath, sessionId, sandboxed, cwd, command, cols, rows));
|
|
||||||
wsRef.current = ws;
|
|
||||||
let commandSent = false;
|
let commandSent = false;
|
||||||
let commandDone = false;
|
let commandDone = false;
|
||||||
let initialInputSent = false;
|
let initialInputSent = false;
|
||||||
@@ -146,7 +148,26 @@ export const TerminalView = ({
|
|||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
const stripAnsi = (s: string) => s.replace(/\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07/g, '');
|
const stripAnsi = (s: string) => s.replace(/\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07/g, '');
|
||||||
|
|
||||||
|
const connect = () => {
|
||||||
|
if (disposed) return;
|
||||||
|
|
||||||
|
fitAddon.fit();
|
||||||
|
const cols = term.cols;
|
||||||
|
const rows = term.rows;
|
||||||
|
|
||||||
|
const ws = new WebSocket(buildWsUrl(wsPath, sessionId, sandboxed, cwd, command, cols, rows));
|
||||||
|
wsRef.current = ws;
|
||||||
|
|
||||||
|
const cleanupWs = () => {
|
||||||
|
ws.removeEventListener('open', handleOpen);
|
||||||
|
ws.removeEventListener('message', handleMessage);
|
||||||
|
ws.removeEventListener('close', handleClose);
|
||||||
|
ws.close();
|
||||||
|
};
|
||||||
|
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
|
reconnectAttempts = 0;
|
||||||
|
onConnectionChangeRef.current?.('connected');
|
||||||
ws.send(JSON.stringify({ type: 'resize', cols, rows }));
|
ws.send(JSON.stringify({ type: 'resize', cols, rows }));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -188,6 +209,7 @@ export const TerminalView = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (msg.type === 'exit') {
|
} else if (msg.type === 'exit') {
|
||||||
|
processExited = true;
|
||||||
term.write('\r\n[Process exited]\r\n');
|
term.write('\r\n[Process exited]\r\n');
|
||||||
onExitRef.current?.();
|
onExitRef.current?.();
|
||||||
} else if (msg.type === 'detached') {
|
} else if (msg.type === 'detached') {
|
||||||
@@ -201,34 +223,74 @@ export const TerminalView = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
|
cleanupWs();
|
||||||
|
if (disposed || processExited) {
|
||||||
|
onConnectionChangeRef.current?.('disconnected');
|
||||||
term.write('\r\n[Disconnected]\r\n');
|
term.write('\r\n[Disconnected]\r\n');
|
||||||
onDisconnectRef.current?.();
|
onDisconnectRef.current?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
||||||
|
const delay = RECONNECT_DELAYS[reconnectAttempts] ?? 5000;
|
||||||
|
reconnectAttempts++;
|
||||||
|
onConnectionChangeRef.current?.('reconnecting');
|
||||||
|
term.write(`\r\n[Reconnecting (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...]\r\n`);
|
||||||
|
reconnectTimer = setTimeout(connect, delay);
|
||||||
|
} else {
|
||||||
|
onConnectionChangeRef.current?.('disconnected');
|
||||||
|
term.write('\r\n[Disconnected]\r\n');
|
||||||
|
onDisconnectRef.current?.();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.addEventListener('open', handleOpen);
|
ws.addEventListener('open', handleOpen);
|
||||||
ws.addEventListener('message', handleMessage);
|
ws.addEventListener('message', handleMessage);
|
||||||
ws.addEventListener('close', handleClose);
|
ws.addEventListener('close', handleClose);
|
||||||
|
|
||||||
|
(container as any).__terminalCleanup = () => {
|
||||||
|
cleanupWs();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const dataDisposable = term.onData((data) => {
|
const dataDisposable = term.onData((data) => {
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||||
ws.send(JSON.stringify({ type: 'input', data }));
|
wsRef.current.send(JSON.stringify({ type: 'input', data }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
(container as any).__terminalCleanup = () => {
|
// Reconnect when tab becomes visible again
|
||||||
dataDisposable.dispose();
|
const handleVisibilityChange = () => {
|
||||||
ws.removeEventListener('open', handleOpen);
|
if (document.visibilityState === 'visible' && !disposed && !processExited) {
|
||||||
ws.removeEventListener('message', handleMessage);
|
if (!wsRef.current || wsRef.current.readyState === WebSocket.CLOSED) {
|
||||||
ws.removeEventListener('close', handleClose);
|
reconnectAttempts = 0;
|
||||||
ws.close();
|
connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||||
|
|
||||||
|
// Wait for layout to fully settle (double rAF), then fit + connect
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
connect();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const originalCleanup = () => {
|
||||||
|
dataDisposable.dispose();
|
||||||
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||||
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
||||||
|
const wsCleanup = (container as any).__terminalCleanup as (() => void) | undefined;
|
||||||
|
wsCleanup?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
(container as any).__terminalFullCleanup = originalCleanup;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
disposed = true;
|
disposed = true;
|
||||||
const cleanup = (container as any).__terminalCleanup as (() => void) | undefined;
|
const cleanup = (container as any).__terminalFullCleanup as (() => void) | undefined;
|
||||||
cleanup?.();
|
cleanup?.();
|
||||||
|
delete (container as any).__terminalFullCleanup;
|
||||||
delete (container as any).__terminalCleanup;
|
delete (container as any).__terminalCleanup;
|
||||||
wsRef.current = null;
|
wsRef.current = null;
|
||||||
termRef.current?.dispose();
|
termRef.current?.dispose();
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import { useWorkspace } from '../../components/Workspace';
|
import { useWorkspace } from '../../components/Workspace';
|
||||||
import { useDashboardState } from 'state/useDashboardState';
|
import { useDashboardState } from 'state/useDashboardState';
|
||||||
|
import { useGlobal } from 'hooks/useGlobal';
|
||||||
|
import type { TerminalConnectionState } from './Terminal';
|
||||||
import { TerminalView } from './Terminal';
|
import { TerminalView } from './Terminal';
|
||||||
import { useTerminalMode } from './useTerminalMode';
|
import { useTerminalMode } from './useTerminalMode';
|
||||||
|
|
||||||
@@ -34,7 +36,18 @@ export const TerminalWrapper = ({ panelId }: { panelId: string }) => {
|
|||||||
};
|
};
|
||||||
}, [panelId]);
|
}, [panelId]);
|
||||||
|
|
||||||
|
const [, setConnState] = useGlobal<TerminalConnectionState>(`terminal-conn-${panelId}`, 'disconnected');
|
||||||
|
const onConnectionChange = useCallback((state: TerminalConnectionState) => setConnState(state), [setConnState]);
|
||||||
|
|
||||||
if (!sessionId) return null;
|
if (!sessionId) return null;
|
||||||
|
|
||||||
return <TerminalView className="h-full w-full p-2" sessionId={sessionId} sandboxed={sandboxed} cwd={cwd} />;
|
return (
|
||||||
|
<TerminalView
|
||||||
|
className="h-full w-full p-2"
|
||||||
|
sessionId={sessionId}
|
||||||
|
sandboxed={sandboxed}
|
||||||
|
cwd={cwd}
|
||||||
|
onConnectionChange={onConnectionChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { HostTerminalWrapper } from './HostTerminalWrapper';
|
|||||||
import { CommandTerminalWrapper } from './CommandTerminalWrapper';
|
import { CommandTerminalWrapper } from './CommandTerminalWrapper';
|
||||||
import { TerminalHeader, HostTerminalHeader, TmuxHeader, NvimHeader, ClaudeCodeHeader } from './Headers';
|
import { TerminalHeader, HostTerminalHeader, TmuxHeader, NvimHeader, ClaudeCodeHeader } from './Headers';
|
||||||
|
|
||||||
export { TerminalView, type TerminalViewProps } from './Terminal';
|
export { TerminalView, type TerminalViewProps, type TerminalConnectionState } from './Terminal';
|
||||||
|
|
||||||
const TmuxWrapper = ({ panelId }: { panelId: string }) => (
|
const TmuxWrapper = ({ panelId }: { panelId: string }) => (
|
||||||
<CommandTerminalWrapper panelId={panelId} command="tmux" statePrefix="tmux" />
|
<CommandTerminalWrapper panelId={panelId} command="tmux" statePrefix="tmux" />
|
||||||
|
|||||||
Reference in New Issue
Block a user