diff --git a/apps/ui/src/components/dialogs/board-background-modal.tsx b/apps/ui/src/components/dialogs/board-background-modal.tsx index e381c366..208d2059 100644 --- a/apps/ui/src/components/dialogs/board-background-modal.tsx +++ b/apps/ui/src/components/dialogs/board-background-modal.tsx @@ -45,6 +45,8 @@ export function BoardBackgroundModal({ open, onOpenChange }: BoardBackgroundModa setCardBorderOpacity, setHideScrollbar, clearBoardBackground, + persistSettings, + getCurrentSettings, } = useBoardBackgroundSettings(); const [isDragOver, setIsDragOver] = useState(false); const [isProcessing, setIsProcessing] = useState(false); @@ -55,12 +57,31 @@ export function BoardBackgroundModal({ open, onOpenChange }: BoardBackgroundModa const backgroundSettings = (currentProject && boardBackgroundByProject[currentProject.path]) || defaultBackgroundSettings; - const cardOpacity = backgroundSettings.cardOpacity; - const columnOpacity = backgroundSettings.columnOpacity; + // Local state for sliders during dragging (avoids store updates during drag) + const [localCardOpacity, setLocalCardOpacity] = useState(backgroundSettings.cardOpacity); + const [localColumnOpacity, setLocalColumnOpacity] = useState(backgroundSettings.columnOpacity); + const [localCardBorderOpacity, setLocalCardBorderOpacity] = useState( + backgroundSettings.cardBorderOpacity + ); + const [isDragging, setIsDragging] = useState(false); + + // Sync local state with store when not dragging (e.g., on modal open or external changes) + useEffect(() => { + if (!isDragging) { + setLocalCardOpacity(backgroundSettings.cardOpacity); + setLocalColumnOpacity(backgroundSettings.columnOpacity); + setLocalCardBorderOpacity(backgroundSettings.cardBorderOpacity); + } + }, [ + isDragging, + backgroundSettings.cardOpacity, + backgroundSettings.columnOpacity, + backgroundSettings.cardBorderOpacity, + ]); + const columnBorderEnabled = backgroundSettings.columnBorderEnabled; const cardGlassmorphism = backgroundSettings.cardGlassmorphism; const cardBorderEnabled = backgroundSettings.cardBorderEnabled; - const cardBorderOpacity = backgroundSettings.cardBorderOpacity; const hideScrollbar = backgroundSettings.hideScrollbar; const imageVersion = backgroundSettings.imageVersion; @@ -198,21 +219,40 @@ export function BoardBackgroundModal({ open, onOpenChange }: BoardBackgroundModa } }, [currentProject, clearBoardBackground]); - // Live update opacity when sliders change (with persistence) - const handleCardOpacityChange = useCallback( - async (value: number[]) => { + // Live update local state during drag (modal-only, no store update) + const handleCardOpacityChange = useCallback((value: number[]) => { + setIsDragging(true); + setLocalCardOpacity(value[0]); + }, []); + + // Update store and persist when slider is released + const handleCardOpacityCommit = useCallback( + (value: number[]) => { if (!currentProject) return; - await setCardOpacity(currentProject.path, value[0]); + setIsDragging(false); + setCardOpacity(currentProject.path, value[0]); + const current = getCurrentSettings(currentProject.path); + persistSettings(currentProject.path, { ...current, cardOpacity: value[0] }); }, - [currentProject, setCardOpacity] + [currentProject, setCardOpacity, getCurrentSettings, persistSettings] ); - const handleColumnOpacityChange = useCallback( - async (value: number[]) => { + // Live update local state during drag (modal-only, no store update) + const handleColumnOpacityChange = useCallback((value: number[]) => { + setIsDragging(true); + setLocalColumnOpacity(value[0]); + }, []); + + // Update store and persist when slider is released + const handleColumnOpacityCommit = useCallback( + (value: number[]) => { if (!currentProject) return; - await setColumnOpacity(currentProject.path, value[0]); + setIsDragging(false); + setColumnOpacity(currentProject.path, value[0]); + const current = getCurrentSettings(currentProject.path); + persistSettings(currentProject.path, { ...current, columnOpacity: value[0] }); }, - [currentProject, setColumnOpacity] + [currentProject, setColumnOpacity, getCurrentSettings, persistSettings] ); const handleColumnBorderToggle = useCallback( @@ -239,12 +279,22 @@ export function BoardBackgroundModal({ open, onOpenChange }: BoardBackgroundModa [currentProject, setCardBorderEnabled] ); - const handleCardBorderOpacityChange = useCallback( - async (value: number[]) => { + // Live update local state during drag (modal-only, no store update) + const handleCardBorderOpacityChange = useCallback((value: number[]) => { + setIsDragging(true); + setLocalCardBorderOpacity(value[0]); + }, []); + + // Update store and persist when slider is released + const handleCardBorderOpacityCommit = useCallback( + (value: number[]) => { if (!currentProject) return; - await setCardBorderOpacity(currentProject.path, value[0]); + setIsDragging(false); + setCardBorderOpacity(currentProject.path, value[0]); + const current = getCurrentSettings(currentProject.path); + persistSettings(currentProject.path, { ...current, cardBorderOpacity: value[0] }); }, - [currentProject, setCardBorderOpacity] + [currentProject, setCardBorderOpacity, getCurrentSettings, persistSettings] ); const handleHideScrollbarToggle = useCallback( @@ -378,11 +428,12 @@ export function BoardBackgroundModal({ open, onOpenChange }: BoardBackgroundModa
- {cardOpacity}% + {localCardOpacity}%
- {columnOpacity}% + {localColumnOpacity}%
- {cardBorderOpacity}% + {localCardBorderOpacity}%
) => { const api = getElectronAPI(); + if (!api.settings) { + throw new Error('Settings API not available'); + } // Use updateGlobal for partial updates const result = await api.settings.updateGlobal(settings); if (!result.success) { @@ -66,33 +69,43 @@ export function useUpdateGlobalSettings(options: UpdateGlobalSettingsOptions = { * @param projectPath - Optional path to the project (can also pass via mutation variables) * @returns Mutation for updating project settings */ +interface ProjectSettingsWithPath { + projectPath: string; + settings: Record; +} + export function useUpdateProjectSettings(projectPath?: string) { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ( - variables: - | Record - | { projectPath: string; settings: Record } - ) => { + mutationFn: async (variables: Record | ProjectSettingsWithPath) => { // Support both call patterns: // 1. useUpdateProjectSettings(projectPath) then mutate(settings) // 2. useUpdateProjectSettings() then mutate({ projectPath, settings }) let path: string; let settings: Record; - if ('projectPath' in variables && 'settings' in variables) { + if ( + typeof variables === 'object' && + 'projectPath' in variables && + 'settings' in variables && + typeof variables.projectPath === 'string' && + typeof variables.settings === 'object' + ) { path = variables.projectPath; - settings = variables.settings; + settings = variables.settings as Record; } else if (projectPath) { path = projectPath; - settings = variables; + settings = variables as Record; } else { throw new Error('Project path is required'); } const api = getElectronAPI(); - const result = await api.settings.setProject(path, settings); + if (!api.settings) { + throw new Error('Settings API not available'); + } + const result = await api.settings.updateProject(path, settings); if (!result.success) { throw new Error(result.error || 'Failed to update project settings'); } @@ -122,9 +135,12 @@ export function useSaveCredentials() { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async (credentials: Record) => { + mutationFn: async (credentials: { anthropic?: string; google?: string; openai?: string }) => { const api = getElectronAPI(); - const result = await api.settings.setCredentials(credentials); + if (!api.settings) { + throw new Error('Settings API not available'); + } + const result = await api.settings.updateCredentials({ apiKeys: credentials }); if (!result.success) { throw new Error(result.error || 'Failed to save credentials'); } diff --git a/apps/ui/src/hooks/use-board-background-settings.ts b/apps/ui/src/hooks/use-board-background-settings.ts index 33618941..cde4f4b5 100644 --- a/apps/ui/src/hooks/use-board-background-settings.ts +++ b/apps/ui/src/hooks/use-board-background-settings.ts @@ -5,6 +5,10 @@ import { useUpdateProjectSettings } from '@/hooks/mutations'; /** * Hook for managing board background settings with automatic persistence to server. * Uses React Query mutation for server persistence with automatic error handling. + * + * For sliders, the modal uses local state during dragging and calls: + * - setCardOpacity/setColumnOpacity/setCardBorderOpacity to update store on commit + * - persistSettings directly to save to server on commit */ export function useBoardBackgroundSettings() { const store = useAppStore(); @@ -65,22 +69,20 @@ export function useBoardBackgroundSettings() { [store, persistSettings, getCurrentSettings] ); + // Update store (called on slider commit to update the board view) const setCardOpacity = useCallback( - async (projectPath: string, opacity: number) => { - const current = getCurrentSettings(projectPath); + (projectPath: string, opacity: number) => { store.setCardOpacity(projectPath, opacity); - await persistSettings(projectPath, { ...current, cardOpacity: opacity }); }, - [store, persistSettings, getCurrentSettings] + [store] ); + // Update store (called on slider commit to update the board view) const setColumnOpacity = useCallback( - async (projectPath: string, opacity: number) => { - const current = getCurrentSettings(projectPath); + (projectPath: string, opacity: number) => { store.setColumnOpacity(projectPath, opacity); - await persistSettings(projectPath, { ...current, columnOpacity: opacity }); }, - [store, persistSettings, getCurrentSettings] + [store] ); const setColumnBorderEnabled = useCallback( @@ -119,16 +121,12 @@ export function useBoardBackgroundSettings() { [store, persistSettings, getCurrentSettings] ); + // Update store (called on slider commit to update the board view) const setCardBorderOpacity = useCallback( - async (projectPath: string, opacity: number) => { - const current = getCurrentSettings(projectPath); + (projectPath: string, opacity: number) => { store.setCardBorderOpacity(projectPath, opacity); - await persistSettings(projectPath, { - ...current, - cardBorderOpacity: opacity, - }); }, - [store, persistSettings, getCurrentSettings] + [store] ); const setHideScrollbar = useCallback( @@ -170,5 +168,6 @@ export function useBoardBackgroundSettings() { setHideScrollbar, clearBoardBackground, getCurrentSettings, + persistSettings, }; }