import { useState, useRef, useEffect, useCallback } from 'react'; import { Terminal, Check, X, Loader2, ChevronDown, ChevronUp } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useAppStore, type InitScriptState } from '@/store/app-store'; import { AnsiOutput } from '@/components/ui/ansi-output'; interface InitScriptIndicatorProps { projectPath: string; } interface SingleIndicatorProps { stateKey: string; state: InitScriptState; onDismiss: (key: string) => void; isOnlyOne: boolean; // Whether this is the only indicator shown autoDismiss: boolean; // Whether to auto-dismiss after completion } function SingleIndicator({ stateKey, state, onDismiss, isOnlyOne, autoDismiss, }: SingleIndicatorProps) { const [showLogs, setShowLogs] = useState(false); const logsEndRef = useRef(null); const { status, output, branch, error } = state; // Auto-scroll to bottom when new output arrives useEffect(() => { if (showLogs && logsEndRef.current) { logsEndRef.current.scrollIntoView({ behavior: 'smooth' }); } }, [output, showLogs]); // Auto-expand logs when script starts (only if it's the only one or running) useEffect(() => { if (status === 'running' && isOnlyOne) { setShowLogs(true); } }, [status, isOnlyOne]); // Auto-dismiss after completion (5 seconds) useEffect(() => { if (autoDismiss && (status === 'success' || status === 'failed')) { const timer = setTimeout(() => { onDismiss(stateKey); }, 5000); return () => clearTimeout(timer); } }, [status, autoDismiss, stateKey, onDismiss]); if (status === 'idle') return null; return (
{/* Header */}
{status === 'running' && } {status === 'success' && } {status === 'failed' && } Init Script{' '} {status === 'running' ? 'Running' : status === 'success' ? 'Completed' : 'Failed'}
{status !== 'running' && ( )}
{/* Branch info */}
Branch: {branch}
{/* Logs (collapsible) */} {showLogs && (
{output.length > 0 ? ( ) : (
{status === 'running' ? 'Waiting for output...' : 'No output'}
)} {error &&
Error: {error}
}
)} {/* Status bar for completed states */} {status !== 'running' && (
{status === 'success' ? 'Initialization completed successfully' : 'Initialization failed - worktree is still usable'}
)}
); } export function InitScriptIndicator({ projectPath }: InitScriptIndicatorProps) { const getInitScriptStatesForProject = useAppStore((s) => s.getInitScriptStatesForProject); const clearInitScriptState = useAppStore((s) => s.clearInitScriptState); const getAutoDismissInitScriptIndicator = useAppStore((s) => s.getAutoDismissInitScriptIndicator); const [dismissedKeys, setDismissedKeys] = useState>(new Set()); // Get auto-dismiss setting const autoDismiss = getAutoDismissInitScriptIndicator(projectPath); // Get all init script states for this project const allStates = getInitScriptStatesForProject(projectPath); // Filter out dismissed and idle states const activeStates = allStates.filter( ({ key, state }) => !dismissedKeys.has(key) && state.status !== 'idle' ); // Reset dismissed keys when a new script starts for a branch useEffect(() => { const runningKeys = allStates .filter(({ state }) => state.status === 'running') .map(({ key }) => key); if (runningKeys.length > 0) { setDismissedKeys((prev) => { const newSet = new Set(prev); runningKeys.forEach((key) => newSet.delete(key)); return newSet; }); } }, [allStates]); const handleDismiss = useCallback( (key: string) => { setDismissedKeys((prev) => new Set(prev).add(key)); // Extract branch from key (format: "projectPath::branch") const branch = key.split('::')[1]; if (branch) { // Clear state after a delay to allow for future scripts setTimeout(() => { clearInitScriptState(projectPath, branch); }, 100); } }, [projectPath, clearInitScriptState] ); if (activeStates.length === 0) return null; return (
{activeStates.map(({ key, state }) => ( ))}
); }