From af3ec11d9c24c05775e6021e5575e68bf10b3a12 Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Tue, 3 Mar 2026 15:42:59 -0800 Subject: [PATCH 1/2] feat: add "Run All Phases" toggle to wizard and resize modal - Resize wizard modal from max-w-5xl (1024px) to 1200px wide / 85vh tall, matching Director's Notes dimensions - Add runAllDocuments toggle to PhaseReviewScreen (Step 5), visible when there are 2+ generated documents - When enabled, all documents with tasks are included in the batch config instead of only the first - Extract reusable ToggleSwitch component to ui/ and refactor SettingCheckbox to use it - Add test for run-all-documents batch behavior Closes #490 --- .../renderer/hooks/useWizardHandlers.test.ts | 70 +++++++++++++++++++ src/renderer/components/SettingCheckbox.tsx | 20 +----- .../components/Wizard/MaestroWizard.tsx | 2 +- .../components/Wizard/WizardContext.tsx | 28 ++++++++ .../Wizard/screens/PhaseReviewScreen.tsx | 43 +++++++++++- src/renderer/components/ui/ToggleSwitch.tsx | 52 ++++++++++++++ src/renderer/components/ui/index.ts | 3 + .../hooks/wizard/useWizardHandlers.ts | 24 +++---- 8 files changed, 209 insertions(+), 33 deletions(-) create mode 100644 src/renderer/components/ui/ToggleSwitch.tsx diff --git a/src/__tests__/renderer/hooks/useWizardHandlers.test.ts b/src/__tests__/renderer/hooks/useWizardHandlers.test.ts index 2c2d8efad..4493ec1e3 100644 --- a/src/__tests__/renderer/hooks/useWizardHandlers.test.ts +++ b/src/__tests__/renderer/hooks/useWizardHandlers.test.ts @@ -1888,6 +1888,76 @@ describe('useWizardHandlers', () => { ); }); + it('auto-starts batch run with all documents when runAllDocuments is true', async () => { + useSessionStore.setState({ sessions: [], activeSessionId: null }); + + const deps = createMockDeps({ + wizardContext: { + state: { + currentStep: 'review' as any, + isOpen: true, + selectedAgent: 'claude-code', + availableAgents: [], + agentName: 'Test', + directoryPath: '/projects/test', + isGitRepo: false, + detectedAgentPath: null, + directoryError: null, + hasExistingAutoRunDocs: false, + existingDocsCount: 0, + existingDocsChoice: null, + conversationHistory: [], + confidenceLevel: 90, + isReadyToProceed: true, + isConversationLoading: false, + conversationError: null, + generatedDocuments: [ + { filename: 'phase-1.md', content: '# Phase 1', taskCount: 3 }, + { filename: 'phase-2.md', content: '# Phase 2', taskCount: 5 }, + { filename: 'phase-3.md', content: '# Phase 3', taskCount: 2 }, + ], + currentDocumentIndex: 0, + isGeneratingDocuments: false, + generationError: null, + editedPhase1Content: null, + runAllDocuments: true, + wantsTour: false, + isComplete: false, + createdSessionId: null, + } as any, + completeWizard: vi.fn(), + clearResumeState: vi.fn(), + }, + }); + + const { result } = renderHook(() => useWizardHandlers(deps)); + + await act(async () => { + await result.current.handleWizardLaunchSession(false); + }); + + // Wait for the setTimeout batch run + await act(async () => { + await new Promise((r) => setTimeout(r, 600)); + }); + + expect(deps.startBatchRun).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + documents: expect.arrayContaining([ + expect.objectContaining({ filename: 'phase-1' }), + expect.objectContaining({ filename: 'phase-2' }), + expect.objectContaining({ filename: 'phase-3' }), + ]), + }), + expect.stringContaining('Auto Run Docs') + ); + + // Should have exactly 3 documents in the batch + const batchConfig = deps.startBatchRun.mock.calls[0][1]; + expect(batchConfig.documents).toHaveLength(3); + }); + it('starts tour when wantsTour is true', async () => { useSessionStore.setState({ sessions: [], activeSessionId: null }); diff --git a/src/renderer/components/SettingCheckbox.tsx b/src/renderer/components/SettingCheckbox.tsx index 1fec960e6..5a5c432aa 100644 --- a/src/renderer/components/SettingCheckbox.tsx +++ b/src/renderer/components/SettingCheckbox.tsx @@ -1,6 +1,7 @@ import React from 'react'; import type { Theme } from '../types'; import type { LucideIcon } from 'lucide-react'; +import { ToggleSwitch } from './ui/ToggleSwitch'; export interface SettingCheckboxProps { /** The icon to display next to the section label */ @@ -62,24 +63,7 @@ export function SettingCheckbox({ )} - + ); diff --git a/src/renderer/components/Wizard/MaestroWizard.tsx b/src/renderer/components/Wizard/MaestroWizard.tsx index 155d4e54d..0a7718295 100644 --- a/src/renderer/components/Wizard/MaestroWizard.tsx +++ b/src/renderer/components/Wizard/MaestroWizard.tsx @@ -456,7 +456,7 @@ export function MaestroWizard({
}; @@ -375,6 +383,9 @@ function wizardReducer(state: WizardState, action: WizardAction): WizardState { case 'SET_EDITED_PHASE1_CONTENT': return { ...state, editedPhase1Content: action.content }; + case 'SET_RUN_ALL_DOCUMENTS': + return { ...state, runAllDocuments: action.runAll }; + case 'SET_WANTS_TOUR': return { ...state, wantsTour: action.wantsTour }; @@ -408,6 +419,7 @@ export interface SerializableWizardState { isReadyToProceed: boolean; generatedDocuments: GeneratedDocument[]; editedPhase1Content: string | null; + runAllDocuments: boolean; wantsTour: boolean; /** Per-session SSH remote configuration (for remote execution) */ sessionSshRemoteConfig?: { @@ -504,6 +516,10 @@ export interface WizardContextAPI { /** Get current Phase 1 content (edited or original) */ getPhase1Content: () => string; + // Launch Options + /** Set whether to run all documents or just the first */ + setRunAllDocuments: (runAll: boolean) => void; + // Tour /** Set whether user wants the tour */ setWantsTour: (wantsTour: boolean) => void; @@ -735,6 +751,11 @@ export function WizardProvider({ children }: WizardProviderProps) { return phase1Doc?.content || ''; }, [state.editedPhase1Content, state.generatedDocuments]); + // Launch Options + const setRunAllDocuments = useCallback((runAll: boolean) => { + dispatch({ type: 'SET_RUN_ALL_DOCUMENTS', runAll }); + }, []); + // Tour const setWantsTour = useCallback((wantsTour: boolean) => { dispatch({ type: 'SET_WANTS_TOUR', wantsTour }); @@ -760,6 +781,7 @@ export function WizardProvider({ children }: WizardProviderProps) { isReadyToProceed: state.isReadyToProceed, generatedDocuments: state.generatedDocuments, editedPhase1Content: state.editedPhase1Content, + runAllDocuments: state.runAllDocuments, wantsTour: state.wantsTour, sessionSshRemoteConfig: state.sessionSshRemoteConfig, }; @@ -774,6 +796,7 @@ export function WizardProvider({ children }: WizardProviderProps) { state.isReadyToProceed, state.generatedDocuments, state.editedPhase1Content, + state.runAllDocuments, state.wantsTour, state.sessionSshRemoteConfig, ]); @@ -842,6 +865,7 @@ export function WizardProvider({ children }: WizardProviderProps) { isReadyToProceed: currentState.isReadyToProceed, generatedDocuments: currentState.generatedDocuments, editedPhase1Content: currentState.editedPhase1Content, + runAllDocuments: currentState.runAllDocuments, wantsTour: currentState.wantsTour, }; window.maestro.settings.set('wizardResumeState', serializableState); @@ -898,6 +922,9 @@ export function WizardProvider({ children }: WizardProviderProps) { setEditedPhase1Content, getPhase1Content, + // Launch Options + setRunAllDocuments, + // Tour setWantsTour, @@ -947,6 +974,7 @@ export function WizardProvider({ children }: WizardProviderProps) { setGenerationError, setEditedPhase1Content, getPhase1Content, + setRunAllDocuments, setWantsTour, completeWizard, saveStateForResume, diff --git a/src/renderer/components/Wizard/screens/PhaseReviewScreen.tsx b/src/renderer/components/Wizard/screens/PhaseReviewScreen.tsx index 77a2797d9..00303e919 100644 --- a/src/renderer/components/Wizard/screens/PhaseReviewScreen.tsx +++ b/src/renderer/components/Wizard/screens/PhaseReviewScreen.tsx @@ -21,6 +21,7 @@ import { useWizard } from '../WizardContext'; import { AUTO_RUN_FOLDER_NAME } from '../services/phaseGenerator'; import { ScreenReaderAnnouncement } from '../ScreenReaderAnnouncement'; import { DocumentEditor } from '../shared/DocumentEditor'; +import { ToggleSwitch } from '../../ui/ToggleSwitch'; import { formatShortcutKeys } from '../../../utils/shortcutFormatter'; // Auto-save debounce delay in milliseconds @@ -67,8 +68,14 @@ function DocumentReview({ ) => void; wizardStartTime?: number; }): JSX.Element { - const { state, setEditedPhase1Content, getPhase1Content, setWantsTour, setCurrentDocumentIndex } = - useWizard(); + const { + state, + setEditedPhase1Content, + getPhase1Content, + setWantsTour, + setCurrentDocumentIndex, + setRunAllDocuments, + } = useWizard(); const { generatedDocuments, directoryPath, currentDocumentIndex } = state; const currentDoc = generatedDocuments[currentDocumentIndex] || generatedDocuments[0]; @@ -479,6 +486,38 @@ function DocumentReview({ backgroundColor: theme.colors.bgSidebar, }} > + {/* Run All toggle - only shown when there are multiple documents */} + {generatedDocuments.length > 1 && ( +
setRunAllDocuments(!state.runAllDocuments)} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + setRunAllDocuments(!state.runAllDocuments); + } + }} + > + + + {state.runAllDocuments ? 'Auto Run All Phases' : 'Auto Run First Phase Only For Now'} + +
+ )} +
{/* Primary button - Ready to Go */} + ); +} diff --git a/src/renderer/components/ui/index.ts b/src/renderer/components/ui/index.ts index b67d723ff..2d9f51141 100644 --- a/src/renderer/components/ui/index.ts +++ b/src/renderer/components/ui/index.ts @@ -13,3 +13,6 @@ export type { FormInputProps } from './FormInput'; export { EmojiPickerField } from './EmojiPickerField'; export type { EmojiPickerFieldProps } from './EmojiPickerField'; + +export { ToggleSwitch } from './ToggleSwitch'; +export type { ToggleSwitchProps } from './ToggleSwitch'; diff --git a/src/renderer/hooks/wizard/useWizardHandlers.ts b/src/renderer/hooks/wizard/useWizardHandlers.ts index abb26cc1d..c8edb0b74 100644 --- a/src/renderer/hooks/wizard/useWizardHandlers.ts +++ b/src/renderer/hooks/wizard/useWizardHandlers.ts @@ -1057,6 +1057,7 @@ export function useWizardHandlers(deps: UseWizardHandlersDeps): UseWizardHandler customArgs, customEnvVars, sessionSshRemoteConfig, + runAllDocuments, } = wizardState; if (!selectedAgent || !directoryPath) { @@ -1201,25 +1202,24 @@ export function useWizardHandlers(deps: UseWizardHandlersDeps): UseWizardHandler setActiveFocus('main'); setTimeout(() => inputRef.current?.focus(), 100); - const firstDocWithTasks = generatedDocuments.find((doc) => doc.taskCount > 0); - if (firstDocWithTasks && autoRunFolderPath) { + const docsWithTasks = generatedDocuments.filter((doc) => doc.taskCount > 0); + if (docsWithTasks.length > 0 && autoRunFolderPath) { + const docsToRun = runAllDocuments ? docsWithTasks : [docsWithTasks[0]]; const batchConfig: BatchRunConfig = { - documents: [ - { - id: generateId(), - filename: firstDocWithTasks.filename.replace(/\.md$/, ''), - resetOnCompletion: false, - isDuplicate: false, - }, - ], + documents: docsToRun.map((doc) => ({ + id: generateId(), + filename: doc.filename.replace(/\.md$/, ''), + resetOnCompletion: false, + isDuplicate: false, + })), prompt: DEFAULT_BATCH_PROMPT, loopEnabled: false, }; setTimeout(() => { console.log( - '[Wizard] Auto-starting batch run with first document:', - firstDocWithTasks.filename + `[Wizard] Auto-starting batch run with ${docsToRun.length} document(s):`, + docsToRun.map((d) => d.filename).join(', ') ); startBatchRun(newId, batchConfig, autoRunFolderPath); }, 500); From 4ebad259bdc4c1a0e2b72991f8446e96642ee5de Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Wed, 4 Mar 2026 09:36:48 -0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20address=20PR=20review=20=E2=80=94=20?= =?UTF-8?q?add=20type=3D"button"=20and=20ariaLabel=20to=20ToggleSwitch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add explicit type="button" to ToggleSwitch to prevent unintended form submissions - Pass ariaLabel={title} in SettingCheckbox for screen reader accessibility --- src/renderer/components/SettingCheckbox.tsx | 2 +- src/renderer/components/ui/ToggleSwitch.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/SettingCheckbox.tsx b/src/renderer/components/SettingCheckbox.tsx index 5a5c432aa..65edc0c97 100644 --- a/src/renderer/components/SettingCheckbox.tsx +++ b/src/renderer/components/SettingCheckbox.tsx @@ -63,7 +63,7 @@ export function SettingCheckbox({
)}
- + ); diff --git a/src/renderer/components/ui/ToggleSwitch.tsx b/src/renderer/components/ui/ToggleSwitch.tsx index 99f8c5a50..cf93555a5 100644 --- a/src/renderer/components/ui/ToggleSwitch.tsx +++ b/src/renderer/components/ui/ToggleSwitch.tsx @@ -27,6 +27,7 @@ export function ToggleSwitch({ }: ToggleSwitchProps): React.ReactElement { return (