mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-19 10:43:08 +00:00
feat(auto-mode): implement facade caching and enhance error handling
- Added caching for facades in AutoModeServiceCompat to persist auto loop state across API calls. - Improved error handling in BoardView for starting and stopping auto mode, with user-friendly toast notifications. - Updated WorktreePanel to manage auto mode state and concurrency limits more effectively. - Enhanced useAutoMode hook to prevent state overwrites during transitions and synchronize UI with backend status. This update optimizes performance and user experience in the auto mode feature.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useCallback, useMemo } from 'react';
|
||||
import { useEffect, useCallback, useMemo, useRef } from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { createLogger } from '@automaker/utils/logger';
|
||||
import { DEFAULT_MAX_CONCURRENCY } from '@automaker/types';
|
||||
@@ -11,6 +11,12 @@ import { getGlobalEventsRecent } from '@/hooks/use-event-recency';
|
||||
const logger = createLogger('AutoMode');
|
||||
|
||||
const AUTO_MODE_SESSION_KEY = 'automaker:autoModeRunningByWorktreeKey';
|
||||
|
||||
function arraysEqual(a: string[], b: string[]): boolean {
|
||||
if (a.length !== b.length) return false;
|
||||
const set = new Set(b);
|
||||
return a.every((id) => set.has(id));
|
||||
}
|
||||
const AUTO_MODE_POLLING_INTERVAL = 30000;
|
||||
|
||||
/**
|
||||
@@ -142,9 +148,16 @@ export function useAutoMode(worktree?: WorktreeInfo) {
|
||||
// Check if we can start a new task based on concurrency limit
|
||||
const canStartNewTask = runningAutoTasks.length < maxConcurrency;
|
||||
|
||||
// Ref to prevent refreshStatus from overwriting optimistic state during start/stop
|
||||
const isTransitioningRef = useRef(false);
|
||||
|
||||
const refreshStatus = useCallback(async () => {
|
||||
if (!currentProject) return;
|
||||
|
||||
// Skip sync when user is in the middle of start/stop - avoids race where
|
||||
// refreshStatus runs before the API call completes and overwrites optimistic state
|
||||
if (isTransitioningRef.current) return;
|
||||
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
if (!api?.autoMode?.status) return;
|
||||
@@ -152,18 +165,28 @@ export function useAutoMode(worktree?: WorktreeInfo) {
|
||||
const result = await api.autoMode.status(currentProject.path, branchName);
|
||||
if (result.success && result.isAutoLoopRunning !== undefined) {
|
||||
const backendIsRunning = result.isAutoLoopRunning;
|
||||
const backendRunningFeatures = result.runningFeatures ?? [];
|
||||
const needsSync =
|
||||
backendIsRunning !== isAutoModeRunning ||
|
||||
// Also sync when backend has runningFeatures we're missing (handles missed WebSocket events)
|
||||
(backendIsRunning &&
|
||||
Array.isArray(backendRunningFeatures) &&
|
||||
backendRunningFeatures.length > 0 &&
|
||||
!arraysEqual(backendRunningFeatures, runningAutoTasks));
|
||||
|
||||
if (backendIsRunning !== isAutoModeRunning) {
|
||||
if (needsSync) {
|
||||
const worktreeDesc = branchName ? `worktree ${branchName}` : 'main worktree';
|
||||
logger.info(
|
||||
`[AutoMode] Syncing UI state with backend for ${worktreeDesc} in ${currentProject.path}: ${backendIsRunning ? 'ON' : 'OFF'}`
|
||||
);
|
||||
if (backendIsRunning !== isAutoModeRunning) {
|
||||
logger.info(
|
||||
`[AutoMode] Syncing UI state with backend for ${worktreeDesc} in ${currentProject.path}: ${backendIsRunning ? 'ON' : 'OFF'}`
|
||||
);
|
||||
}
|
||||
setAutoModeRunning(
|
||||
currentProject.id,
|
||||
branchName,
|
||||
backendIsRunning,
|
||||
result.maxConcurrency,
|
||||
result.runningFeatures
|
||||
backendRunningFeatures
|
||||
);
|
||||
setAutoModeSessionForWorktree(currentProject.path, branchName, backendIsRunning);
|
||||
}
|
||||
@@ -171,7 +194,7 @@ export function useAutoMode(worktree?: WorktreeInfo) {
|
||||
} catch (error) {
|
||||
logger.error('Error syncing auto mode state with backend:', error);
|
||||
}
|
||||
}, [branchName, currentProject, isAutoModeRunning, setAutoModeRunning]);
|
||||
}, [branchName, currentProject, isAutoModeRunning, runningAutoTasks, setAutoModeRunning]);
|
||||
|
||||
// On mount, query backend for current auto loop status and sync UI state.
|
||||
// This handles cases where the backend is still running after a page refresh.
|
||||
@@ -558,6 +581,7 @@ export function useAutoMode(worktree?: WorktreeInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
isTransitioningRef.current = true;
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
if (!api?.autoMode?.start) {
|
||||
@@ -588,14 +612,18 @@ export function useAutoMode(worktree?: WorktreeInfo) {
|
||||
}
|
||||
|
||||
logger.debug(`[AutoMode] Started successfully for ${worktreeDesc}`);
|
||||
// Sync with backend after success (gets runningFeatures if events were delayed)
|
||||
queueMicrotask(() => void refreshStatus());
|
||||
} catch (error) {
|
||||
// Revert UI state on error
|
||||
setAutoModeSessionForWorktree(currentProject.path, branchName, false);
|
||||
setAutoModeRunning(currentProject.id, branchName, false);
|
||||
logger.error('Error starting auto mode:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
isTransitioningRef.current = false;
|
||||
}
|
||||
}, [currentProject, branchName, setAutoModeRunning]);
|
||||
}, [currentProject, branchName, setAutoModeRunning, getMaxConcurrencyForWorktree]);
|
||||
|
||||
// Stop auto mode - calls backend to stop the auto loop for this worktree
|
||||
const stop = useCallback(async () => {
|
||||
@@ -604,6 +632,7 @@ export function useAutoMode(worktree?: WorktreeInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
isTransitioningRef.current = true;
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
if (!api?.autoMode?.stop) {
|
||||
@@ -631,12 +660,16 @@ export function useAutoMode(worktree?: WorktreeInfo) {
|
||||
// NOTE: Running tasks will continue until natural completion.
|
||||
// The backend stops picking up new features but doesn't abort running ones.
|
||||
logger.info(`Stopped ${worktreeDesc} - running tasks will continue`);
|
||||
// Sync with backend after success
|
||||
queueMicrotask(() => void refreshStatus());
|
||||
} catch (error) {
|
||||
// Revert UI state on error
|
||||
setAutoModeSessionForWorktree(currentProject.path, branchName, true);
|
||||
setAutoModeRunning(currentProject.id, branchName, true);
|
||||
logger.error('Error stopping auto mode:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
isTransitioningRef.current = false;
|
||||
}
|
||||
}, [currentProject, branchName, setAutoModeRunning]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user