import { useEffect, useRef, useState } from "react"; import { X } from "lucide-react"; import { COLORS, FONT, ACCENTS } from "./theme"; import { setConfig, restartDaemon } from "./socketBridge"; import type { ConfigView, DaemonHealth } from "./socketBridge"; const FONTS = ["JetBrains Mono", "Menlo", "Monaco", "SF Mono", "Fira Code", "Cascadia Code"]; export function Settings({ config, health, onClose, onReload }: { config: ConfigView; health: DaemonHealth | null; onClose: () => void; onReload: () => void }) { const ref = useRef(null); useEffect(() => { ref.current?.focus(); }, []); // Fix 2: local state for font-size slider — committed only on pointer release. const [sizeLocal, setSizeLocal] = useState(config.font_size); useEffect(() => { setSizeLocal(config.font_size); }, [config.font_size]); // Fix 3: controlled shell input — synced from config, committed on blur. const [shellLocal, setShellLocal] = useState(config.default_shell); useEffect(() => { setShellLocal(config.default_shell); }, [config.default_shell]); return (
e.stopPropagation()} onKeyDown={(e) => { e.stopPropagation(); if (e.key === "Escape") onClose(); }} style={{ width: 520, maxHeight: "80vh", overflowY: "auto", background: COLORS.bgApp, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 14, padding: 24, color: COLORS.textPrimary, fontFamily: FONT.ui }}>
Settings
Terminal font
Size {sizeLocal} setSizeLocal(Number(e.target.value))} onPointerUp={() => void setConfig({ font_size: sizeLocal })} style={{ flex: 1 }} />
Theme
{(["dark", "light"] as const).map((t) => ( ))}
Accent
{Object.entries(ACCENTS).map(([id, hex]) => (
Default shell (empty = auto)
setShellLocal(e.target.value)} onBlur={() => void setConfig({ default_shell: shellLocal })} style={{ width: "100%", padding: 8, marginBottom: 18, background: COLORS.bgPanel, color: COLORS.textPrimary, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 8 }} />
); } function fmtUptime(ms: number): string { const s = Math.max(0, Math.floor((Date.now() - ms) / 1000)); if (s < 60) return `${s}s`; if (s < 3600) return `${Math.floor(s / 60)}m`; return `${Math.floor(s / 3600)}h ${Math.floor((s % 3600) / 60)}m`; } function DaemonSection({ health, onReload }: { health: DaemonHealth | null; onReload: () => void }) { const [confirm, setConfirm] = useState(false); // Tick so uptime counts up live while the modal is open. const [, setTick] = useState(0); useEffect(() => { const t = setInterval(() => setTick((n) => n + 1), 1000); return () => clearInterval(t); }, []); return (
Daemon
{health ? (<>
version {health.version}{health.build ? ` · ${health.build}` : ""} · pid {health.pid}
uptime {fmtUptime(health.started_at_ms)}
) :
offline
}
{confirm && (
Restart the daemon? Running sessions end and respawn; panels re-attach automatically.
)}
); }