fix(app): settings review — startup theme default, slider/shell input UX, dedupe accents, memoize palette

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-14 19:10:19 +07:00
parent 9ca1ff3bc5
commit 5e6cf4d982
4 changed files with 29 additions and 20 deletions
+19 -12
View File
@@ -1,17 +1,21 @@
import { useEffect, useRef, useState } from "react";
import { COLORS, FONT } from "./theme";
import { COLORS, FONT, ACCENTS } from "./theme";
import { setConfig, shutdownDaemon, restartDaemon } from "./socketBridge";
import type { ConfigView, DaemonHealth } from "./socketBridge";
const FONTS = ["JetBrains Mono", "Menlo", "Monaco", "SF Mono", "Fira Code", "Cascadia Code"];
const ACCENTS: { id: string; hex: string }[] = [
{ id: "blue", hex: "#4C8DFF" }, { id: "teal", hex: "#34D3C2" }, { id: "purple", hex: "#9B7BFF" },
{ id: "green", hex: "#3FB950" }, { id: "orange", hex: "#F2934B" },
];
export function Settings({ config, health, onClose }: { config: ConfigView; health: DaemonHealth | null; onClose: () => void }) {
const ref = useRef<HTMLDivElement>(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 (
<div onMouseDown={onClose} style={{ position: "fixed", inset: 0, zIndex: 2000, background: "#000A", display: "flex", alignItems: "center", justifyContent: "center" }}>
<div ref={ref} tabIndex={-1} onMouseDown={(e) => e.stopPropagation()} onKeyDown={(e) => { e.stopPropagation(); if (e.key === "Escape") onClose(); }}
@@ -24,8 +28,11 @@ export function Settings({ config, health, onClose }: { config: ConfigView; heal
{FONTS.map((f) => <option key={f} value={f}>{f}</option>)}
</select>
<div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 18 }}>
<span style={{ fontSize: 12, color: COLORS.textSecondary }}>Size {config.font_size}</span>
<input type="range" min={10} max={20} value={config.font_size} onChange={(e) => void setConfig({ font_size: Number(e.target.value) })} style={{ flex: 1 }} />
<span style={{ fontSize: 12, color: COLORS.textSecondary }}>Size {sizeLocal}</span>
<input type="range" min={10} max={20} value={sizeLocal}
onChange={(e) => setSizeLocal(Number(e.target.value))}
onPointerUp={() => void setConfig({ font_size: sizeLocal })}
style={{ flex: 1 }} />
</div>
<div style={{ fontSize: 12, color: COLORS.textSecondary, marginBottom: 6 }}>Theme</div>
@@ -39,15 +46,15 @@ export function Settings({ config, health, onClose }: { config: ConfigView; heal
</div>
<div style={{ fontSize: 12, color: COLORS.textSecondary, marginBottom: 6 }}>Accent</div>
<div style={{ display: "flex", gap: 10, marginBottom: 18 }}>
{ACCENTS.map((a) => (
<button key={a.id} onClick={() => void setConfig({ accent: a.id })} aria-label={a.id}
style={{ width: 26, height: 26, borderRadius: "50%", background: a.hex, cursor: "pointer",
border: config.accent === a.id ? `2px solid ${COLORS.textPrimary}` : "2px solid transparent" }} />
{Object.entries(ACCENTS).map(([id, hex]) => (
<button key={id} onClick={() => void setConfig({ accent: id })} aria-label={id}
style={{ width: 26, height: 26, borderRadius: "50%", background: hex, cursor: "pointer",
border: config.accent === id ? `2px solid ${COLORS.textPrimary}` : "2px solid transparent" }} />
))}
</div>
<div style={{ fontSize: 12, color: COLORS.textSecondary, marginBottom: 6 }}>Default shell (empty = auto)</div>
<input defaultValue={config.default_shell} onBlur={(e) => void setConfig({ default_shell: e.target.value })}
<input value={shellLocal} onChange={(e) => 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 }} />
<DaemonSection health={health} />