summaryrefslogblamecommitdiff
path: root/makima/frontend/src/routes/contracts.tsx
blob: bb662155b558b180a3ee976b6f895472cd8e66fa (plain) (tree)
1
2
3
4
5
6
7
8
                                                         






                                                                        





                           

                        
                  

                 
               


                        
                         
                       
























































                                                                                           

                                                                           





                                                                                          

                                                                                       

                                                                                 
                                                    

                                                              
 
                                                                                      


                                    









                                                                                         
                                                                                                                       



                                                                                     
                                                                                                                  









                                                                              

                         





                                                                                       



                                                                

                                                         















                                                                                                                                              












                                                                                         



                                         





























                                                                                   






















































                                                                



                                                                                                                


                                                              
                                 
                                                                                    
                                        

                                                                                               

































                                                                     

                                  



                              
                            







                                                                                       
                 
                  
                 















                                                

                              



                          
                        

                             































































































































                                                                                        


                                                                    
      
                              

































                                                                              










































                                                                                          





















































                                                                                                                                                                            




                                                                                           






























                                                                                                              

                      









                                                                                                                                                                










                                                                                                                              

                                                                       
                                              

                                                                                              


                      







































                                                                                                      




















































                                                                                                                                                                                     










































                                                                                                        




















                                                                                                                                                                             
                                                                                                      






                                   

















































































                                                                                                                                                                                                    




                                                          









































                                                                                                                                                            
import { useState, useCallback, useEffect } from "react";
import { useParams, useNavigate } from "react-router";
import { Masthead } from "../components/Masthead";
import { ContractList } from "../components/contracts/ContractList";
import { ContractDetail } from "../components/contracts/ContractDetail";
import { DirectoryInput } from "../components/mesh/DirectoryInput";
import { useContracts } from "../hooks/useContracts";
import { useAuth } from "../contexts/AuthContext";
import {
  createTask,
  getDaemonDirectories,
  getRepositorySuggestions,
  listContractTypes,
} from "../lib/api";
import type {
  ContractWithRelations,
  ContractSummary,
  ContractPhase,
  ContractStatus,
  ContractType,
  CreateContractRequest,
  RepositorySourceType,
  DaemonDirectory,
  RepositoryHistoryEntry,
  ContractTypeTemplate,
} from "../lib/api";

export default function ContractsPage() {
  const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth();
  const navigate = useNavigate();

  // Redirect to login if not authenticated (when auth is configured)
  useEffect(() => {
    if (!authLoading && isAuthConfigured && !isAuthenticated) {
      navigate("/login");
    }
  }, [authLoading, isAuthConfigured, isAuthenticated, navigate]);

  // Show loading while checking auth
  if (authLoading) {
    return (
      <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]">
        <Masthead showNav />
        <main className="flex-1 flex items-center justify-center">
          <p className="text-[#7788aa] font-mono text-sm">Loading...</p>
        </main>
      </div>
    );
  }

  // Don't render if not authenticated (will redirect)
  if (isAuthConfigured && !isAuthenticated) {
    return null;
  }

  return <ContractsPageContent />;
}

function ContractsPageContent() {
  const { id } = useParams<{ id: string }>();
  const navigate = useNavigate();
  const {
    contracts,
    loading,
    error,
    fetchContract,
    saveContract,
    editContract,
    removeContract,
    changePhase,
    addRemoteRepo,
    addLocalRepo,
    createManagedRepo,
    removeRepo,
    setRepoPrimary,
  } = useContracts();

  const [contractDetail, setContractDetail] = useState<ContractWithRelations | null>(null);
  const [detailLoading, setDetailLoading] = useState(false);
  const [isCreating, setIsCreating] = useState(false);
  const [newContractName, setNewContractName] = useState("");
  const [newContractDescription, setNewContractDescription] = useState("");
  const [contractType, setContractType] = useState<ContractType>("simple");
  const [initialPhase, setInitialPhase] = useState<ContractPhase>("plan");
  const [repoType, setRepoType] = useState<RepositorySourceType>("remote");
  const [repoName, setRepoName] = useState("");
  const [repoUrl, setRepoUrl] = useState("");
  const [repoPath, setRepoPath] = useState("");
  const [createError, setCreateError] = useState<string | null>(null);
  const [suggestedDirectories, setSuggestedDirectories] = useState<DaemonDirectory[]>([]);
  const [repoSuggestions, setRepoSuggestions] = useState<RepositoryHistoryEntry[]>([]);
  const [showRepoSuggestions, setShowRepoSuggestions] = useState(false);
  const [contractTypes, setContractTypes] = useState<ContractTypeTemplate[]>([]);
  const [contractTypesLoading, setContractTypesLoading] = useState(false);
  const [localOnly, setLocalOnly] = useState(false);
  const [redTeamEnabled, setRedTeamEnabled] = useState(false);
  const [redTeamPrompt, setRedTeamPrompt] = useState("");

  // Fetch contract types when modal opens - merges built-in types with user templates
  useEffect(() => {
    if (isCreating) {
      setContractTypesLoading(true);

      // Load user templates from localStorage
      const loadUserTemplates = (): ContractTypeTemplate[] => {
        try {
          const saved = localStorage.getItem("makima_contract_templates");
          if (saved) {
            const templates = JSON.parse(saved);
            // Convert user templates to ContractTypeTemplate format, excluding built-ins
            return templates
              .filter((t: { isBuiltIn?: boolean }) => !t.isBuiltIn)
              .map((t: { id: string; name: string; description: string; phases: { id: string; name: string }[] }) => ({
                id: t.id,
                name: t.name,
                description: t.description,
                phases: t.phases.map((p: { id: string }) => p.id) as ContractPhase[],
                phaseNames: Object.fromEntries(t.phases.map((p: { id: string; name: string }) => [p.id, p.name])),
                defaultPhase: (t.phases[0]?.id || "execute") as ContractPhase,
                isBuiltin: false,
              }));
          }
        } catch {
          // Ignore localStorage errors
        }
        return [];
      };

      listContractTypes()
        .then((res) => {
          // Merge built-in types from API with user templates from localStorage
          const userTemplates = loadUserTemplates();
          // Filter out any user templates that have the same ID as built-in types
          const builtinIds = new Set(res.contractTypes.map(t => t.id));
          const uniqueUserTemplates = userTemplates.filter(t => !builtinIds.has(t.id));
          setContractTypes([...res.contractTypes, ...uniqueUserTemplates]);
          setContractTypesLoading(false);
        })
        .catch((err) => {
          console.error("Failed to fetch contract types:", err);
          // Fall back to built-in types + user templates
          const builtinTypes: ContractTypeTemplate[] = [
            {
              id: "simple",
              name: "Simple",
              description: "Plan \u2192 Execute: Simple workflow with a plan document",
              phases: ["plan", "execute"],
              defaultPhase: "plan",
              isBuiltin: true,
            },
            {
              id: "specification",
              name: "Specification",
              description: "Research \u2192 Specify \u2192 Plan \u2192 Execute \u2192 Review: Full specification-driven development with TDD",
              phases: ["research", "specify", "plan", "execute", "review"],
              defaultPhase: "research",
              isBuiltin: true,
            },
            {
              id: "execute",
              name: "Execute",
              description: "Execute only: Minimal workflow for immediate task execution",
              phases: ["execute"],
              defaultPhase: "execute",
              isBuiltin: true,
            },
          ];
          const userTemplates = loadUserTemplates();
          const builtinIds = new Set(builtinTypes.map(t => t.id));
          const uniqueUserTemplates = userTemplates.filter(t => !builtinIds.has(t.id));
          setContractTypes([...builtinTypes, ...uniqueUserTemplates]);
          setContractTypesLoading(false);
        });
    }
  }, [isCreating]);

  // Fetch repository suggestions when modal opens and repo type changes
  useEffect(() => {
    if (isCreating && (repoType === "remote" || repoType === "local")) {
      getRepositorySuggestions(repoType, undefined, 10)
        .then((res) => {
          setRepoSuggestions(res.entries);
          setShowRepoSuggestions(res.entries.length > 0);
        })
        .catch(() => {
          setRepoSuggestions([]);
          setShowRepoSuggestions(false);
        });
    } else {
      setRepoSuggestions([]);
      setShowRepoSuggestions(false);
    }
  }, [isCreating, repoType]);

  // Apply a repository suggestion
  const applyRepoSuggestion = useCallback((suggestion: RepositoryHistoryEntry) => {
    setRepoName(suggestion.name);
    if (suggestion.repositoryUrl) {
      setRepoUrl(suggestion.repositoryUrl);
    }
    if (suggestion.localPath) {
      setRepoPath(suggestion.localPath);
    }
    setShowRepoSuggestions(false);
  }, []);

  // Fetch daemon directories when "local" repo type is selected
  useEffect(() => {
    if (repoType === "local" && isCreating) {
      getDaemonDirectories()
        .then((res) => setSuggestedDirectories(res.directories))
        .catch(() => setSuggestedDirectories([]));
    }
  }, [repoType, isCreating]);

  // Load contract detail when ID changes
  useEffect(() => {
    if (id) {
      setDetailLoading(true);
      fetchContract(id).then((contract) => {
        setContractDetail(contract);
        setDetailLoading(false);
      });
    } else {
      setContractDetail(null);
    }
  }, [id, fetchContract]);

  const handleSelect = useCallback(
    (contractId: string) => {
      navigate(`/contracts/${contractId}`);
    },
    [navigate]
  );

  const handleBack = useCallback(() => {
    navigate("/contracts");
  }, [navigate]);

  const handleCreate = useCallback(() => {
    setIsCreating(true);
  }, []);

  // Validate repository configuration
  const isRepoValid = useCallback(() => {
    if (!repoName.trim()) return false;
    if (repoType === "remote" && !repoUrl.trim()) return false;
    if (repoType === "local" && !repoPath.trim()) return false;
    return true;
  }, [repoType, repoName, repoUrl, repoPath]);

  const handleCreateSubmit = useCallback(async () => {
    if (!newContractName.trim()) return;
    if (!isRepoValid()) {
      setCreateError("Repository configuration is required");
      return;
    }

    setCreateError(null);

    // Get default phase from contract types or fall back to static function
    const selectedType = contractTypes.find((t) => t.id === contractType);
    const defaultPhaseForType = selectedType?.defaultPhase || (contractType === "simple" ? "plan" : "research");

    const data: CreateContractRequest = {
      name: newContractName.trim(),
      description: newContractDescription.trim() || undefined,
      contractType: contractType,
      initialPhase: initialPhase !== defaultPhaseForType ? initialPhase : undefined,
      localOnly: localOnly || undefined,
      redTeamEnabled: redTeamEnabled || undefined,
      redTeamPrompt: redTeamEnabled && redTeamPrompt.trim() ? redTeamPrompt.trim() : undefined,
    };

    try {
      const contract = await saveContract(data);
      if (contract) {
        // Add the repository after contract creation
        try {
          if (repoType === "remote") {
            await addRemoteRepo(contract.id, {
              name: repoName.trim(),
              repositoryUrl: repoUrl.trim(),
              isPrimary: true,
            });
          } else if (repoType === "local") {
            await addLocalRepo(contract.id, {
              name: repoName.trim(),
              localPath: repoPath.trim(),
              isPrimary: true,
            });
          } else if (repoType === "managed") {
            await createManagedRepo(contract.id, {
              name: repoName.trim(),
              isPrimary: true,
            });
          }
        } catch (repoError) {
          console.error("Failed to add repository:", repoError);
          // Still navigate to the contract - repo can be added later
        }

        // Clear form state
        setIsCreating(false);
        setNewContractName("");
        setNewContractDescription("");
        setContractType("simple");
        setInitialPhase("plan");
        setRepoType("remote");
        setRepoName("");
        setRepoUrl("");
        setRepoPath("");
        setLocalOnly(false);
        navigate(`/contracts/${contract.id}`);
      }
    } catch (err) {
      setCreateError(err instanceof Error ? err.message : "Failed to create contract");
    }
  }, [
    newContractName,
    newContractDescription,
    contractType,
    contractTypes,
    initialPhase,
    repoType,
    repoName,
    repoUrl,
    repoPath,
    isRepoValid,
    saveContract,
    addRemoteRepo,
    addLocalRepo,
    createManagedRepo,
    navigate,
  ]);

  const handleCreateCancel = useCallback(() => {
    setIsCreating(false);
    setNewContractName("");
    setNewContractDescription("");
    setContractType("simple");
    setInitialPhase("plan");
    setRepoType("remote");
    setRepoName("");
    setRepoUrl("");
    setRepoPath("");
    setLocalOnly(false);
    setRedTeamEnabled(false);
    setRedTeamPrompt("");
    setCreateError(null);
  }, []);

  const handleUpdate = useCallback(
    async (name: string, description: string) => {
      if (contractDetail) {
        const updated = await editContract(contractDetail.id, {
          name,
          description: description || undefined,
          version: contractDetail.version,
        });
        if (updated) {
          // Refresh detail
          const refreshed = await fetchContract(contractDetail.id);
          setContractDetail(refreshed);
        }
      }
    },
    [contractDetail, editContract, fetchContract]
  );

  const handleDelete = useCallback(async () => {
    if (contractDetail && confirm("Are you sure you want to delete this contract?")) {
      const success = await removeContract(contractDetail.id);
      if (success) {
        navigate("/contracts");
      }
    }
  }, [contractDetail, removeContract, navigate]);

  const handlePhaseChange = useCallback(
    async (phase: ContractPhase) => {
      if (contractDetail) {
        const updated = await changePhase(contractDetail.id, phase);
        if (updated) {
          // Refresh detail
          const refreshed = await fetchContract(contractDetail.id);
          setContractDetail(refreshed);
        }
      }
    },
    [contractDetail, changePhase, fetchContract]
  );

  const handleStatusChange = useCallback(
    async (status: ContractStatus) => {
      if (contractDetail) {
        const updated = await editContract(contractDetail.id, {
          status,
          version: contractDetail.version,
        });
        if (updated) {
          // Refresh detail
          const refreshed = await fetchContract(contractDetail.id);
          setContractDetail(refreshed);
        }
      }
    },
    [contractDetail, editContract, fetchContract]
  );

  // Repository handlers
  const handleAddRemoteRepo = useCallback(
    async (name: string, url: string, isPrimary: boolean) => {
      if (contractDetail) {
        await addRemoteRepo(contractDetail.id, { name, repositoryUrl: url, isPrimary });
        const refreshed = await fetchContract(contractDetail.id);
        setContractDetail(refreshed);
      }
    },
    [contractDetail, addRemoteRepo, fetchContract]
  );

  const handleAddLocalRepo = useCallback(
    async (name: string, path: string, isPrimary: boolean) => {
      if (contractDetail) {
        await addLocalRepo(contractDetail.id, { name, localPath: path, isPrimary });
        const refreshed = await fetchContract(contractDetail.id);
        setContractDetail(refreshed);
      }
    },
    [contractDetail, addLocalRepo, fetchContract]
  );

  const handleCreateManagedRepo = useCallback(
    async (name: string, isPrimary: boolean) => {
      if (contractDetail) {
        await createManagedRepo(contractDetail.id, { name, isPrimary });
        const refreshed = await fetchContract(contractDetail.id);
        setContractDetail(refreshed);
      }
    },
    [contractDetail, createManagedRepo, fetchContract]
  );

  const handleDeleteRepo = useCallback(
    async (repoId: string) => {
      if (contractDetail) {
        await removeRepo(contractDetail.id, repoId);
        const refreshed = await fetchContract(contractDetail.id);
        setContractDetail(refreshed);
      }
    },
    [contractDetail, removeRepo, fetchContract]
  );

  const handleSetRepoPrimary = useCallback(
    async (repoId: string) => {
      if (contractDetail) {
        await setRepoPrimary(contractDetail.id, repoId);
        const refreshed = await fetchContract(contractDetail.id);
        setContractDetail(refreshed);
      }
    },
    [contractDetail, setRepoPrimary, fetchContract]
  );

  // Refresh contract detail (used after file/task operations)
  const handleRefresh = useCallback(async () => {
    if (contractDetail) {
      const refreshed = await fetchContract(contractDetail.id);
      setContractDetail(refreshed);
    }
  }, [contractDetail, fetchContract]);

  // File/task navigation handlers
  const handleFileSelect = useCallback(
    (fileId: string) => {
      if (contractDetail) {
        navigate(`/contracts/${contractDetail.id}/files/${fileId}`);
      }
    },
    [navigate, contractDetail]
  );

  const handleTaskSelect = useCallback(
    (taskId: string) => {
      navigate(`/mesh/${taskId}`);
    },
    [navigate]
  );

  // Create task within contract context
  const handleTaskCreate = useCallback(
    async (name: string, plan: string, repositoryUrl?: string) => {
      if (!contractDetail) return;
      try {
        // Create the task with contract_id (task is automatically associated)
        const task = await createTask({
          contractId: contractDetail.id,
          name,
          plan,
          repositoryUrl,
        });
        // Refresh contract detail to show new task
        const refreshed = await fetchContract(contractDetail.id);
        setContractDetail(refreshed);
        // Navigate to the new task
        navigate(`/mesh/${task.id}`);
      } catch (e) {
        console.error("Failed to create task:", e);
        alert(e instanceof Error ? e.message : "Failed to create task");
      }
    },
    [contractDetail, fetchContract, navigate]
  );

  // Context menu handlers for ContractList
  const handleContextMarkComplete = useCallback(
    async (contract: ContractSummary) => {
      await editContract(contract.id, { status: "completed", version: contract.version });
    },
    [editContract]
  );

  const handleContextMarkActive = useCallback(
    async (contract: ContractSummary) => {
      await editContract(contract.id, { status: "active", version: contract.version });
    },
    [editContract]
  );

  const handleContextArchive = useCallback(
    async (contract: ContractSummary) => {
      await editContract(contract.id, { status: "archived", version: contract.version });
    },
    [editContract]
  );

  const handleContextDelete = useCallback(
    async (contract: ContractSummary) => {
      if (confirm(`Are you sure you want to delete "${contract.name}"?`)) {
        const success = await removeContract(contract.id);
        if (success && contract.id === id) {
          navigate("/contracts");
        }
      }
    },
    [removeContract, id, navigate]
  );

  const handleContextGoToSupervisor = useCallback(
    (contract: ContractSummary) => {
      if (contract.supervisorTaskId) {
        navigate(`/mesh/${contract.supervisorTaskId}`);
      }
    },
    [navigate]
  );

  return (
    <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]">
      <Masthead showNav />
      <main className="flex-1 flex flex-col p-4 pt-2 gap-4 overflow-hidden">
        {error && (
          <div className="p-3 bg-red-400/10 border border-red-400/30 text-red-400 font-mono text-sm">
            {error}
          </div>
        )}

        {/* Create contract modal */}
        {isCreating && (
          <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
            <div className="w-full max-w-lg p-6 bg-[#0a1628] border border-[rgba(117,170,252,0.3)]">
              <h3 className="font-mono text-sm text-[#75aafc] uppercase mb-4">
                Create Contract
              </h3>

              {createError && (
                <div className="mb-4 p-3 bg-red-400/10 border border-red-400/30 text-red-400 font-mono text-xs">
                  {createError}
                </div>
              )}

              <div className="space-y-4">
                {/* Contract name */}
                <div>
                  <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
                    Contract Name
                  </label>
                  <input
                    type="text"
                    value={newContractName}
                    onChange={(e) => setNewContractName(e.target.value)}
                    placeholder="Contract name"
                    className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]"
                    autoFocus
                  />
                </div>

                {/* Description */}
                <div>
                  <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
                    Description (optional)
                  </label>
                  <textarea
                    value={newContractDescription}
                    onChange={(e) => setNewContractDescription(e.target.value)}
                    placeholder="Describe what this contract is for..."
                    rows={2}
                    className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc] resize-none"
                  />
                </div>

                {/* Contract Type */}
                <div>
                  <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
                    Contract Type
                  </label>
                  {contractTypesLoading ? (
                    <div className="flex items-center justify-center py-4">
                      <span className="font-mono text-xs text-[#8b949e]">Loading contract types...</span>
                    </div>
                  ) : (
                    <>
                      <div className="flex gap-2">
                        {contractTypes.map((type) => (
                          <button
                            key={type.id}
                            type="button"
                            onClick={() => {
                              setContractType(type.id as ContractType);
                              setInitialPhase(type.defaultPhase as ContractPhase);
                            }}
                            className={`flex-1 px-3 py-2 font-mono text-xs uppercase transition-colors ${
                              contractType === type.id
                                ? "bg-[#0f3c78] text-[#dbe7ff] border border-[#75aafc]"
                                : "bg-[#0d1b2d] text-[#8b949e] border border-[#3f6fb3] hover:border-[#75aafc]"
                            }`}
                          >
                            {type.name}
                          </button>
                        ))}
                      </div>
                      <p className="mt-1 font-mono text-xs text-[#8b949e]">
                        {contractTypes.find((t) => t.id === contractType)?.description ||
                          "Select a contract type"}
                      </p>
                    </>
                  )}
                </div>

                {/* Starting Phase */}
                <div>
                  <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
                    Starting Phase
                  </label>
                  <select
                    value={initialPhase}
                    onChange={(e) => setInitialPhase(e.target.value as ContractPhase)}
                    className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]"
                  >
                    {(() => {
                      const template = contractTypes.find((t) => t.id === contractType);
                      return (template?.phases || []).map((phase) => {
                        const displayName = template?.phaseNames?.[phase] || (phase.charAt(0).toUpperCase() + phase.slice(1));
                        return (
                          <option key={phase} value={phase}>
                            {displayName}
                          </option>
                        );
                      });
                    })()}
                  </select>
                  <p className="mt-1 font-mono text-xs text-[#8b949e]">
                    {contractType === "simple"
                      ? "Start in Plan to define what to build, or Execute if already planned"
                      : "Skip earlier phases if you already have requirements defined"}
                  </p>
                </div>

                {/* Local-Only Mode */}
                <div className="space-y-2">
                  <div className="flex items-center space-x-3">
                    <button
                      type="button"
                      onClick={() => setLocalOnly(!localOnly)}
                      className={`w-5 h-5 flex items-center justify-center border transition-colors ${
                        localOnly
                          ? "bg-[#0f3c78] border-[#75aafc] text-[#dbe7ff]"
                          : "bg-[#0d1b2d] border-[#3f6fb3] text-transparent"
                      }`}
                    >
                      {localOnly && (
                        <svg
                          xmlns="http://www.w3.org/2000/svg"
                          viewBox="0 0 24 24"
                          fill="none"
                          stroke="currentColor"
                          strokeWidth="3"
                          strokeLinecap="round"
                          strokeLinejoin="round"
                          className="w-3 h-3"
                        >
                          <polyline points="20 6 9 17 4 12" />
                        </svg>
                      )}
                    </button>
                    <label
                      className="font-mono text-sm text-[#dbe7ff] cursor-pointer select-none"
                      onClick={() => setLocalOnly(!localOnly)}
                    >
                      Local-Only Mode
                    </label>
                  </div>
                  <p className="font-mono text-xs text-[#8b949e] pl-8">
                    When enabled, tasks won't automatically push to remote or create PRs.
                    Use patch files to export changes.
                  </p>
                </div>

                {/* Red Team Monitoring */}
                <div className="border-t border-[rgba(117,170,252,0.2)] pt-4">
                  <div className="flex items-center gap-3">
                    <button
                      type="button"
                      onClick={() => setRedTeamEnabled(!redTeamEnabled)}
                      className={`w-5 h-5 flex items-center justify-center border transition-colors ${
                        redTeamEnabled
                          ? "bg-[#0f3c78] border-[#75aafc] text-[#dbe7ff]"
                          : "bg-[#0d1b2d] border-[#3f6fb3] text-transparent"
                      }`}
                    >
                      {redTeamEnabled && (
                        <svg
                          xmlns="http://www.w3.org/2000/svg"
                          width="12"
                          height="12"
                          viewBox="0 0 24 24"
                          fill="none"
                          stroke="currentColor"
                          strokeWidth="3"
                          strokeLinecap="round"
                          strokeLinejoin="round"
                        >
                          <polyline points="20 6 9 17 4 12" />
                        </svg>
                      )}
                    </button>
                    <label
                      className="font-mono text-sm text-[#dbe7ff] cursor-pointer select-none"
                      onClick={() => setRedTeamEnabled(!redTeamEnabled)}
                    >
                      Enable Red Team Monitoring
                    </label>
                  </div>
                  <p className="font-mono text-xs text-[#8b949e] pl-8">
                    Spawns a parallel task to monitor work output for quality and compliance.
                  </p>
                  {redTeamEnabled && (
                    <div className="mt-3 pl-8">
                      <label className="block font-mono text-xs text-[#75aafc] uppercase mb-2">
                        Custom Review Criteria (Optional)
                      </label>
                      <textarea
                        value={redTeamPrompt}
                        onChange={(e) => setRedTeamPrompt(e.target.value)}
                        placeholder="e.g., 'Focus on security best practices' or 'Ensure all functions have tests'"
                        className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] text-sm font-mono h-20 resize-none focus:border-[#75aafc] focus:outline-none"
                      />
                    </div>
                  )}
                </div>

                {/* Repository Configuration */}
                <div className="border-t border-[rgba(117,170,252,0.2)] pt-4">
                  <label className="block font-mono text-xs text-[#75aafc] uppercase mb-3">
                    Repository Configuration (Required)
                  </label>

                  {/* Repository type selector */}
                  <div className="flex gap-2 mb-3">
                    <button
                      type="button"
                      onClick={() => setRepoType("remote")}
                      className={`flex-1 px-3 py-2 font-mono text-xs uppercase transition-colors ${
                        repoType === "remote"
                          ? "bg-[#0f3c78] text-[#dbe7ff] border border-[#75aafc]"
                          : "bg-[#0d1b2d] text-[#8b949e] border border-[#3f6fb3] hover:border-[#75aafc]"
                      }`}
                    >
                      Remote
                    </button>
                    <button
                      type="button"
                      onClick={() => setRepoType("local")}
                      className={`flex-1 px-3 py-2 font-mono text-xs uppercase transition-colors ${
                        repoType === "local"
                          ? "bg-[#0f3c78] text-[#dbe7ff] border border-[#75aafc]"
                          : "bg-[#0d1b2d] text-[#8b949e] border border-[#3f6fb3] hover:border-[#75aafc]"
                      }`}
                    >
                      Local
                    </button>
                    <button
                      type="button"
                      onClick={() => setRepoType("managed")}
                      className={`flex-1 px-3 py-2 font-mono text-xs uppercase transition-colors ${
                        repoType === "managed"
                          ? "bg-[#0f3c78] text-[#dbe7ff] border border-[#75aafc]"
                          : "bg-[#0d1b2d] text-[#8b949e] border border-[#3f6fb3] hover:border-[#75aafc]"
                      }`}
                    >
                      Managed
                    </button>
                  </div>

                  {/* Repository suggestions */}
                  {showRepoSuggestions && repoSuggestions.length > 0 && (
                    <div className="mb-3">
                      <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
                        Recent Repositories
                      </label>
                      <div className="border border-[rgba(117,170,252,0.2)] bg-[#0a1525] max-h-32 overflow-y-auto">
                        {repoSuggestions.map((suggestion) => (
                          <button
                            key={suggestion.id}
                            type="button"
                            onClick={() => applyRepoSuggestion(suggestion)}
                            className="w-full text-left px-3 py-2 font-mono text-xs hover:bg-[rgba(117,170,252,0.1)] border-b border-[rgba(117,170,252,0.1)] last:border-b-0"
                          >
                            <div className="flex items-center justify-between">
                              <span className="text-[#9bc3ff] truncate">{suggestion.name}</span>
                              <span className="text-[10px] text-[#556677] ml-2">
                                {suggestion.useCount}×
                              </span>
                            </div>
                            <div className="text-[10px] text-[#556677] truncate">
                              {repoType === "local" ? suggestion.localPath : suggestion.repositoryUrl}
                            </div>
                          </button>
                        ))}
                      </div>
                    </div>
                  )}

                  {/* Repository name */}
                  <div className="mb-3">
                    <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
                      Repository Name
                    </label>
                    <input
                      type="text"
                      value={repoName}
                      onChange={(e) => setRepoName(e.target.value)}
                      placeholder="e.g., my-project"
                      className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]"
                    />
                  </div>

                  {/* Repository URL (for remote) */}
                  {repoType === "remote" && (
                    <div>
                      <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
                        Repository URL
                      </label>
                      <input
                        type="text"
                        value={repoUrl}
                        onChange={(e) => setRepoUrl(e.target.value)}
                        placeholder="https://github.com/user/repo.git"
                        className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]"
                      />
                    </div>
                  )}

                  {/* Repository path (for local) */}
                  {repoType === "local" && (
                    <div>
                      <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
                        Local Path
                      </label>
                      <DirectoryInput
                        value={repoPath}
                        onChange={setRepoPath}
                        suggestions={suggestedDirectories}
                        placeholder="/path/to/repository"
                      />
                    </div>
                  )}

                  {/* Managed description */}
                  {repoType === "managed" && (
                    <p className="font-mono text-xs text-[#8b949e]">
                      A managed repository will be created automatically by the daemon.
                    </p>
                  )}
                </div>

                {/* Actions */}
                <div className="flex gap-2 justify-end pt-2">
                  <button
                    onClick={handleCreateCancel}
                    className="px-4 py-2 font-mono text-xs text-[#9bc3ff] hover:text-[#dbe7ff] transition-colors"
                  >
                    Cancel
                  </button>
                  <button
                    onClick={handleCreateSubmit}
                    disabled={!newContractName.trim() || !isRepoValid()}
                    className="px-4 py-2 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
                  >
                    Create
                  </button>
                </div>
              </div>
            </div>
          </div>
        )}

        <div className="flex-1 grid grid-cols-[350px_1fr] gap-4 min-h-0">
          {/* Contract list */}
          <ContractList
            contracts={contracts}
            loading={loading}
            onSelect={handleSelect}
            onCreate={handleCreate}
            selectedId={id}
            onMarkComplete={handleContextMarkComplete}
            onMarkActive={handleContextMarkActive}
            onArchive={handleContextArchive}
            onDelete={handleContextDelete}
            onGoToSupervisor={handleContextGoToSupervisor}
          />

          {/* Contract detail or empty state */}
          {contractDetail ? (
            <ContractDetail
              contract={contractDetail}
              loading={detailLoading}
              onBack={handleBack}
              onUpdate={handleUpdate}
              onDelete={handleDelete}
              onPhaseChange={handlePhaseChange}
              onStatusChange={handleStatusChange}
              onFileSelect={handleFileSelect}
              onTaskSelect={handleTaskSelect}
              onTaskCreate={handleTaskCreate}
              onRefresh={handleRefresh}
              onAddRemoteRepo={handleAddRemoteRepo}
              onAddLocalRepo={handleAddLocalRepo}
              onCreateManagedRepo={handleCreateManagedRepo}
              onDeleteRepo={handleDeleteRepo}
              onSetRepoPrimary={handleSetRepoPrimary}
            />
          ) : (
            <div className="panel h-full flex items-center justify-center">
              <div className="text-center">
                <p className="font-mono text-sm text-[#555] mb-4">
                  Select a contract or create a new one
                </p>
                <button
                  onClick={handleCreate}
                  className="px-4 py-2 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase"
                >
                  + New Contract
                </button>
              </div>
            </div>
          )}
        </div>
      </main>
    </div>
  );
}