import { useEffect, useRef, useState } from 'react' import { Settings2, Plus, X, Columns2, Grid2x2, SquareSlash, Sparkles } from 'lucide-react' import { api, type Integration, type PromptPreset, type PromptStatus } from '../lib/api' import { useTerminalSessions } from '../lib/TerminalSessionContext' import { TERM_THEMES } from '../lib/terminalPrefs' const GOLD = '#C8A434' const TEXT_SECONDARY = '#7A7D85' const FONT_SIZES = [11, 12, 13, 14, 15, 16] // "Symbols Nerd Font Mono" is appended as a glyph-only fallback to every option, so // distro icons / git branch / etc. from prompts like Starship render instead of // showing as boxes — it has no letterforms of its own, so it never overrides the // chosen base font for normal text. const NERD_FALLBACK = '"Symbols Nerd Font Mono"' const FONT_FAMILIES = [ { name: 'Monospace', value: `ui-monospace, SFMono-Regular, Menlo, monospace, ${NERD_FALLBACK}` }, { name: 'Fira Code', value: `"Fira Code", ui-monospace, monospace, ${NERD_FALLBACK}` }, { name: 'JetBrains Mono', value: `"JetBrains Mono", ui-monospace, monospace, ${NERD_FALLBACK}` }, ] export default function Terminal() { const [hosts, setHosts] = useState([]) const [showPrefs, setShowPrefs] = useState(false) const { tabs, activeTabId, activePaneId, prefs, setActiveTabId, setActivePaneId, setPrefs, addTab, closeTab, setPaneCount, setPaneHost, } = useTerminalSessions() useEffect(() => { api.listIntegrations().then(({ integrations }) => { setHosts(integrations.filter((i) => i.type === 'ssh')) }) }, []) const activeTab = tabs.find((t) => t.id === activeTabId) ?? tabs[0] const paneGridClass = activeTab.panes.length === 1 ? 'grid-cols-1 grid-rows-1' : activeTab.panes.length === 2 ? 'grid-cols-2 grid-rows-1' : 'grid-cols-2 grid-rows-2' const activePaneHostId = activeTab.panes.find((p) => p.id === activePaneId)?.hostId ?? null return (

SSH Hosts

{hosts.length === 0 && (

No SSH integrations configured. Add one in Settings → Integrations.

)}
{hosts.map((h) => ( ))}

Click a pane, then a host to connect it. Sessions stay connected when you switch pages — close a tab or pane to disconnect.

{tabs.map((tab) => (
setActiveTabId(tab.id)} className="flex shrink-0 cursor-pointer items-center gap-2 rounded-md px-3 py-1.5 text-xs" style={{ background: tab.id === activeTabId ? 'rgba(200,164,52,0.15)' : 'rgba(255,255,255,0.04)', color: tab.id === activeTabId ? GOLD : '#E8E6E0', }} > {tab.name} { e.stopPropagation() closeTab(tab.id) }} />
))}
{showPrefs && (
)}
{activeTab.panes.map((pane) => ( setActivePaneId(pane.id)} /> ))}
) } function TerminalPane({ paneId, hostId, hosts, active, onFocus, }: { paneId: number hostId: number | null hosts: Integration[] active: boolean onFocus: () => void }) { const { attachPane, getPaneInfo, reconnectTmux, version } = useTerminalSessions() const cellRef = useRef(null) // Mount the persistent xterm DOM node owned by the provider into this cell; // on unmount (route change or layout change) the wrapper is moved back to the // hidden root rather than disposed, so the session keeps running. useEffect(() => { if (!cellRef.current) return const detach = attachPane(paneId, cellRef.current) return detach // eslint-disable-next-line react-hooks/exhaustive-deps }, [paneId]) // `version` is read so the header re-renders when live pane state changes. void version const info = getPaneInfo(paneId) const host = hosts.find((h) => h.id === hostId) return (
{host ? (info.connected ? `Connected — ${host.name}` : `Disconnected — ${host.name}`) : 'Select a host to connect'} {host && ( )}
) } /** * Lets a user pick one of a few Starship prompt looks and install it (Starship * + a Nerd Font, if not already present) on the active pane's SSH host with * one click, instead of running a script by hand. */ function ShellPromptControl({ hostId }: { hostId: number | null }) { const [presets, setPresets] = useState([]) const [status, setStatus] = useState(null) const [selected, setSelected] = useState('') const [installing, setInstalling] = useState(false) const [message, setMessage] = useState(null) const [error, setError] = useState(null) useEffect(() => { setStatus(null) setMessage(null) setError(null) if (hostId === null) return api .getShellPromptStatus(hostId) .then(({ presets, status }) => { setPresets(presets) setStatus(status) setSelected(status.configuredPreset ?? presets[0]?.id ?? '') }) .catch((err) => setError(err instanceof Error ? err.message : 'Failed to check host')) }, [hostId]) if (hostId === null) { return Connect a host to set up its shell prompt } async function handleInstall() { if (!selected) return setInstalling(true) setError(null) setMessage(null) try { await api.installShellPrompt(hostId!, selected) setMessage('Installed — open a new terminal session to this host to see it.') const { status } = await api.getShellPromptStatus(hostId!) setStatus(status) } catch (err) { setError(err instanceof Error ? err.message : 'Install failed') } finally { setInstalling(false) } } return (
{message && {message}} {error && {error}}
) }