diff --git a/src/hooks/useDirectorySettings.ts b/src/hooks/useDirectorySettings.ts index e62a25dea..8c7f59806 100644 --- a/src/hooks/useDirectorySettings.ts +++ b/src/hooks/useDirectorySettings.ts @@ -102,6 +102,8 @@ export interface UseDirectorySettingsResult { resolvedDirs: ResolvedDirectories; isLoading: boolean; initialAppConfigDir?: string; + /** 保存成功后把已保存的数据目录换成刚存的值 */ + commitAppConfigDir: (value: string | undefined) => void; updateDirectory: (app: DirectoryAppId, value?: string) => void; updateAppConfigDir: (value?: string) => void; browseDirectory: (app: DirectoryAppId) => Promise; @@ -157,7 +159,16 @@ export function useDirectorySettings({ hermes: "", pi: "", }); + // 已保存的数据目录(保存按钮和「需要重启」都和它比)。ref 给回调用,state 让比较它的 + // 组件在保存后重渲染:保存成功要把它更新成刚存的值,否则保存按钮一直在、改回去存不了 const initialAppConfigDirRef = useRef(undefined); + const [initialAppConfigDir, setInitialAppConfigDir] = useState< + string | undefined + >(undefined); + const commitAppConfigDir = useCallback((value: string | undefined) => { + initialAppConfigDirRef.current = value; + setInitialAppConfigDir(value); + }, []); // 加载目录信息 useEffect(() => { @@ -223,7 +234,7 @@ export function useDirectorySettings({ }; setAppConfigDir(normalizedOverride); - initialAppConfigDirRef.current = normalizedOverride; + commitAppConfigDir(normalizedOverride); setResolvedDirs({ appConfig: normalizedOverride ?? defaultsRef.current.appConfig, @@ -387,7 +398,8 @@ export function useDirectorySettings({ appConfigDir, resolvedDirs, isLoading, - initialAppConfigDir: initialAppConfigDirRef.current, + initialAppConfigDir, + commitAppConfigDir, updateDirectory, updateAppConfigDir, browseDirectory, diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index b1206a3bc..e538a610e 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -87,6 +87,7 @@ export function useSettings(): UseSettingsResult { resolvedDirs, isLoading: isDirectoryLoading, initialAppConfigDir, + commitAppConfigDir, updateDirectory, updateAppConfigDir, browseDirectory, @@ -373,6 +374,8 @@ export function useSettings(): UseSettingsResult { await saveMutation.mutateAsync(payload); await settingsApi.setAppConfigDirOverride(sanitizedAppDir ?? null); + // 基准值换成刚存的:设置页不卸载,下次比较和「需要重启」都只跟真正没保存的改动走 + commitAppConfigDir(sanitizedAppDir); // 只在开机自启状态真正改变时调用系统 API if ( @@ -496,6 +499,7 @@ export function useSettings(): UseSettingsResult { }, [ appConfigDir, + commitAppConfigDir, data, initialAppConfigDir, queryClient, diff --git a/tests/hooks/useDirectorySettings.test.tsx b/tests/hooks/useDirectorySettings.test.tsx index 454dd8f26..7fc8aabc3 100644 --- a/tests/hooks/useDirectorySettings.test.tsx +++ b/tests/hooks/useDirectorySettings.test.tsx @@ -170,6 +170,27 @@ describe("useDirectorySettings", () => { }); }); + it("moves the saved baseline when a new app config dir is committed", async () => { + getAppConfigDirOverrideMock.mockResolvedValue("/override/app"); + const { result } = renderHook(() => + useDirectorySettings({ + settings: createSettings(), + onUpdateSettings: vi.fn(), + }), + ); + await waitFor(() => + expect(result.current.initialAppConfigDir).toBe("/override/app"), + ); + + act(() => result.current.commitAppConfigDir("/saved/app")); + expect(result.current.initialAppConfigDir).toBe("/saved/app"); + + // 重置回的也是新基准 + act(() => result.current.updateAppConfigDir("/typed/app")); + act(() => result.current.resetAllDirectories()); + expect(result.current.appConfigDir).toBe("/saved/app"); + }); + it("updates app config directory via browseAppConfigDir", async () => { selectConfigDirectoryMock.mockResolvedValue(" /new/app "); diff --git a/tests/hooks/useSettings.test.tsx b/tests/hooks/useSettings.test.tsx index 3146d53f4..ecd324428 100644 --- a/tests/hooks/useSettings.test.tsx +++ b/tests/hooks/useSettings.test.tsx @@ -123,6 +123,7 @@ const createDirectorySettingsMock = ( }, isLoading: false, initialAppConfigDir: undefined, + commitAppConfigDir: vi.fn(), updateDirectory: vi.fn(), updateAppConfigDir: vi.fn(), browseDirectory: vi.fn(), @@ -297,6 +298,10 @@ describe("useSettings hook", () => { }); expect(saveResult).toEqual({ requiresRestart: true }); + // 存完把基准值换成刚存的,保存按钮和下一次「需要重启」只跟新改动走 + expect(directorySettingsMock.commitAppConfigDir).toHaveBeenCalledWith( + "/override/app", + ); expect(mutateAsyncMock).toHaveBeenCalledTimes(1); const payload = mutateAsyncMock.mock.calls[0][0] as Settings; expect(payload.claudeConfigDir).toBe("/custom/claude");