mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-16 21:53:07 +00:00
Changes from fix/manual-crash (#795)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useState, useCallback, useEffect, startTransition } from 'react';
|
||||
import { Plus, Bug, FolderOpen, BookOpen } from 'lucide-react';
|
||||
import { useNavigate, useLocation } from '@tanstack/react-router';
|
||||
import { cn, isMac } from '@/lib/utils';
|
||||
@@ -112,9 +112,17 @@ export function ProjectSwitcher() {
|
||||
// Continue with switch even if initialization fails -
|
||||
// the project may already be initialized
|
||||
}
|
||||
setCurrentProject(project);
|
||||
// Navigate to board view when switching projects
|
||||
navigate({ to: '/board' });
|
||||
// Wrap in startTransition to let React batch the project switch and
|
||||
// navigation into a single low-priority update. Without this, the two
|
||||
// synchronous calls fire separate renders where currentProject points
|
||||
// to the new project but per-project state (worktrees, features) is
|
||||
// still stale, causing a cascade of effects and store mutations that
|
||||
// can trigger React error #185 (maximum update depth exceeded).
|
||||
startTransition(() => {
|
||||
setCurrentProject(project);
|
||||
// Navigate to board view when switching projects
|
||||
navigate({ to: '/board' });
|
||||
});
|
||||
},
|
||||
[setCurrentProject, navigate]
|
||||
);
|
||||
|
||||
@@ -478,6 +478,13 @@ export function BoardView() {
|
||||
|
||||
// Get the branch for the currently selected worktree
|
||||
// Find the worktree that matches the current selection, or use main worktree
|
||||
//
|
||||
// IMPORTANT: Stabilize the returned object reference using a ref to prevent
|
||||
// cascading re-renders during project switches. The spread `{ ...found, ... }`
|
||||
// creates a new object every time, even when the underlying data is identical.
|
||||
// Without stabilization, the new reference propagates to useAutoMode and other
|
||||
// consumers, contributing to the re-render cascade that triggers React error #185.
|
||||
const prevSelectedWorktreeRef = useRef<WorktreeInfo | undefined>(undefined);
|
||||
const selectedWorktree = useMemo((): WorktreeInfo | undefined => {
|
||||
let found;
|
||||
let usedFallback = false;
|
||||
@@ -495,9 +502,12 @@ export function BoardView() {
|
||||
usedFallback = true;
|
||||
}
|
||||
}
|
||||
if (!found) return undefined;
|
||||
if (!found) {
|
||||
prevSelectedWorktreeRef.current = undefined;
|
||||
return undefined;
|
||||
}
|
||||
// Ensure all required WorktreeInfo fields are present
|
||||
return {
|
||||
const result: WorktreeInfo = {
|
||||
...found,
|
||||
isCurrent:
|
||||
found.isCurrent ??
|
||||
@@ -508,6 +518,21 @@ export function BoardView() {
|
||||
: found.isMain),
|
||||
hasWorktree: found.hasWorktree ?? true,
|
||||
};
|
||||
// Return the previous reference if the key fields haven't changed,
|
||||
// preventing downstream hooks from seeing a "new" worktree on every render.
|
||||
const prev = prevSelectedWorktreeRef.current;
|
||||
if (
|
||||
prev &&
|
||||
prev.path === result.path &&
|
||||
prev.branch === result.branch &&
|
||||
prev.isMain === result.isMain &&
|
||||
prev.isCurrent === result.isCurrent &&
|
||||
prev.hasWorktree === result.hasWorktree
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
prevSelectedWorktreeRef.current = result;
|
||||
return result;
|
||||
}, [worktrees, currentWorktreePath]);
|
||||
|
||||
// Auto mode hook - pass current worktree to get worktree-specific state
|
||||
|
||||
@@ -252,10 +252,19 @@ export function useAutoMode(worktree?: WorktreeInfo) {
|
||||
}
|
||||
}, [branchName, currentProject, setAutoModeRunning]);
|
||||
|
||||
// On mount, query backend for current auto loop status and sync UI state.
|
||||
// On mount (and when refreshStatus identity changes, e.g. project switch),
|
||||
// query backend for current auto loop status and sync UI state.
|
||||
// This handles cases where the backend is still running after a page refresh.
|
||||
//
|
||||
// IMPORTANT: Debounce with a short delay to prevent a synchronous cascade
|
||||
// during project switches. Without this, the sequence is:
|
||||
// refreshStatus() → setAutoModeRunning() → store update → re-render →
|
||||
// other effects fire → more store updates → React error #185.
|
||||
// The 150ms delay lets React settle the initial mount renders before we
|
||||
// trigger additional store mutations from the API response.
|
||||
useEffect(() => {
|
||||
void refreshStatus();
|
||||
const timer = setTimeout(() => void refreshStatus(), 150);
|
||||
return () => clearTimeout(timer);
|
||||
}, [refreshStatus]);
|
||||
|
||||
// Periodic polling fallback when WebSocket events are stale.
|
||||
|
||||
@@ -113,8 +113,25 @@ export function syncUICache(appState: {
|
||||
if ('collapsedNavSections' in appState) {
|
||||
update.cachedCollapsedNavSections = appState.collapsedNavSections;
|
||||
}
|
||||
if ('currentWorktreeByProject' in appState) {
|
||||
update.cachedCurrentWorktreeByProject = appState.currentWorktreeByProject;
|
||||
if ('currentWorktreeByProject' in appState && appState.currentWorktreeByProject) {
|
||||
// Sanitize on write: only persist entries where path is null (main branch).
|
||||
// Non-null paths point to worktree directories on disk that may be deleted
|
||||
// while the app is not running. Persisting stale paths can cause crash loops
|
||||
// on restore (the board renders with an invalid selection, the error boundary
|
||||
// reloads, which restores the same bad cache). This mirrors the sanitization
|
||||
// in restoreFromUICache() for defense-in-depth.
|
||||
const sanitized: Record<string, { path: string | null; branch: string }> = {};
|
||||
for (const [projectPath, worktree] of Object.entries(appState.currentWorktreeByProject)) {
|
||||
if (
|
||||
typeof worktree === 'object' &&
|
||||
worktree !== null &&
|
||||
'path' in worktree &&
|
||||
worktree.path === null
|
||||
) {
|
||||
sanitized[projectPath] = worktree;
|
||||
}
|
||||
}
|
||||
update.cachedCurrentWorktreeByProject = sanitized;
|
||||
}
|
||||
|
||||
if (Object.keys(update).length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user