From dbf87ca7608e441a6a8bfd035a1f56fa80c171b6 Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Mon, 2 Feb 2026 02:55:55 -0600 Subject: [PATCH] feat(batchRunner): add unsaved changes confirmation on close When closing the Auto Run Configuration modal, show a confirmation dialog if there are unsaved changes to: - Document list (documents added or removed) - Loop settings (enabled/disabled, max loops) - Agent prompt (edited from initial value) If no changes were made, the modal closes immediately without confirmation. This prevents accidental loss of configuration when users press Escape, click Cancel, or click the X button. --- src/renderer/components/BatchRunnerModal.tsx | 55 ++++++++++++++++++-- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/src/renderer/components/BatchRunnerModal.tsx b/src/renderer/components/BatchRunnerModal.tsx index 68466461..84b7c772 100644 --- a/src/renderer/components/BatchRunnerModal.tsx +++ b/src/renderer/components/BatchRunnerModal.tsx @@ -117,6 +117,9 @@ export function BatchRunnerModal(props: BatchRunnerModalProps) { return []; }); + // Track initial document state for dirty checking + const initialDocumentsRef = useRef([currentDocument].filter(Boolean)); + // Task counts per document (keyed by filename) const [taskCounts, setTaskCounts] = useState>({}); const [loadingTaskCounts, setLoadingTaskCounts] = useState(true); @@ -125,6 +128,10 @@ export function BatchRunnerModal(props: BatchRunnerModalProps) { const [loopEnabled, setLoopEnabled] = useState(false); const [maxLoops, setMaxLoops] = useState(null); // null = infinite + // Track initial loop settings for dirty checking + const initialLoopEnabledRef = useRef(false); + const initialMaxLoopsRef = useRef(null); + // Prompt state const [prompt, setPrompt] = useState(initialPrompt || DEFAULT_BATCH_PROMPT); const [variablesExpanded, setVariablesExpanded] = useState(false); @@ -132,6 +139,43 @@ export function BatchRunnerModal(props: BatchRunnerModalProps) { const [promptComposerOpen, setPromptComposerOpen] = useState(false); const textareaRef = useRef(null); + // Track initial prompt for dirty checking + const initialPromptRef = useRef(initialPrompt || DEFAULT_BATCH_PROMPT); + + // Compute if there are unsaved configuration changes + // This checks if documents, loop settings, or prompt have changed from initial values + const hasUnsavedConfigChanges = useCallback(() => { + // Check if documents have changed (compare filenames) + const currentDocFilenames = documents.map((d) => d.filename).sort(); + const initialDocFilenames = [...initialDocumentsRef.current].sort(); + const documentsChanged = + currentDocFilenames.length !== initialDocFilenames.length || + currentDocFilenames.some((f, i) => f !== initialDocFilenames[i]); + + // Check if loop settings have changed + const loopChanged = + loopEnabled !== initialLoopEnabledRef.current || maxLoops !== initialMaxLoopsRef.current; + + // Check if prompt has changed + const promptChanged = prompt !== initialPromptRef.current; + + return documentsChanged || loopChanged || promptChanged; + }, [documents, loopEnabled, maxLoops, prompt]); + + // Handler for closing with unsaved changes check + const handleCloseWithConfirmation = useCallback(() => { + if (hasUnsavedConfigChanges()) { + showConfirmation( + 'You have unsaved changes to your Auto Run configuration. Close without saving?', + () => { + onClose(); + } + ); + } else { + onClose(); + } + }, [hasUnsavedConfigChanges, showConfirmation, onClose]); + // Playbook management callback to apply loaded playbook configuration const handleApplyPlaybook = useCallback( (data: { @@ -238,7 +282,7 @@ export function BatchRunnerModal(props: BatchRunnerModalProps) { } else if (showSavePlaybookModal) { setShowSavePlaybookModal(false); } else { - onClose(); + handleCloseWithConfirmation(); } }, }); @@ -255,6 +299,7 @@ export function BatchRunnerModal(props: BatchRunnerModalProps) { showSavePlaybookModal, showDeleteConfirmModal, handleCancelDeletePlaybook, + handleCloseWithConfirmation, ]); // Update handler when dependencies change @@ -266,12 +311,12 @@ export function BatchRunnerModal(props: BatchRunnerModalProps) { } else if (showSavePlaybookModal) { setShowSavePlaybookModal(false); } else { - onClose(); + handleCloseWithConfirmation(); } }); } }, [ - onClose, + handleCloseWithConfirmation, updateLayerHandler, showSavePlaybookModal, showDeleteConfirmModal, @@ -365,7 +410,7 @@ export function BatchRunnerModal(props: BatchRunnerModalProps) { {totalTaskCount === 1 ? 'task' : 'tasks'} - @@ -710,7 +755,7 @@ export function BatchRunnerModal(props: BatchRunnerModalProps) { {/* Right side: Buttons */}