/** * @file HomeDirSetting.tsx * @description One editable home directory (CLAUDE_HOME in Claude Code → * Overview, CODEX_HOME in Codex → Overview): a path field with Save, the * server's refusal inline, the saved value while the field differs from it, * and a warning when the real * environment sets the variable (the environment wins at the next start, so a * saved value would not survive a restart). Enter saves; a caller may ask for * a confirmation first (`confirm`). The server validates and persists; this * component only loads, submits and reports. * @author Nguyễn Ngọc Trí Vĩ */ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { AlertTriangle, FolderOpen } from "lucide-react"; import { confirmDialog } from "../../lib/confirm"; import { isSubmitEnter } from "../../lib/keys"; import { SettingsSection } from "./SettingsRow"; import { PathPickButton } from "../PathPickButton"; export interface HomeDirValue { value: string; envOwned: boolean; /** Shown after a save that succeeded only in part (e.g. a lane not updated). */ warning?: string | null; } interface Props { /** `
` — a deep-link target. */ id: string; title: string; description: string; /** Optional second line under the description. */ note?: string; placeholder: string; /** Ask this before saving (a change with wide effects, like CLAUDE_HOME). */ confirm?: { title: string; message?: string }; load: () => Promise; save: (path: string) => Promise; } /** * An editable home directory with its own load/save cycle. * @returns The section, or its skeleton-free shell while loading. */ export function HomeDirSetting({ id, title, description, note, placeholder, confirm, load, save, }: Props) { const { t } = useTranslation("settings"); const [current, setCurrent] = useState(null); const [input, setInput] = useState(""); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); useEffect(() => { let live = true; load() .then((v) => { if (!live) return; setCurrent(v); setInput(v.value); }) .catch((err) => live && setError(err instanceof Error ? err.message : String(err))); return () => { live = false; }; // `load` is a stable api call per mount; re-running on identity would loop. // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleSave = async () => { if (!current || input === current.value || saving) return; if (confirm && !(await confirmDialog({ ...confirm, confirmLabel: t("homeDir.save") }))) return; setSaving(true); setError(null); try { const next = await save(input); setCurrent(next); setInput(next.value); } catch (err) { setError(err instanceof Error ? err.message : t("homeDir.saveFailed")); } finally { setSaving(false); } }; return ( {title} } description={description} >
{note &&

{note}

}
{ setInput(e.target.value); setError(null); }} onKeyDown={(e) => { if (isSubmitEnter(e)) { e.preventDefault(); void handleSave(); } }} className="input flex-1 font-mono" placeholder={placeholder} /> { setInput(p); setError(null); }} />
{error &&

{error}

} {/* The saved value, only while the field says something else. */} {current && input !== current.value && (

{t("homeDir.current")} {current.value}

)} {current?.envOwned && (

{t("homeDir.envOwned")}

)} {current?.warning && (

{current.warning}

)}
); }