From b11e6ed7ebc329ae43f1c0b5fc6ab7065d2649d2 Mon Sep 17 00:00:00 2001 From: Him188 Date: Tue, 10 Feb 2026 16:40:45 +0900 Subject: [PATCH] feat(web): persist new session agent and yolo preferences (#171) --- web/src/components/NewSession/index.tsx | 18 +++++++- .../components/NewSession/preferences.test.ts | 40 ++++++++++++++++++ web/src/components/NewSession/preferences.ts | 42 +++++++++++++++++++ 3 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 web/src/components/NewSession/preferences.test.ts create mode 100644 web/src/components/NewSession/preferences.ts diff --git a/web/src/components/NewSession/index.tsx b/web/src/components/NewSession/index.tsx index 8a77d5f4..08fd8156 100644 --- a/web/src/components/NewSession/index.tsx +++ b/web/src/components/NewSession/index.tsx @@ -13,6 +13,12 @@ import { AgentSelector } from './AgentSelector' import { DirectorySection } from './DirectorySection' import { MachineSelector } from './MachineSelector' import { ModelSelector } from './ModelSelector' +import { + loadPreferredAgent, + loadPreferredYoloMode, + savePreferredAgent, + savePreferredYoloMode, +} from './preferences' import { SessionTypeSelector } from './SessionTypeSelector' import { YoloToggle } from './YoloToggle' @@ -34,9 +40,9 @@ export function NewSession(props: { const [suppressSuggestions, setSuppressSuggestions] = useState(false) const [isDirectoryFocused, setIsDirectoryFocused] = useState(false) const [pathExistence, setPathExistence] = useState>({}) - const [agent, setAgent] = useState('claude') + const [agent, setAgent] = useState(loadPreferredAgent) const [model, setModel] = useState('auto') - const [yoloMode, setYoloMode] = useState(false) + const [yoloMode, setYoloMode] = useState(loadPreferredYoloMode) const [sessionType, setSessionType] = useState('simple') const [worktreeName, setWorktreeName] = useState('') const [error, setError] = useState(null) @@ -52,6 +58,14 @@ export function NewSession(props: { setModel('auto') }, [agent]) + useEffect(() => { + savePreferredAgent(agent) + }, [agent]) + + useEffect(() => { + savePreferredYoloMode(yoloMode) + }, [yoloMode]) + useEffect(() => { if (props.machines.length === 0) return if (machineId && props.machines.find((m) => m.id === machineId)) return diff --git a/web/src/components/NewSession/preferences.test.ts b/web/src/components/NewSession/preferences.test.ts new file mode 100644 index 00000000..0f5a8fa0 --- /dev/null +++ b/web/src/components/NewSession/preferences.test.ts @@ -0,0 +1,40 @@ +import { beforeEach, describe, expect, it } from 'vitest' +import { + loadPreferredAgent, + loadPreferredYoloMode, + savePreferredAgent, + savePreferredYoloMode, +} from './preferences' + +describe('NewSession preferences', () => { + beforeEach(() => { + localStorage.clear() + }) + + it('loads defaults when storage is empty', () => { + expect(loadPreferredAgent()).toBe('claude') + expect(loadPreferredYoloMode()).toBe(false) + }) + + it('loads saved values from storage', () => { + localStorage.setItem('hapi:newSession:agent', 'codex') + localStorage.setItem('hapi:newSession:yolo', 'true') + + expect(loadPreferredAgent()).toBe('codex') + expect(loadPreferredYoloMode()).toBe(true) + }) + + it('falls back to default agent on invalid stored value', () => { + localStorage.setItem('hapi:newSession:agent', 'unknown-agent') + + expect(loadPreferredAgent()).toBe('claude') + }) + + it('persists new values to storage', () => { + savePreferredAgent('gemini') + savePreferredYoloMode(true) + + expect(localStorage.getItem('hapi:newSession:agent')).toBe('gemini') + expect(localStorage.getItem('hapi:newSession:yolo')).toBe('true') + }) +}) diff --git a/web/src/components/NewSession/preferences.ts b/web/src/components/NewSession/preferences.ts new file mode 100644 index 00000000..b39a68ec --- /dev/null +++ b/web/src/components/NewSession/preferences.ts @@ -0,0 +1,42 @@ +import type { AgentType } from './types' + +const AGENT_STORAGE_KEY = 'hapi:newSession:agent' +const YOLO_STORAGE_KEY = 'hapi:newSession:yolo' + +const VALID_AGENTS: AgentType[] = ['claude', 'codex', 'gemini', 'opencode'] + +export function loadPreferredAgent(): AgentType { + try { + const stored = localStorage.getItem(AGENT_STORAGE_KEY) + if (stored && VALID_AGENTS.includes(stored as AgentType)) { + return stored as AgentType + } + } catch { + // Ignore storage errors + } + return 'claude' +} + +export function savePreferredAgent(agent: AgentType): void { + try { + localStorage.setItem(AGENT_STORAGE_KEY, agent) + } catch { + // Ignore storage errors + } +} + +export function loadPreferredYoloMode(): boolean { + try { + return localStorage.getItem(YOLO_STORAGE_KEY) === 'true' + } catch { + return false + } +} + +export function savePreferredYoloMode(enabled: boolean): void { + try { + localStorage.setItem(YOLO_STORAGE_KEY, enabled ? 'true' : 'false') + } catch { + // Ignore storage errors + } +}