← back to Letsbegin

components/ProcessStatusBar.tsx

789 lines

'use client'

import { useState, useEffect } from 'react'

interface RalphProcess {
  project: string
  projectPath: string
  pid: number
  cpu: string
  memory: string
  status: 'running' | 'idle' | 'completed' | 'failed' | 'paused'
  currentStory: string
  startTime: string
  progress: string
  errorLogs?: string[]
  port?: number
}

// Project port mappings
const PROJECT_PORTS: Record<string, number> = {
  'portfolio': 7899,
  'letsbegin': 7300,
  'test': 7300,
  'angels-flowers': 3000,
  'watches': 3000,
  'dear-bubbe-nextjs': 3011,
  'bubbe-ai': 3011,
}

interface ProcessDetails {
  project: string
  projectPath: string
  status: string
  logs: string[]
  stories: { id: string; title: string; status: string; notes?: string }[]
  progress: string
  startTime: string
  pid: number
  cpu: string
  memory: string
  port: number | null
}

export default function ProcessStatusBar() {
  const [processes, setProcesses] = useState<RalphProcess[]>([])
  const [isExpanded, setIsExpanded] = useState(false)
  const [lastUpdate, setLastUpdate] = useState<Date>(new Date())
  const [loadingAction, setLoadingAction] = useState<string | null>(null)
  const [errorModal, setErrorModal] = useState<{ project: string; projectPath: string; analysis: string; isFixing?: boolean; fixAttempt?: number } | null>(null)
  const [detailModal, setDetailModal] = useState<ProcessDetails | null>(null)
  const [loadingDetails, setLoadingDetails] = useState(false)

  useEffect(() => {
    fetchProcesses()
    const interval = setInterval(fetchProcesses, 3000) // Update every 3 seconds
    return () => clearInterval(interval)
  }, [])

  async function fetchProcesses() {
    try {
      const res = await fetch('/api/processes')
      if (res.ok) {
        const data = await res.json()
        setProcesses(data.processes || [])
        setLastUpdate(new Date())
      }
    } catch (err) {
      console.error('Failed to fetch processes:', err)
    }
  }

  async function handleStart(projectPath: string, project: string) {
    setLoadingAction(`start-${project}`)
    try {
      const res = await fetch('/api/processes/start', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ projectPath, project })
      })
      if (res.ok) {
        fetchProcesses()
      } else {
        const data = await res.json()
        alert(`Failed to start: ${data.error || 'Unknown error'}`)
      }
    } catch (err) {
      console.error('Failed to start process:', err)
      alert('Failed to start process')
    } finally {
      setLoadingAction(null)
    }
  }

  async function handleStop(projectPath: string, project: string, pid: number) {
    setLoadingAction(`stop-${project}`)
    try {
      const res = await fetch('/api/ralph/control', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ action: 'stop', project, pid })
      })
      if (res.ok) {
        fetchProcesses()
      } else {
        const data = await res.json()
        alert(`Failed to stop: ${data.error || 'Unknown error'}`)
      }
    } catch (err) {
      console.error('Failed to stop process:', err)
      alert('Failed to stop process')
    } finally {
      setLoadingAction(null)
    }
  }

  async function handlePause(project: string, pid: number) {
    setLoadingAction(`pause-${project}`)
    try {
      const res = await fetch('/api/ralph/control', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ action: 'pause', project, pid })
      })
      if (res.ok) {
        fetchProcesses()
      } else {
        const data = await res.json()
        alert(`Failed to pause: ${data.error || 'Unknown error'}`)
      }
    } catch (err) {
      console.error('Failed to pause process:', err)
      alert('Failed to pause process')
    } finally {
      setLoadingAction(null)
    }
  }

  async function handleResume(project: string, pid: number) {
    setLoadingAction(`resume-${project}`)
    try {
      const res = await fetch('/api/ralph/control', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ action: 'resume', project, pid })
      })
      if (res.ok) {
        fetchProcesses()
      } else {
        const data = await res.json()
        alert(`Failed to resume: ${data.error || 'Unknown error'}`)
      }
    } catch (err) {
      console.error('Failed to resume process:', err)
      alert('Failed to resume process')
    } finally {
      setLoadingAction(null)
    }
  }

  async function handleAnalyzeErrors(projectPath: string, project: string) {
    setLoadingAction(`analyze-${project}`)
    try {
      const res = await fetch('/api/processes/analyze', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ projectPath, project })
      })
      const data = await res.json()
      if (res.ok) {
        setErrorModal({ project, projectPath, analysis: data.analysis, fixAttempt: 0 })
      } else {
        alert(`Failed to analyze: ${data.error || 'Unknown error'}`)
      }
    } catch (err) {
      console.error('Failed to analyze errors:', err)
      alert('Failed to analyze errors')
    } finally {
      setLoadingAction(null)
    }
  }

  async function handleFixAndRestart(projectPath: string, project: string, analysis: string, attempt: number = 1) {
    if (!errorModal) return

    setErrorModal(prev => prev ? { ...prev, isFixing: true, fixAttempt: attempt } : null)

    try {
      const res = await fetch('/api/processes/fix', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ projectPath, project, analysis, attempt })
      })
      const data = await res.json()

      // Update modal with fix results
      const newAnalysis = `${analysis}\n\n${'='.repeat(50)}\nFIX ATTEMPT ${attempt}\n${'='.repeat(50)}\n${data.logs?.join('\n') || 'No logs'}`

      if (data.success && data.restarted) {
        setErrorModal(prev => prev ? {
          ...prev,
          analysis: newAnalysis + '\n\n✅ Fixes applied and process restarted!',
          isFixing: false
        } : null)

        // Wait and check if process is still failing
        setTimeout(async () => {
          await fetchProcesses()
          const proc = processes.find(p => p.project === project)
          if (proc?.status === 'failed' && attempt < 5) {
            // Auto-retry if still failing
            handleFixAndRestart(projectPath, project, newAnalysis, attempt + 1)
          }
        }, 10000) // Check after 10 seconds
      } else {
        setErrorModal(prev => prev ? {
          ...prev,
          analysis: newAnalysis + (data.error ? `\n\n❌ Error: ${data.error}` : ''),
          isFixing: false
        } : null)
      }
    } catch (err: any) {
      setErrorModal(prev => prev ? {
        ...prev,
        analysis: `${analysis}\n\n❌ Fix failed: ${err.message}`,
        isFixing: false
      } : null)
    }
  }

  async function openProcessDetails(proc: RalphProcess) {
    setLoadingDetails(true)
    try {
      const res = await fetch(`/api/processes/details?project=${encodeURIComponent(proc.project)}&path=${encodeURIComponent(proc.projectPath)}`)
      const data = await res.json()
      if (res.ok) {
        setDetailModal({
          project: proc.project,
          projectPath: proc.projectPath,
          status: proc.status,
          logs: data.logs || [],
          stories: data.stories || [],
          progress: proc.progress,
          startTime: proc.startTime,
          pid: proc.pid,
          cpu: proc.cpu,
          memory: proc.memory,
          port: data.port || null
        })
      } else {
        alert(`Failed to load details: ${data.error || 'Unknown error'}`)
      }
    } catch (err) {
      console.error('Failed to load process details:', err)
      alert('Failed to load process details')
    } finally {
      setLoadingDetails(false)
    }
  }

  const runningCount = processes.filter(p => p.status === 'running').length
  const pausedCount = processes.filter(p => p.status === 'paused').length
  const completedCount = processes.filter(p => p.status === 'completed').length
  const failedCount = processes.filter(p => p.status === 'failed').length

  // If no processes, show minimal bar
  if (processes.length === 0) {
    return (
      <div className="fixed bottom-0 left-0 right-0 z-50">
        <div className="bg-[#0d0d12] border-t-4 border-[var(--border)] px-4 py-2 flex items-center justify-center">
          <span className="text-sm text-[var(--foreground-tertiary)]">
            🤖 No Ralphy processes running
          </span>
        </div>
      </div>
    )
  }

  return (
    <div className="fixed bottom-0 left-0 right-0 z-50">
      {/* Expanded Panel */}
      {isExpanded && (
        <div className="bg-[var(--surface)] border-t-2 border-[var(--primary)] max-h-64 overflow-y-auto">
          <div className="p-4">
            <div className="flex justify-between items-center mb-3">
              <h3 className="font-bold">🤖 Ralphy Boy Processes</h3>
              <span className="text-xs text-[var(--foreground-tertiary)]">
                Updated: {lastUpdate.toLocaleTimeString()}
              </span>
            </div>
            <div className="overflow-x-auto">
              <table className="w-full text-sm">
                <thead>
                  <tr className="text-left text-[var(--foreground-secondary)] border-b border-[var(--border)]">
                    <th className="pb-2 pr-4">Project</th>
                    <th className="pb-2 pr-4">Status</th>
                    <th className="pb-2 pr-4">Current Story</th>
                    <th className="pb-2 pr-4">Progress</th>
                    <th className="pb-2 pr-4">CPU</th>
                    <th className="pb-2 pr-4">Started</th>
                    <th className="pb-2">Actions</th>
                  </tr>
                </thead>
                <tbody>
                  {processes.map((proc, idx) => (
                    <tr
                      key={idx}
                      className="border-b border-[var(--border)]/50 hover:bg-[var(--surface-alt)] cursor-pointer"
                      onClick={() => openProcessDetails(proc)}
                    >
                      <td className="py-2 pr-4 font-medium">
                        <div className="flex items-center gap-2">
                          <span className={`w-2 h-2 rounded-full ${
                            proc.status === 'running' ? 'bg-green-500 animate-pulse' :
                            proc.status === 'paused' ? 'bg-yellow-500' :
                            proc.status === 'completed' ? 'bg-blue-500' :
                            proc.status === 'failed' ? 'bg-red-500' : 'bg-gray-500'
                          }`}></span>
                          <span>{proc.project}</span>
                          <button
                            onClick={(e) => {
                              e.stopPropagation()
                              const port = proc.port || PROJECT_PORTS[proc.project.toLowerCase()] || 3000
                              window.open(`http://45.61.58.125:${port}/`, '_blank')
                            }}
                            className="ml-1 text-[var(--primary)] hover:text-[var(--primary-hover)] transition-colors"
                            title={`Open http://45.61.58.125:${proc.port || PROJECT_PORTS[proc.project.toLowerCase()] || 3000}/`}
                          >
                            <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
                            </svg>
                          </button>
                        </div>
                      </td>
                      <td className="py-2 pr-4">
                        <span className={`px-2 py-0.5 rounded text-xs font-medium ${
                          proc.status === 'running' ? 'bg-green-500/20 text-green-400' :
                          proc.status === 'paused' ? 'bg-yellow-500/20 text-yellow-400' :
                          proc.status === 'completed' ? 'bg-blue-500/20 text-blue-400' :
                          proc.status === 'failed' ? 'bg-red-500/20 text-red-400' : 'bg-gray-500/20 text-gray-400'
                        }`}>
                          {proc.status.toUpperCase()}
                        </span>
                      </td>
                      <td className="py-2 pr-4 font-mono text-[var(--primary)]">{proc.currentStory}</td>
                      <td className="py-2 pr-4">
                        <div className="flex items-center gap-2">
                          <div className="w-20 h-2 bg-[var(--surface-alt)] rounded-full overflow-hidden">
                            <div
                              className={`h-full ${
                                proc.status === 'completed' ? 'bg-blue-500' :
                                proc.status === 'failed' ? 'bg-red-500' : 'bg-green-500'
                              }`}
                              style={{
                                width: `${(() => {
                                  const [done, total] = proc.progress.split('/').map(Number)
                                  return total > 0 ? (done / total) * 100 : 0
                                })()}%`
                              }}
                            ></div>
                          </div>
                          <span className="text-xs">{proc.progress}</span>
                        </div>
                      </td>
                      <td className="py-2 pr-4 text-[var(--foreground-secondary)]">{proc.cpu}%</td>
                      <td className="py-2 pr-4 text-[var(--foreground-secondary)]">{proc.startTime}</td>
                      <td className="py-2">
                        <div className="flex items-center gap-1">
                          {proc.status === 'running' ? (
                            <>
                              <button
                                onClick={(e) => { e.stopPropagation(); handlePause(proc.project, proc.pid); }}
                                disabled={loadingAction === `pause-${proc.project}`}
                                className="px-2 py-1 text-xs font-medium rounded bg-yellow-500/20 text-yellow-400 hover:bg-yellow-500/30 border border-yellow-500/40 disabled:opacity-50"
                                title="Pause process"
                              >
                                {loadingAction === `pause-${proc.project}` ? '...' : '⏸'}
                              </button>
                              <button
                                onClick={(e) => { e.stopPropagation(); handleStop(proc.projectPath, proc.project, proc.pid); }}
                                disabled={loadingAction === `stop-${proc.project}`}
                                className="px-2 py-1 text-xs font-medium rounded bg-red-500/20 text-red-400 hover:bg-red-500/30 border border-red-500/40 disabled:opacity-50"
                                title="Stop process"
                              >
                                {loadingAction === `stop-${proc.project}` ? '...' : '⏹'}
                              </button>
                            </>
                          ) : proc.status === 'paused' ? (
                            <>
                              <button
                                onClick={(e) => { e.stopPropagation(); handleResume(proc.project, proc.pid); }}
                                disabled={loadingAction === `resume-${proc.project}`}
                                className="px-2 py-1 text-xs font-medium rounded bg-green-500/20 text-green-400 hover:bg-green-500/30 border border-green-500/40 disabled:opacity-50"
                                title="Resume process"
                              >
                                {loadingAction === `resume-${proc.project}` ? '...' : '▶'}
                              </button>
                              <button
                                onClick={(e) => { e.stopPropagation(); handleStop(proc.projectPath, proc.project, proc.pid); }}
                                disabled={loadingAction === `stop-${proc.project}`}
                                className="px-2 py-1 text-xs font-medium rounded bg-red-500/20 text-red-400 hover:bg-red-500/30 border border-red-500/40 disabled:opacity-50"
                                title="Stop process"
                              >
                                {loadingAction === `stop-${proc.project}` ? '...' : '⏹'}
                              </button>
                            </>
                          ) : (
                            <button
                              onClick={(e) => { e.stopPropagation(); handleStart(proc.projectPath, proc.project); }}
                              disabled={loadingAction === `start-${proc.project}`}
                              className="px-2 py-1 text-xs font-medium rounded bg-green-500/20 text-green-400 hover:bg-green-500/30 border border-green-500/40 disabled:opacity-50"
                              title="Start process"
                            >
                              {loadingAction === `start-${proc.project}` ? '...' : '▶ Start'}
                            </button>
                          )}
                          {proc.status === 'failed' && (
                            <button
                              onClick={(e) => { e.stopPropagation(); handleAnalyzeErrors(proc.projectPath, proc.project); }}
                              disabled={loadingAction === `analyze-${proc.project}`}
                              className="px-2 py-1 text-xs font-medium rounded bg-yellow-500/20 text-yellow-400 hover:bg-yellow-500/30 border border-yellow-500/40 disabled:opacity-50"
                              title="Analyze errors"
                            >
                              {loadingAction === `analyze-${proc.project}` ? '...' : '🔍'}
                            </button>
                          )}
                        </div>
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </div>
        </div>
      )}

      {/* Status Bar - Thick Line */}
      <div
        className="bg-[#0d0d12] border-t-4 border-[var(--primary)] px-4 py-3 flex items-center justify-between cursor-pointer hover:bg-[var(--surface)]"
        onClick={() => setIsExpanded(!isExpanded)}
      >
        <div className="flex items-center gap-4">
          <div className="flex items-center gap-2">
            <span className="text-xl">🤖</span>
            <span className="font-bold">Ralphy Processes</span>
          </div>

          {/* Process Pills */}
          <div className="flex items-center gap-2">
            {processes.map((proc, idx) => (
              <div
                key={idx}
                className={`px-3 py-1.5 rounded-lg text-sm font-medium flex items-center gap-2 ${
                  proc.status === 'running'
                    ? 'bg-green-500/20 text-green-400 border border-green-500/40'
                    : proc.status === 'paused'
                    ? 'bg-yellow-500/20 text-yellow-400 border border-yellow-500/40'
                    : proc.status === 'completed'
                    ? 'bg-blue-500/20 text-blue-400 border border-blue-500/40'
                    : proc.status === 'failed'
                    ? 'bg-red-500/20 text-red-400 border border-red-500/40'
                    : 'bg-gray-500/20 text-gray-400 border border-gray-500/40'
                }`}
              >
                <span className={`w-2 h-2 rounded-full ${
                  proc.status === 'running' ? 'bg-green-500 animate-pulse' :
                  proc.status === 'paused' ? 'bg-yellow-500' :
                  proc.status === 'completed' ? 'bg-blue-500' :
                  proc.status === 'failed' ? 'bg-red-500' : 'bg-gray-500'
                }`}></span>
                <span className="font-semibold">{proc.project}</span>
                <span className="text-xs opacity-75">{proc.currentStory}</span>
                <span className="text-xs opacity-50">{proc.progress}</span>
              </div>
            ))}
          </div>
        </div>

        <div className="flex items-center gap-4">
          {/* Summary Stats */}
          <div className="flex items-center gap-3 text-sm">
            {runningCount > 0 && (
              <div className="flex items-center gap-1">
                <span className="text-green-400 animate-pulse">●</span>
                <span className="text-green-400 font-medium">{runningCount} Running</span>
              </div>
            )}
            {pausedCount > 0 && (
              <div className="flex items-center gap-1">
                <span className="text-yellow-400">●</span>
                <span className="text-yellow-400">{pausedCount} Paused</span>
              </div>
            )}
            {completedCount > 0 && (
              <div className="flex items-center gap-1">
                <span className="text-blue-400">●</span>
                <span className="text-blue-400">{completedCount} Done</span>
              </div>
            )}
            {failedCount > 0 && (
              <div className="flex items-center gap-1">
                <span className="text-red-400">●</span>
                <span className="text-red-400">{failedCount} Failed</span>
              </div>
            )}
          </div>

          {/* Expand/Collapse Icon */}
          <svg
            className={`w-5 h-5 text-[var(--foreground-secondary)] transition-transform ${isExpanded ? 'rotate-180' : ''}`}
            fill="none"
            stroke="currentColor"
            viewBox="0 0 24 24"
          >
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 15l7-7 7 7" />
          </svg>
        </div>
      </div>

      {/* Error Analysis Modal */}
      {errorModal && (
        <div className="fixed inset-0 bg-black/60 flex items-center justify-center z-[100]" onClick={() => !errorModal.isFixing && setErrorModal(null)}>
          <div
            className="bg-[var(--surface)] border border-[var(--border)] rounded-xl max-w-3xl w-full mx-4 max-h-[85vh] overflow-hidden"
            onClick={(e) => e.stopPropagation()}
          >
            <div className="p-4 border-b border-[var(--border)] flex items-center justify-between">
              <div className="flex items-center gap-3">
                <h3 className="font-bold text-lg flex items-center gap-2">
                  <span>🔍</span>
                  Error Analysis: {errorModal.project}
                </h3>
                {errorModal.isFixing && (
                  <span className="px-2 py-1 text-xs rounded-full bg-yellow-500/20 text-yellow-400 flex items-center gap-1">
                    <svg className="w-3 h-3 animate-spin" fill="none" viewBox="0 0 24 24">
                      <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
                      <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                    </svg>
                    Fixing (Attempt {errorModal.fixAttempt})...
                  </span>
                )}
                {errorModal.fixAttempt && errorModal.fixAttempt > 0 && !errorModal.isFixing && (
                  <span className="px-2 py-1 text-xs rounded-full bg-blue-500/20 text-blue-400">
                    Attempt {errorModal.fixAttempt}/5
                  </span>
                )}
              </div>
              <button
                onClick={() => !errorModal.isFixing && setErrorModal(null)}
                className="text-[var(--foreground-secondary)] hover:text-[var(--foreground)] text-xl disabled:opacity-50"
                disabled={errorModal.isFixing}
              >
                ×
              </button>
            </div>
            <div className="p-4 overflow-y-auto max-h-[60vh]">
              <pre className="whitespace-pre-wrap text-sm font-mono bg-[var(--background)] p-4 rounded-lg">
                {errorModal.analysis}
              </pre>
            </div>
            <div className="p-4 border-t border-[var(--border)] flex items-center justify-between">
              <div className="text-xs text-[var(--foreground-tertiary)]">
                Auto-fix uses AI to diagnose and repair issues (max 5 attempts)
              </div>
              <div className="flex items-center gap-2">
                <button
                  onClick={() => {
                    navigator.clipboard.writeText(errorModal.analysis)
                    alert('Copied to clipboard!')
                  }}
                  className="px-4 py-2 text-sm rounded bg-[var(--surface-alt)] hover:bg-[var(--border)]"
                  disabled={errorModal.isFixing}
                >
                  Copy
                </button>
                <button
                  onClick={() => handleFixAndRestart(
                    errorModal.projectPath,
                    errorModal.project,
                    errorModal.analysis,
                    (errorModal.fixAttempt || 0) + 1
                  )}
                  disabled={errorModal.isFixing || (errorModal.fixAttempt || 0) >= 5}
                  className="px-4 py-2 text-sm rounded bg-green-500/20 text-green-400 hover:bg-green-500/30 border border-green-500/40 disabled:opacity-50 flex items-center gap-2"
                >
                  {errorModal.isFixing ? (
                    <>
                      <svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
                        <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
                        <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                      </svg>
                      Fixing...
                    </>
                  ) : (
                    <>🔧 Fix & Restart</>
                  )}
                </button>
                <button
                  onClick={() => setErrorModal(null)}
                  className="px-4 py-2 text-sm rounded bg-[var(--surface-alt)] hover:bg-[var(--border)]"
                  disabled={errorModal.isFixing}
                >
                  Close
                </button>
              </div>
            </div>
          </div>
        </div>
      )}

      {/* Process Details Modal */}
      {detailModal && (
        <div className="fixed inset-0 bg-black/60 flex items-center justify-center z-[100]" onClick={() => setDetailModal(null)}>
          <div
            className="bg-[var(--surface)] border border-[var(--border)] rounded-xl max-w-4xl w-full mx-4 max-h-[85vh] overflow-hidden"
            onClick={(e) => e.stopPropagation()}
          >
            {/* Header */}
            <div className="p-4 border-b border-[var(--border)] flex items-center justify-between">
              <div className="flex items-center gap-3">
                <h3 className="font-bold text-lg">📋 {detailModal.project}</h3>
                <span className={`px-2 py-0.5 rounded text-xs font-medium ${
                  detailModal.status === 'running' ? 'bg-green-500/20 text-green-400' :
                  detailModal.status === 'paused' ? 'bg-yellow-500/20 text-yellow-400' :
                  detailModal.status === 'completed' ? 'bg-blue-500/20 text-blue-400' :
                  detailModal.status === 'failed' ? 'bg-red-500/20 text-red-400' : 'bg-gray-500/20 text-gray-400'
                }`}>
                  {detailModal.status.toUpperCase()}
                </span>
              </div>
              <button
                onClick={() => setDetailModal(null)}
                className="text-[var(--foreground-secondary)] hover:text-[var(--foreground)] text-2xl leading-none"
              >
                ×
              </button>
            </div>

            {/* Process Info Bar */}
            <div className="px-4 py-2 bg-[var(--background)] border-b border-[var(--border)] flex items-center gap-6 text-sm">
              <div>
                <span className="text-[var(--foreground-tertiary)]">PID:</span>{' '}
                <span className="font-mono">{detailModal.pid || 'N/A'}</span>
              </div>
              <div>
                <span className="text-[var(--foreground-tertiary)]">CPU:</span>{' '}
                <span className="font-mono">{detailModal.cpu}%</span>
              </div>
              <div>
                <span className="text-[var(--foreground-tertiary)]">Memory:</span>{' '}
                <span className="font-mono">{detailModal.memory}</span>
              </div>
              <div>
                <span className="text-[var(--foreground-tertiary)]">Progress:</span>{' '}
                <span className="font-mono text-[var(--primary)]">{detailModal.progress}</span>
              </div>
              <div>
                <span className="text-[var(--foreground-tertiary)]">Started:</span>{' '}
                <span>{detailModal.startTime}</span>
              </div>
              <div>
                <span className="text-[var(--foreground-tertiary)]">Port:</span>{' '}
                <span className="font-mono text-[var(--primary)]">
                  {detailModal.port || PROJECT_PORTS[detailModal.project.toLowerCase()] || 'N/A'}
                </span>
              </div>
              <button
                onClick={() => {
                  const port = detailModal.port || PROJECT_PORTS[detailModal.project.toLowerCase()] || 3000
                  window.open(`http://45.61.58.125:${port}/`, '_blank')
                }}
                className="ml-auto px-3 py-1 text-xs rounded bg-[var(--primary)]/20 text-[var(--primary)] hover:bg-[var(--primary)]/30 flex items-center gap-1"
              >
                Open App ↗
              </button>
            </div>

            {/* Content - Two Columns */}
            <div className="flex h-[60vh]">
              {/* Stories Panel */}
              <div className="w-1/2 border-r border-[var(--border)] flex flex-col">
                <div className="p-3 border-b border-[var(--border)] font-medium text-sm">
                  📝 User Stories ({detailModal.stories.length})
                </div>
                <div className="flex-1 overflow-y-auto p-2">
                  {detailModal.stories.length === 0 ? (
                    <p className="text-[var(--foreground-tertiary)] text-sm p-2">No stories found</p>
                  ) : (
                    <div className="space-y-2">
                      {detailModal.stories.map((story, idx) => (
                        <div
                          key={idx}
                          className={`p-2 rounded border ${
                            story.status === 'DONE' || story.status === 'pass' ? 'border-green-500/30 bg-green-500/10' :
                            story.status === 'RUNNING' || story.status === 'running' ? 'border-yellow-500/30 bg-yellow-500/10' :
                            story.status === 'FAIL' || story.status === 'fail' ? 'border-red-500/30 bg-red-500/10' :
                            'border-[var(--border)] bg-[var(--surface-alt)]'
                          }`}
                        >
                          <div className="flex items-center justify-between">
                            <span className="font-mono text-sm text-[var(--primary)]">{story.id}</span>
                            <span className={`text-xs px-1.5 py-0.5 rounded ${
                              story.status === 'DONE' || story.status === 'pass' ? 'bg-green-500/20 text-green-400' :
                              story.status === 'RUNNING' || story.status === 'running' ? 'bg-yellow-500/20 text-yellow-400' :
                              story.status === 'FAIL' || story.status === 'fail' ? 'bg-red-500/20 text-red-400' :
                              'bg-gray-500/20 text-gray-400'
                            }`}>
                              {story.status}
                            </span>
                          </div>
                          <div className="text-xs text-[var(--foreground-secondary)] mt-1 truncate">{story.title}</div>
                          {story.notes && (
                            <div className="text-xs text-[var(--foreground-tertiary)] mt-1 italic">{story.notes}</div>
                          )}
                        </div>
                      ))}
                    </div>
                  )}
                </div>
              </div>

              {/* Logs Panel */}
              <div className="w-1/2 flex flex-col">
                <div className="p-3 border-b border-[var(--border)] font-medium text-sm flex items-center justify-between">
                  <span>📺 Live Logs</span>
                  <button
                    onClick={() => {
                      navigator.clipboard.writeText(detailModal.logs.join('\n'))
                      alert('Logs copied!')
                    }}
                    className="text-xs text-[var(--foreground-tertiary)] hover:text-[var(--foreground)]"
                  >
                    Copy
                  </button>
                </div>
                <div className="flex-1 overflow-y-auto p-2 bg-[var(--background)] font-mono text-xs">
                  {detailModal.logs.length === 0 ? (
                    <p className="text-[var(--foreground-tertiary)] p-2">No logs available</p>
                  ) : (
                    detailModal.logs.map((log, idx) => (
                      <div
                        key={idx}
                        className={`py-0.5 ${
                          log.includes('Error') || log.includes('FAIL') ? 'text-red-400' :
                          log.includes('✓') || log.includes('DONE') || log.includes('pass') ? 'text-green-400' :
                          log.includes('Running') || log.includes('RUNNING') ? 'text-yellow-400' :
                          'text-[var(--foreground-secondary)]'
                        }`}
                      >
                        {log}
                      </div>
                    ))
                  )}
                </div>
              </div>
            </div>

            {/* Footer */}
            <div className="p-3 border-t border-[var(--border)] flex items-center justify-between">
              <span className="text-xs text-[var(--foreground-tertiary)]">
                Path: {detailModal.projectPath}
              </span>
              <button
                onClick={() => setDetailModal(null)}
                className="px-4 py-2 text-sm rounded bg-[var(--surface-alt)] hover:bg-[var(--border)]"
              >
                Close
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Loading Overlay */}
      {loadingDetails && (
        <div className="fixed inset-0 bg-black/40 flex items-center justify-center z-[100]">
          <div className="bg-[var(--surface)] border border-[var(--border)] rounded-lg px-6 py-4 flex items-center gap-3">
            <svg className="w-5 h-5 animate-spin text-[var(--primary)]" fill="none" viewBox="0 0 24 24">
              <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
              <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
            </svg>
            <span>Loading process details...</span>
          </div>
        </div>
      )}
    </div>
  )
}