Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions frontend/src/components/SessionView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -973,7 +973,7 @@ export const SessionView = memo(() => {
.filter(p => !defaultTerminalPanel || p.id !== defaultTerminalPanel.id)
.map(panel => {
const isActive = panel.id === currentActivePanel.id;
const shouldKeepAlive = ['terminal', 'browser', 'diff', 'explorer'].includes(panel.type);
const shouldKeepAlive = ['terminal', 'browser'].includes(panel.type);
if (!isActive && !shouldKeepAlive) return null;
return (
<div
Expand Down Expand Up @@ -1116,7 +1116,7 @@ export const SessionView = memo(() => {
.filter(p => !defaultTerminalPanel || p.id !== defaultTerminalPanel.id)
.map(panel => {
const isActive = panel.id === currentActivePanel.id;
const shouldKeepAlive = ['terminal', 'browser', 'diff', 'explorer'].includes(panel.type);
const shouldKeepAlive = ['terminal', 'browser'].includes(panel.type);
if (!isActive && !shouldKeepAlive) return null;
return (
<div
Expand Down
41 changes: 0 additions & 41 deletions frontend/src/components/panels/diff/CombinedDiffView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,6 @@ const CombinedDiffView = memo(forwardRef<CombinedDiffViewHandle, CombinedDiffVie
const [isResizing, setIsResizing] = useState(false);

const diffViewerRef = useRef<DiffViewerHandle>(null);
// Track diff-relevant git state to avoid spurious prefetches on no-op status events
const lastGitFingerprintRef = useRef<string>('');

// Expose refresh() to parent (DiffPanel) via ref
// Keeps current diff visible while new data loads (no flash),
Expand Down Expand Up @@ -186,7 +184,6 @@ const CombinedDiffView = memo(forwardRef<CombinedDiffViewHandle, CombinedDiffVie
setExecutions([]);
setHistorySource(isMainRepo ? 'remote' : 'branch');
diffCacheRef.current.clear();
lastGitFingerprintRef.current = '';
}
}, [sessionId, lastSessionId, isMainRepo]);

Expand Down Expand Up @@ -314,46 +311,9 @@ const CombinedDiffView = memo(forwardRef<CombinedDiffViewHandle, CombinedDiffVie
// eslint-disable-next-line react-hooks/exhaustive-deps -- selectedExecutions read inside closure to check if auto-select needed; must not re-trigger on selection change
}, [sessionId, isMainRepo, forceRefresh, isVisible, processExecutions]);

// Background prefetch: when git status changes, fetch executions + diff even when not visible
// This ensures the diff tab has data ready before the user opens it
useEffect(() => {
if (!isVisible) return;
let cancelled = false;

const handleGitStatusUpdated = (event: Event) => {
const { sessionId: eventSessionId, gitStatus } = (event as CustomEvent).detail || {};
if (eventSessionId !== sessionId) return;

// Fingerprint diff-relevant fields — ignore no-op status refreshes
const fingerprint = `${gitStatus?.state}-${gitStatus?.ahead}-${gitStatus?.behind}-${gitStatus?.uncommittedChanges}`;
if (fingerprint === lastGitFingerprintRef.current) return;
lastGitFingerprintRef.current = fingerprint;

// Clear stale cache and force diff re-fetch for existing selection
diffCacheRef.current.clear();
setForceRefresh(prev => prev + 1);

// Prefetch executions and let the diff loading effect chain handle the rest
// Only auto-select if user hasn't made a custom selection (preserves their commit range)
API.sessions.getExecutions(sessionId).then(response => {
if (cancelled || !response.success) return;
const data: ExecutionDiff[] = response.data || [];
processExecutions(data, selectedExecutionsRef.current.length === 0);
}).catch(() => { /* silent — prefetch is best-effort */ });
};

window.addEventListener('git-status-updated', handleGitStatusUpdated);
return () => {
cancelled = true;
window.removeEventListener('git-status-updated', handleGitStatusUpdated);
};
}, [sessionId, isVisible, processExecutions]);

// Keep refs to avoid stale closures in event handlers
const executionsLengthRef = useRef(executions.length);
executionsLengthRef.current = executions.length;
const selectedExecutionsRef = useRef(selectedExecutions);
selectedExecutionsRef.current = selectedExecutions;
const viewingCommitHashRef = useRef(viewingCommitHash);
viewingCommitHashRef.current = viewingCommitHash;

Expand Down Expand Up @@ -428,7 +388,6 @@ const CombinedDiffView = memo(forwardRef<CombinedDiffViewHandle, CombinedDiffVie
cancelled = true;
clearTimeout(timeoutId);
};
// eslint-disable-next-line react-hooks/exhaustive-deps -- executions.length read via ref to avoid re-triggering when executions reload
}, [selectedExecutions, sessionId, forceRefresh]);

const handleSelectionChange = (newSelection: number[]) => {
Expand Down
25 changes: 1 addition & 24 deletions frontend/src/components/panels/diff/DiffPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ import CombinedDiffView from './CombinedDiffView';
import type { CombinedDiffViewHandle } from './CombinedDiffView';
import type { ToolPanel, DiffPanelState } from '../../../../../shared/types/panels';
import { AlertCircle } from 'lucide-react';
import { useSession } from '../../../contexts/SessionContext';

const DIFF_PRELOAD_DELAY_MS = 10000;

interface DiffPanelProps {
panel: ToolPanel;
Expand All @@ -20,32 +17,12 @@ export const DiffPanel: React.FC<DiffPanelProps> = ({
sessionId,
isMainRepo = false
}) => {
const sessionContext = useSession();
const [isStale, setIsStale] = useState(false);
const [backgroundPreloadReady, setBackgroundPreloadReady] = useState(isActive);
const diffState = panel.state?.customState as DiffPanelState | undefined;
const lastRefreshRef = useRef<number>(Date.now());
const combinedDiffRef = useRef<CombinedDiffViewHandle>(null);
// Track diff-relevant git state to avoid spurious refreshes on no-op status events
const lastGitFingerprintRef = useRef<string>('');
const sessionStatus = sessionContext?.session.status;
const isSessionBusy = sessionStatus === 'running' || sessionStatus === 'initializing';
const shouldLoadDiff = isActive || (backgroundPreloadReady && !isSessionBusy);

useEffect(() => {
if (isActive) {
setBackgroundPreloadReady(true);
return;
}

if (backgroundPreloadReady || isSessionBusy) return;

const timer = window.setTimeout(() => {
setBackgroundPreloadReady(true);
}, DIFF_PRELOAD_DELAY_MS);

return () => window.clearTimeout(timer);
}, [isActive, backgroundPreloadReady, isSessionBusy]);

// Listen for file change events from other panels
useEffect(() => {
Expand Down Expand Up @@ -152,7 +129,7 @@ export const DiffPanel: React.FC<DiffPanelProps> = ({
selectedExecutions={[]}
isGitOperationRunning={false}
isMainRepo={isMainRepo}
isVisible={shouldLoadDiff}
isVisible={isActive}
/>
</div>
</div>
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/components/panels/editor/EditorPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,20 @@ export const ExplorerPanel: React.FC<ExplorerPanelProps> = ({
handleStateChange({ filePath, isDirty });
}
}, [panel.id, handleStateChange]);

// Only render when active. Explorer is cheap to initialize and should not
// keep Monaco, file tree loading, or git file-status checks alive in the
// background while the user is working in another panel.
if (!isActive) {
return (
<div className="flex-1 flex items-center justify-center text-text-secondary">
<div className="text-center">
<div className="text-sm">Explorer panel not active</div>
<div className="text-xs mt-1 text-text-tertiary">Click to activate</div>
</div>
</div>
);
}

return (
<div className="h-full w-full">
Expand Down
Loading