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);
// 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 }[] }) => ({
id: t.id,
name: t.name,
description: t.description,
phases: t.phases.map((p: { id: string }) => p.id) as ContractPhase[],
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,
};
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);
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]"
>
{(contractTypes.find((t) => t.id === contractType)?.phases || []).map((phase) => (
<option key={phase} value={phase}>
{phase.charAt(0).toUpperCase() + phase.slice(1)}
</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>
{/* 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>
);
}