summaryrefslogblamecommitdiff
path: root/makima/frontend/src/components/mesh/TaskDetail.tsx
blob: e4fcce3492a6bb332a082d3802e554dcdfd3b546 (plain) (tree)
1
2
3
4
5
6
7
8
9







                                                                                                                  
                                                    
















                                                                                                                                                  

                                                

                                                                               






                                                                              

                                                                                          










































































                                                       
                 
           





                
                
























                                                                                          

                                                                




                                                                                                                  



                                                                              

                                                                     
 








                                                                                                         
                               

                                           














































































































































































































                                                                                                                                                                                                             
                               







                                                                                                                                                                                                   








                                                                                                                                                                                                           






















































                                                                                                                                                                                             








                                                                                                                                                       



















































































































































































































                                                                                                                                                                                                  
                                                           



                                                                                        
                                                                                                                
                    
                                           







                                                                                   

                

                                        


                                                       
                                        
                                                                            
                                                                             



                                                                    
                                       





                                                                                                             
                                                           








































































































































































































                                                                                                                                                                                                                                








                                                   


          
import { useState, useCallback, useMemo, useEffect } from "react";
import type { TaskWithSubtasks, TaskStatus, TaskSummary, CompletionAction, DaemonDirectory } from "../../lib/api";
import { retryCompletionAction, getDaemonDirectories, cloneWorktree } from "../../lib/api";
import { SubtaskTree, SubtaskProgressBar, calculateTreeStats } from "./SubtaskTree";
import { OverlayDiffViewer } from "./OverlayDiffViewer";
import { PRPreview } from "./PRPreview";
import { InlineSubtaskEditor } from "./InlineSubtaskEditor";
import { DirectoryInput } from "./DirectoryInput";
import { BranchTaskModal } from "./BranchTaskModal";

interface TaskDetailProps {
  task: TaskWithSubtasks;
  loading: boolean;
  onBack: () => void;
  onSave: (taskId: string, name: string, description: string, plan: string, targetRepoPath?: string, completionAction?: CompletionAction) => void;
  onDelete: (taskId: string) => void;
  onStart: (taskId: string) => void;
  onStop: (taskId: string) => void;
  onRestart: (taskId: string) => void;
  onContinue: (taskId: string) => void;
  onSelectSubtask: (taskId: string) => void;
  onCreateSubtask: () => void;
  /** Toggle viewing a subtask's output (for running subtasks) */
  onToggleSubtaskOutput?: (subtaskId: string, subtaskName: string) => void;
  /** Which subtask's output is currently being viewed */
  viewingSubtaskId?: string | null;
  /** Navigate to view the contract */
  onViewContract?: (contractId: string) => void;
  /** Branch the task to create a new task with same state */
  onBranch?: (taskId: string, message: string, name?: string) => Promise<void>;
  // Optional advanced features
  overlayDiff?: string;
  changedFiles?: string[];
  onRequestDiff?: () => void;
  onCreatePR?: (title: string, body: string, draft: boolean) => Promise<void>;
  onAutoMerge?: () => Promise<void>;
  fetchSubtasks?: (taskId: string) => Promise<TaskSummary[]>;
  /** For supervisor tasks: all tasks in the contract (excluding the supervisor itself) */
  contractTasks?: TaskSummary[];
}

function formatDate(dateStr: string): string {
  const date = new Date(dateStr);
  return date.toLocaleDateString("en-US", {
    month: "short",
    day: "numeric",
    year: "numeric",
    hour: "2-digit",
    minute: "2-digit",
  });
}

function getStatusColor(status: TaskStatus): string {
  switch (status) {
    case "pending":
      return "text-[#9bc3ff]";
    case "initializing":
    case "starting":
      return "text-cyan-400";
    case "running":
      return "text-green-400";
    case "paused":
      return "text-yellow-400";
    case "blocked":
      return "text-orange-400";
    case "done":
      return "text-emerald-400";
    case "failed":
      return "text-red-400";
    case "merged":
      return "text-purple-400";
    default:
      return "text-[#9bc3ff]";
  }
}

function getStatusBgColor(status: TaskStatus): string {
  switch (status) {
    case "pending":
      return "bg-[rgba(117,170,252,0.1)]";
    case "initializing":
    case "starting":
      return "bg-cyan-400/10";
    case "running":
      return "bg-green-400/10";
    case "paused":
      return "bg-yellow-400/10";
    case "blocked":
      return "bg-orange-400/10";
    case "done":
      return "bg-emerald-400/10";
    case "failed":
      return "bg-red-400/10";
    case "merged":
      return "bg-purple-400/10";
    default:
      return "bg-[rgba(117,170,252,0.1)]";
  }
}

export function TaskDetail({
  task,
  loading,
  onBack,
  onSave,
  onDelete,
  onStart,
  onStop,
  onRestart,
  onContinue,
  onSelectSubtask,
  onCreateSubtask,
  onToggleSubtaskOutput,
  viewingSubtaskId,
  onViewContract,
  onBranch,
  overlayDiff,
  changedFiles,
  onRequestDiff,
  onCreatePR,
  onAutoMerge,
  fetchSubtasks,
  contractTasks,
}: TaskDetailProps) {
  const [isEditing, setIsEditing] = useState(false);
  const [editName, setEditName] = useState(task.name);
  const [editDescription, setEditDescription] = useState(task.description || "");
  const [editPlan, setEditPlan] = useState(task.plan);
  const [editTargetRepoPath, setEditTargetRepoPath] = useState(task.targetRepoPath || "");
  const [editCompletionAction, setEditCompletionAction] = useState<CompletionAction>(
    (task.completionAction as CompletionAction) || "none"
  );
  const [showDiff, setShowDiff] = useState(false);
  const [showPRPreview, setShowPRPreview] = useState(false);
  const [useTreeView, setUseTreeView] = useState(false);
  // Track which subtask is expanded for inline editing
  const [expandedSubtaskId, setExpandedSubtaskId] = useState<string | null>(null);
  // Track interrupt dropdown state
  const [showInterruptMenu, setShowInterruptMenu] = useState(false);
  // Track retry completion action state
  const [isRetryingCompletion, setIsRetryingCompletion] = useState(false);
  const [retryError, setRetryError] = useState<string | null>(null);
  // Suggested directories from daemon
  const [suggestedDirectories, setSuggestedDirectories] = useState<DaemonDirectory[]>([]);
  // Track clone worktree state
  const [isCloning, setIsCloning] = useState(false);
  const [cloneError, setCloneError] = useState<string | null>(null);
  const [cloneTargetDir, setCloneTargetDir] = useState("");
  // Track branch modal state
  const [showBranchModal, setShowBranchModal] = useState(false);

  // Check if task is running
  const isTaskRunning = task.status === "running" || task.status === "initializing" || task.status === "starting";
  // Check if task is in a terminal state (can be continued/reopened)
  const isTaskTerminal = task.status === "done" || task.status === "failed" || task.status === "merged";
  // Check if this is a supervisor task
  const isSupervisor = task.isSupervisor === true;
  // Show continue for supervisors (always) or terminal states for other tasks
  const canContinue = isSupervisor || isTaskTerminal;
  // Show branch button when task has run at least once (not pending)
  const canBranch = onBranch && task.status !== "pending";

  // Determine which tasks to show: for supervisors, show contractTasks; for regular tasks, show subtasks
  const displayTasks = useMemo(() => {
    if (isSupervisor && contractTasks) {
      return contractTasks;
    }
    return task.subtasks;
  }, [isSupervisor, contractTasks, task.subtasks]);

  // Calculate task statistics for progress bar
  const subtaskStats = useMemo(
    () => calculateTreeStats(displayTasks),
    [displayTasks]
  );

  // Check if task can create PR
  const canCreatePR = useMemo(() => {
    return (
      (task.status === "done" || task.status === "merged") &&
      task.repositoryUrl &&
      (onCreatePR || onAutoMerge)
    );
  }, [task.status, task.repositoryUrl, onCreatePR, onAutoMerge]);

  // Check if task can retry completion action
  const canRetryCompletion = useMemo(() => {
    return (
      (task.status === "done" || task.status === "failed" || task.status === "merged") &&
      task.completionAction &&
      task.completionAction !== "none" &&
      task.targetRepoPath
      // Note: overlayPath may be null in server DB even if worktree exists on daemon
      // The daemon will scan for the worktree by task ID
    );
  }, [task.status, task.completionAction, task.targetRepoPath]);

  // Handler for retrying completion action
  const handleRetryCompletion = useCallback(async () => {
    setIsRetryingCompletion(true);
    setRetryError(null);
    try {
      await retryCompletionAction(task.id);
      // Success - the result will be shown in task output
    } catch (e) {
      setRetryError(e instanceof Error ? e.message : "Failed to retry completion action");
    } finally {
      setIsRetryingCompletion(false);
    }
  }, [task.id]);

  // Check if task can clone worktree
  const canCloneWorktree = useMemo(() => {
    return (
      (task.status === "done" || task.status === "failed" || task.status === "merged")
    );
  }, [task.status]);

  // Handler for cloning worktree
  const handleCloneWorktree = useCallback(async () => {
    if (!cloneTargetDir.trim()) {
      setCloneError("Please enter a target directory");
      return;
    }
    setIsCloning(true);
    setCloneError(null);
    try {
      await cloneWorktree(task.id, cloneTargetDir);
      // Success - the result will be shown in task output
      setCloneTargetDir(""); // Clear input on success
    } catch (e) {
      setCloneError(e instanceof Error ? e.message : "Failed to clone worktree");
    } finally {
      setIsCloning(false);
    }
  }, [task.id, cloneTargetDir]);

  // Fetch suggested directories when entering edit mode or when clone section is visible
  useEffect(() => {
    if (isEditing || canCloneWorktree) {
      getDaemonDirectories()
        .then((res) => setSuggestedDirectories(res.directories))
        .catch(() => setSuggestedDirectories([]));
    }
  }, [isEditing, canCloneWorktree]);

  const handleSave = useCallback(() => {
    onSave(
      task.id,
      editName,
      editDescription,
      editPlan,
      editTargetRepoPath || undefined,
      editCompletionAction
    );
    setIsEditing(false);
  }, [task.id, editName, editDescription, editPlan, editTargetRepoPath, editCompletionAction, onSave]);

  const handleCancel = useCallback(() => {
    setEditName(task.name);
    setEditDescription(task.description || "");
    setEditPlan(task.plan);
    setEditTargetRepoPath(task.targetRepoPath || "");
    setEditCompletionAction((task.completionAction as CompletionAction) || "none");
    setIsEditing(false);
  }, [task]);

  // Toggle subtask expansion for inline editing
  const handleSubtaskToggle = useCallback((subtaskId: string) => {
    setExpandedSubtaskId((prev) => (prev === subtaskId ? null : subtaskId));
  }, []);

  // Handle subtask click - toggle output view for any task status
  const handleSubtaskClick = useCallback(
    (subtask: TaskSummary) => {
      if (onToggleSubtaskOutput) {
        // Toggle viewing this subtask's output (works for any status)
        onToggleSubtaskOutput(subtask.id, subtask.name);
      } else {
        // Fallback to expand/collapse if output viewing not available
        handleSubtaskToggle(subtask.id);
      }
    },
    [onToggleSubtaskOutput, handleSubtaskToggle]
  );

  // Called when inline subtask editor saves changes
  const handleSubtaskUpdated = useCallback(() => {
    // Re-fetch the parent task to refresh subtask list
    // This will trigger from the parent component when task updates
  }, []);

  if (loading) {
    return (
      <div className="panel h-full flex items-center justify-center">
        <div className="font-mono text-[#9bc3ff] text-sm">Loading task...</div>
      </div>
    );
  }

  return (
    <div className="panel h-full flex flex-col overflow-hidden">
      {/* Header */}
      <div className="flex items-center justify-between p-4 pb-2 border-b border-dashed border-[rgba(117,170,252,0.35)] shrink-0">
        <div className="flex items-center gap-3">
          <button
            onClick={onBack}
            className="font-mono text-xs text-[#75aafc] hover:text-[#9bc3ff] transition-colors"
          >
            &lt; Back
          </button>
          <div className="font-mono text-xs text-[#9bc3ff] tracking-wide uppercase">
            TASK//
          </div>
        </div>
        <div className="flex items-center gap-2">
          {isEditing ? (
            <>
              <button
                onClick={handleCancel}
                className="px-3 py-1 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors uppercase"
              >
                Cancel
              </button>
              <button
                onClick={handleSave}
                className="px-3 py-1 font-mono text-xs text-green-400 border border-green-400/30 hover:border-green-400/50 hover:bg-green-400/10 transition-colors uppercase"
              >
                Save
              </button>
            </>
          ) : (
            <>
              {(task.status === "pending" || task.status === "failed") && (
                <button
                  onClick={() => onStart(task.id)}
                  className="px-3 py-1 font-mono text-xs text-green-400 border border-green-400/30 hover:border-green-400/50 hover:bg-green-400/10 transition-colors uppercase"
                >
                  Start
                </button>
              )}
              {isTaskRunning && (
                <div className="relative">
                  <button
                    onClick={() => setShowInterruptMenu(!showInterruptMenu)}
                    className="px-3 py-1 font-mono text-xs text-orange-400 border border-orange-400/30 hover:border-orange-400/50 hover:bg-orange-400/10 transition-colors uppercase flex items-center gap-1"
                  >
                    <span className="w-1.5 h-1.5 bg-orange-400 rounded-full animate-pulse" />
                    Interrupt
                  </button>
                  {showInterruptMenu && (
                    <>
                      {/* Backdrop to close menu on click outside */}
                      <div
                        className="fixed inset-0 z-40"
                        onClick={() => setShowInterruptMenu(false)}
                      />
                      <div className="absolute right-0 top-full mt-1 z-50 bg-[#0a1525] border border-[rgba(117,170,252,0.35)] shadow-lg">
                        <button
                          onClick={() => {
                            onRestart(task.id);
                            setShowInterruptMenu(false);
                          }}
                          className="block w-full px-4 py-2 font-mono text-xs text-left text-yellow-400 hover:bg-yellow-400/10 transition-colors whitespace-nowrap"
                        >
                          Restart Task
                        </button>
                        <button
                          onClick={() => {
                            onStop(task.id);
                            setShowInterruptMenu(false);
                          }}
                          className="block w-full px-4 py-2 font-mono text-xs text-left text-red-400 hover:bg-red-400/10 transition-colors whitespace-nowrap"
                        >
                          Cancel Task
                        </button>
                      </div>
                    </>
                  )}
                </div>
              )}
              {canContinue && (
                <button
                  onClick={() => onContinue(task.id)}
                  className="px-3 py-1 font-mono text-xs text-cyan-400 border border-cyan-400/30 hover:border-cyan-400/50 hover:bg-cyan-400/10 transition-colors uppercase flex items-center gap-1"
                >
                  <span className="w-1.5 h-1.5 bg-cyan-400 rounded-full" />
                  Continue
                </button>
              )}
              {canBranch && (
                <button
                  onClick={() => setShowBranchModal(true)}
                  className="px-3 py-1 font-mono text-xs text-purple-400 border border-purple-400/30 hover:border-purple-400/50 hover:bg-purple-400/10 transition-colors uppercase flex items-center gap-1"
                >
                  <span className="w-1.5 h-1.5 bg-purple-400 rounded-full" />
                  Branch
                </button>
              )}
              <button
                onClick={() => setIsEditing(true)}
                className="px-3 py-1 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors uppercase"
              >
                Edit
              </button>
              <button
                onClick={() => onDelete(task.id)}
                className="px-3 py-1 font-mono text-xs text-red-400 border border-red-400/30 hover:border-red-400/50 transition-colors uppercase"
              >
                Delete
              </button>
            </>
          )}
        </div>
      </div>

      {/* Content */}
      <div className="flex-1 overflow-y-auto p-4 space-y-4">
        {/* Task Info */}
        <div className="space-y-3">
          {isEditing ? (
            <>
              <input
                type="text"
                value={editName}
                onChange={(e) => setEditName(e.target.value)}
                className="w-full bg-transparent border border-[rgba(117,170,252,0.25)] text-[#dbe7ff] font-mono text-lg px-3 py-2 outline-none focus:border-[#3f6fb3]"
                placeholder="Task name"
              />
              <textarea
                value={editDescription}
                onChange={(e) => setEditDescription(e.target.value)}
                className="w-full bg-transparent border border-[rgba(117,170,252,0.25)] text-[#9bc3ff] font-mono text-sm px-3 py-2 outline-none focus:border-[#3f6fb3] min-h-[60px] resize-y"
                placeholder="Description (optional)"
              />
            </>
          ) : (
            <>
              <h2 className="font-mono text-lg text-[#dbe7ff]">{task.name}</h2>
              {task.description && (
                <p className="font-mono text-sm text-[#9bc3ff]">{task.description}</p>
              )}
            </>
          )}

          {/* Status badges */}
          <div className="flex flex-wrap gap-2">
            <span
              className={`px-2 py-0.5 font-mono text-xs uppercase ${getStatusColor(
                task.status as TaskStatus
              )} ${getStatusBgColor(task.status as TaskStatus)} border border-current/20`}
            >
              {task.status}
            </span>
            {/* Contract badge - clickable to view contract */}
            {task.contractId && onViewContract && (
              <button
                onClick={() => onViewContract(task.contractId!)}
                className="px-2 py-0.5 font-mono text-xs text-blue-400 bg-blue-400/10 border border-blue-400/20 hover:bg-blue-400/20 transition-colors"
              >
                Contract
              </button>
            )}
            {/* Orchestrator badge for depth 0 tasks with subtasks */}
            {task.depth === 0 && task.subtasks.length > 0 && (
              <span className="px-2 py-0.5 font-mono text-xs text-purple-400 bg-purple-400/10 border border-purple-400/20">
                Orchestrator
              </span>
            )}
            {/* Depth indicator for subtasks */}
            {task.depth > 0 && (
              <span className="px-2 py-0.5 font-mono text-xs text-cyan-400 bg-cyan-400/10 border border-cyan-400/20">
                Depth: {task.depth}
              </span>
            )}
            {task.priority > 0 && (
              <span className="px-2 py-0.5 font-mono text-xs text-orange-400 bg-orange-400/10 border border-orange-400/20">
                Priority: {task.priority}
              </span>
            )}
            {task.mergeMode && (
              <span className="px-2 py-0.5 font-mono text-xs text-purple-400 bg-purple-400/10 border border-purple-400/20">
                Merge: {task.mergeMode}
              </span>
            )}
          </div>

          {/* Metadata */}
          <div className="flex flex-wrap gap-4 font-mono text-[10px] text-[#75aafc]">
            <span>Created: {formatDate(task.createdAt)}</span>
            {task.startedAt && <span>Started: {formatDate(task.startedAt)}</span>}
            {task.completedAt && <span>Completed: {formatDate(task.completedAt)}</span>}
            <span>Version: {task.version}</span>
          </div>
        </div>

        {/* Plan */}
        <div className="space-y-2">
          <div className="font-mono text-xs text-[#9bc3ff] tracking-wide uppercase">
            Plan
          </div>
          {isEditing ? (
            <textarea
              value={editPlan}
              onChange={(e) => setEditPlan(e.target.value)}
              className="w-full bg-[rgba(0,0,0,0.2)] border border-[rgba(117,170,252,0.25)] text-[#dbe7ff] font-mono text-sm px-3 py-2 outline-none focus:border-[#3f6fb3] min-h-[200px] resize-y"
              placeholder="Enter the plan/instructions for this task..."
            />
          ) : (
            <pre className="bg-[rgba(0,0,0,0.2)] border border-[rgba(117,170,252,0.15)] p-3 font-mono text-sm text-[#dbe7ff] whitespace-pre-wrap overflow-x-auto">
              {task.plan}
            </pre>
          )}
        </div>

        {/* Progress Summary */}
        {task.progressSummary && (
          <div className="space-y-2">
            <div className="font-mono text-xs text-[#9bc3ff] tracking-wide uppercase">
              Progress
            </div>
            <div className="bg-[rgba(0,0,0,0.2)] border border-[rgba(117,170,252,0.15)] p-3 font-mono text-sm text-[#9bc3ff]">
              {task.progressSummary}
            </div>
          </div>
        )}

        {/* Last Output */}
        {task.lastOutput && (
          <div className="space-y-2">
            <div className="font-mono text-xs text-[#9bc3ff] tracking-wide uppercase">
              Last Output
            </div>
            <pre className="bg-[rgba(0,0,0,0.3)] border border-[rgba(117,170,252,0.15)] p-3 font-mono text-xs text-[#75aafc] whitespace-pre-wrap overflow-x-auto max-h-[200px] overflow-y-auto">
              {task.lastOutput}
            </pre>
          </div>
        )}

        {/* Error Message */}
        {task.errorMessage && (
          <div className="space-y-2">
            <div className="font-mono text-xs text-red-400 tracking-wide uppercase">
              Error
            </div>
            <div className="bg-red-400/5 border border-red-400/30 p-3 font-mono text-sm text-red-400">
              {task.errorMessage}
            </div>
          </div>
        )}

        {/* Repository Info */}
        {(task.repositoryUrl || task.baseBranch || task.targetBranch) && (
          <div className="space-y-2">
            <div className="font-mono text-xs text-[#9bc3ff] tracking-wide uppercase">
              Repository
            </div>
            <div className="bg-[rgba(0,0,0,0.2)] border border-[rgba(117,170,252,0.15)] p-3 space-y-1">
              {task.repositoryUrl && (
                <div className="font-mono text-xs text-[#75aafc]">
                  <span className="text-[#555]">URL:</span> {task.repositoryUrl}
                </div>
              )}
              {task.baseBranch && (
                <div className="font-mono text-xs text-[#75aafc]">
                  <span className="text-[#555]">Base:</span> {task.baseBranch}
                </div>
              )}
              {task.targetBranch && (
                <div className="font-mono text-xs text-[#75aafc]">
                  <span className="text-[#555]">Target:</span> {task.targetBranch}
                </div>
              )}
              {task.prUrl && (
                <div className="font-mono text-xs text-[#75aafc]">
                  <span className="text-[#555]">PR:</span>{" "}
                  <a
                    href={task.prUrl}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="text-[#9bc3ff] hover:underline"
                  >
                    {task.prUrl}
                  </a>
                </div>
              )}
            </div>
          </div>
        )}

        {/* Completion Action Settings */}
        <div className="space-y-2">
          <div className="font-mono text-xs text-[#9bc3ff] tracking-wide uppercase">
            Completion Actions
          </div>
          {isEditing ? (
            <div className="bg-[rgba(0,0,0,0.2)] border border-[rgba(117,170,252,0.15)] p-3 space-y-3">
              <div className="space-y-1">
                <label className="font-mono text-xs text-[#555]">Action on Completion</label>
                <select
                  value={editCompletionAction}
                  onChange={(e) => setEditCompletionAction(e.target.value as CompletionAction)}
                  className="w-full bg-[#0a1525] border border-[rgba(117,170,252,0.25)] text-[#dbe7ff] font-mono text-sm px-3 py-2 outline-none focus:border-[#3f6fb3]"
                >
                  <option value="none">None (keep in worktree)</option>
                  <option value="branch">Create branch in target repo</option>
                  <option value="merge">Auto-merge to target branch</option>
                  <option value="pr">Create Pull Request</option>
                </select>
              </div>
              {editCompletionAction !== "none" && (
                <div className="space-y-1">
                  <label className="font-mono text-xs text-[#555]">Target Repository Path</label>
                  <DirectoryInput
                    value={editTargetRepoPath}
                    onChange={setEditTargetRepoPath}
                    suggestions={suggestedDirectories}
                    placeholder="/path/to/your/local/repo"
                    repoUrl={task.repositoryUrl}
                  />
                  <p className="font-mono text-[10px] text-[#555]">
                    Path to your local repository where the branch will be pushed/merged.
                  </p>
                </div>
              )}
            </div>
          ) : (
            <div className="bg-[rgba(0,0,0,0.2)] border border-[rgba(117,170,252,0.15)] p-3 space-y-1">
              <div className="font-mono text-xs text-[#75aafc]">
                <span className="text-[#555]">Action:</span>{" "}
                {task.completionAction === "none" || !task.completionAction
                  ? "None (keep in worktree)"
                  : task.completionAction === "branch"
                  ? "Create branch in target repo"
                  : task.completionAction === "merge"
                  ? "Auto-merge to target branch"
                  : task.completionAction === "pr"
                  ? "Create Pull Request"
                  : task.completionAction}
              </div>
              {task.targetRepoPath && (
                <div className="font-mono text-xs text-[#75aafc]">
                  <span className="text-[#555]">Target Repo:</span> {task.targetRepoPath}
                </div>
              )}
            </div>
          )}
        </div>

        {/* Metadata Info */}
        {(task.daemonId || task.containerId || task.overlayPath) && (
          <div className="space-y-2">
            <div className="font-mono text-xs text-[#9bc3ff] tracking-wide uppercase">
              Metadata
            </div>
            <div className="bg-[rgba(0,0,0,0.2)] border border-[rgba(117,170,252,0.15)] p-3 space-y-1">
              {task.daemonId && (
                <div className="font-mono text-xs text-[#75aafc]">
                  <span className="text-[#555]">Daemon:</span> {task.daemonId}
                </div>
              )}
              {task.containerId && (
                <div className="font-mono text-xs text-[#75aafc]">
                  <span className="text-[#555]">Container:</span> {task.containerId}
                </div>
              )}
              {task.overlayPath && (
                <div className="font-mono text-xs text-[#75aafc]">
                  <span className="text-[#555]">Overlay:</span> {task.overlayPath}
                </div>
              )}
            </div>
          </div>
        )}

        {/* Subtasks / Contract Tasks (for supervisors) */}
        <div className="space-y-2">
          <div className="flex items-center justify-between">
            <div className="flex items-center gap-3">
              <div className="font-mono text-xs text-[#9bc3ff] tracking-wide uppercase">
                {isSupervisor ? `Contract Tasks (${displayTasks.length})` : `Subtasks (${displayTasks.length})`}
              </div>
              {displayTasks.length > 0 && (
                <button
                  onClick={() => setUseTreeView(!useTreeView)}
                  className="font-mono text-[9px] text-[#555] hover:text-[#75aafc]"
                >
                  {useTreeView ? "List" : "Tree"}
                </button>
              )}
            </div>
          </div>

          {/* Progress bar for tasks */}
          {displayTasks.length > 0 && (
            <SubtaskProgressBar stats={subtaskStats} />
          )}

          {displayTasks.length === 0 ? (
            <div className="text-[#555] font-mono text-xs py-4 text-center">
              {isSupervisor ? "No tasks in contract yet" : "No subtasks yet"}
            </div>
          ) : useTreeView ? (
            <div className="border border-[rgba(117,170,252,0.15)]">
              <SubtaskTree
                subtasks={displayTasks}
                onSelect={onSelectSubtask}
                fetchSubtasks={fetchSubtasks}
              />
            </div>
          ) : (
            <div className="divide-y divide-[rgba(117,170,252,0.15)] border border-[rgba(117,170,252,0.15)]">
              {displayTasks.map((subtask: TaskSummary) => {
                const isRunning = subtask.status === "running" || subtask.status === "initializing" || subtask.status === "starting";
                const isViewingOutput = viewingSubtaskId === subtask.id;
                const isExpanded = expandedSubtaskId === subtask.id;

                // Different highlight colors: green for running, subtle blue for others
                const outputHighlightBg = isRunning ? "bg-green-400/10" : "bg-[rgba(117,170,252,0.08)]";
                const outputHighlightBorder = isRunning ? "border-l-green-400" : "border-l-[#75aafc]";
                const outputLabelColor = isRunning ? "text-green-400" : "text-[#75aafc]";

                return (
                  <div key={subtask.id}>
                    {/* Subtask header - clickable to view output */}
                    <div
                      className={`w-full p-3 text-left hover:bg-[rgba(117,170,252,0.05)] transition-colors cursor-pointer ${
                        isExpanded && !isViewingOutput ? "bg-[rgba(117,170,252,0.08)]" : ""
                      } ${isViewingOutput ? `${outputHighlightBg} border-l-2 ${outputHighlightBorder}` : ""}`}
                      onClick={() => handleSubtaskClick(subtask)}
                    >
                      <div className="flex items-center gap-2 mb-1">
                        <span className="text-[#555] text-xs">
                          {isViewingOutput ? "[*]" : (isExpanded ? "[-]" : "[+]")}
                        </span>
                        <span className="font-mono text-sm text-[#dbe7ff]">
                          {subtask.name}
                        </span>
                        <span
                          className={`px-1.5 py-0.5 font-mono text-[9px] uppercase ${getStatusColor(
                            subtask.status
                          )} ${getStatusBgColor(subtask.status)} border border-current/20`}
                        >
                          {subtask.status}
                        </span>
                        {subtask.subtaskCount > 0 && (
                          <span className="font-mono text-[9px] text-[#555]">
                            +{subtask.subtaskCount}
                          </span>
                        )}
                        {isViewingOutput && (
                          <span className={`font-mono text-[9px] ${outputLabelColor} ml-auto`}>
                            {isRunning ? "viewing live output" : "viewing output"}
                          </span>
                        )}
                        {/* Expand/edit button - always available */}
                        <button
                          onClick={(e) => {
                            e.stopPropagation();
                            handleSubtaskToggle(subtask.id);
                          }}
                          className={`ml-auto px-1.5 py-0.5 font-mono text-[10px] text-[#75aafc] hover:text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors ${
                            isViewingOutput ? "ml-2" : ""
                          }`}
                          title="Expand details"
                        >
                          {isExpanded ? "-" : "+"}
                        </button>
                      </div>
                      {subtask.progressSummary && !isExpanded && !isViewingOutput && (
                        <p className="font-mono text-xs text-[#75aafc] line-clamp-1 pl-6">
                          {subtask.progressSummary}
                        </p>
                      )}
                    </div>
                    {/* Inline subtask editor */}
                    {isExpanded && (
                      <InlineSubtaskEditor
                        subtaskId={subtask.id}
                        onClose={() => setExpandedSubtaskId(null)}
                        onUpdated={handleSubtaskUpdated}
                        onNavigate={onSelectSubtask}
                      />
                    )}
                  </div>
                );
              })}
            </div>
          )}
        </div>

        {/* Action buttons for completed tasks */}
        {(task.status === "done" || task.status === "merged" || task.status === "failed") && (
          <div className="space-y-2 pt-4 border-t border-[rgba(117,170,252,0.2)]">
            <div className="flex flex-wrap gap-2">
              {onRequestDiff && (
                <button
                  onClick={() => {
                    onRequestDiff();
                    setShowDiff(true);
                  }}
                  className="px-3 py-1.5 font-mono text-xs text-[#75aafc] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors"
                >
                  View Diff
                </button>
              )}
              {canCreatePR && (
                <button
                  onClick={() => setShowPRPreview(true)}
                  className="px-3 py-1.5 font-mono text-xs text-green-400 border border-green-400/30 hover:border-green-400/50 hover:bg-green-400/10 transition-colors"
                >
                  Create PR
                </button>
              )}
              {/* Retry completion action button */}
              {canRetryCompletion && (
                <button
                  onClick={handleRetryCompletion}
                  disabled={isRetryingCompletion}
                  className="px-3 py-1.5 font-mono text-xs text-cyan-400 border border-cyan-400/30 hover:border-cyan-400/50 hover:bg-cyan-400/10 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
                >
                  {isRetryingCompletion
                    ? "Retrying..."
                    : task.completionAction === "branch"
                    ? "Push Branch"
                    : task.completionAction === "merge"
                    ? "Merge to Target"
                    : task.completionAction === "pr"
                    ? "Create PR"
                    : "Run Completion Action"}
                </button>
              )}
              {/* Show hint if completion action needs configuration */}
              {!canRetryCompletion && (
                <span className="px-3 py-1.5 font-mono text-xs text-[#555] italic">
                  {!task.completionAction || task.completionAction === "none"
                    ? "Set completion action to enable"
                    : !task.targetRepoPath
                    ? "Set target repo path to enable"
                    : ""}
                </span>
              )}
            </div>
            {/* Retry error message */}
            {retryError && (
              <div className="font-mono text-xs text-red-400 bg-red-400/10 px-3 py-2 border border-red-400/30">
                {retryError}
              </div>
            )}
          </div>
        )}

        {/* Clone Worktree Section */}
        {canCloneWorktree && (
          <div className="bg-[rgba(0,0,0,0.2)] border border-[rgba(117,170,252,0.15)] p-3 space-y-2">
            <div className="font-mono text-xs text-[#555]">Clone Worktree to Directory</div>
            <div className="flex gap-2 items-start">
              <DirectoryInput
                value={cloneTargetDir}
                onChange={setCloneTargetDir}
                suggestions={suggestedDirectories}
                placeholder="/path/to/clone"
                repoUrl={task.repositoryUrl}
                className="flex-1"
              />
              <button
                onClick={handleCloneWorktree}
                disabled={isCloning || !cloneTargetDir.trim()}
                className="px-3 py-2 font-mono text-xs text-purple-400 border border-purple-400/30 hover:border-purple-400/50 hover:bg-purple-400/10 transition-colors disabled:opacity-50 disabled:cursor-not-allowed shrink-0"
              >
                {isCloning ? "Cloning..." : "Clone"}
              </button>
            </div>
            <p className="font-mono text-[10px] text-[#555]">
              Clone the worktree (git repo) to a new directory. Useful for moving completed work outside ~/.makima.
            </p>
            {cloneError && (
              <div className="font-mono text-xs text-red-400 bg-red-400/10 px-3 py-2 border border-red-400/30">
                {cloneError}
              </div>
            )}
          </div>
        )}
      </div>

      {/* Overlay Diff Modal */}
      {showDiff && overlayDiff !== undefined && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
          <div className="max-w-4xl w-full max-h-[80vh]">
            <OverlayDiffViewer
              diff={overlayDiff}
              changedFiles={changedFiles}
              onClose={() => setShowDiff(false)}
              title={`Changes in ${task.name}`}
            />
          </div>
        </div>
      )}

      {/* PR Preview Modal */}
      {showPRPreview && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
          <div className="max-w-3xl w-full">
            <PRPreview
              task={task}
              diff={overlayDiff}
              changedFiles={changedFiles}
              onCreatePR={onCreatePR}
              onAutoMerge={task.mergeMode === "auto" ? onAutoMerge : undefined}
              onClose={() => setShowPRPreview(false)}
            />
          </div>
        </div>
      )}

      {/* Branch Task Modal */}
      {showBranchModal && onBranch && (
        <BranchTaskModal
          task={task}
          onBranch={onBranch}
          onClose={() => setShowBranchModal(false)}
        />
      )}
    </div>
  );
}