Move src/ → apps/server/ and packages/web/ → apps/web/ to adopt standard monorepo conventions (apps/ for runnable apps, packages/ for reusable libraries). Update all config files, shared package imports, test fixtures, and documentation to reflect new paths. Key fixes: - Update workspace config to ["apps/*", "packages/*"] - Update tsconfig.json rootDir/include for apps/server/ - Add apps/web/** to vitest exclude list - Update drizzle.config.ts schema path - Fix ensure-schema.ts migration path detection (3 levels up in dev, 2 levels up in dist) - Fix tests/integration/cli-server.test.ts import paths - Update packages/shared imports to apps/server/ paths - Update all docs/ files with new paths
104 lines
2.9 KiB
TypeScript
104 lines
2.9 KiB
TypeScript
import { Link } from "@tanstack/react-router";
|
|
import { X } from "lucide-react";
|
|
import { StatusBadge } from "@/components/StatusBadge";
|
|
import { DependencyIndicator } from "@/components/DependencyIndicator";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
/** Task shape as returned by tRPC (Date fields serialized to string over JSON) */
|
|
export interface SerializedTask {
|
|
id: string;
|
|
phaseId: string | null;
|
|
initiativeId: string | null;
|
|
parentTaskId: string | null;
|
|
name: string;
|
|
description: string | null;
|
|
type: "auto" | "checkpoint:human-verify" | "checkpoint:decision" | "checkpoint:human-action";
|
|
category: string;
|
|
priority: "low" | "medium" | "high";
|
|
status: "pending_approval" | "pending" | "in_progress" | "completed" | "blocked";
|
|
requiresApproval: boolean | null;
|
|
order: number;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
interface TaskRowProps {
|
|
task: SerializedTask;
|
|
agentName: string | null;
|
|
blockedBy: Array<{ name: string; status: string }>;
|
|
isLast: boolean;
|
|
onClick: () => void;
|
|
onDelete?: () => void;
|
|
}
|
|
|
|
export function TaskRow({
|
|
task,
|
|
agentName,
|
|
blockedBy,
|
|
isLast,
|
|
onClick,
|
|
onDelete,
|
|
}: TaskRowProps) {
|
|
const connector = isLast ? "└──" : "├──";
|
|
|
|
return (
|
|
<div>
|
|
{/* Task row */}
|
|
<div
|
|
className={cn(
|
|
"group flex cursor-pointer items-center gap-2 rounded px-2 py-1 hover:bg-accent",
|
|
!isLast && "border-l-2 border-muted-foreground/20",
|
|
)}
|
|
onClick={onClick}
|
|
>
|
|
{/* Tree connector */}
|
|
<span className="shrink-0 font-mono text-sm text-muted-foreground">
|
|
{connector}
|
|
</span>
|
|
|
|
{/* Task name */}
|
|
<span className="min-w-0 flex-1 truncate text-sm">{task.name}</span>
|
|
|
|
{/* Agent assignment */}
|
|
{agentName && (
|
|
<Link
|
|
to="/inbox"
|
|
className="shrink-0 text-xs text-primary hover:underline"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
[{agentName}]
|
|
</Link>
|
|
)}
|
|
|
|
{/* Status badge */}
|
|
<StatusBadge status={task.status} className="shrink-0" />
|
|
|
|
{/* Delete button */}
|
|
{onDelete && (
|
|
<button
|
|
className="shrink-0 rounded p-0.5 text-muted-foreground opacity-0 transition-opacity hover:text-destructive group-hover:opacity-100"
|
|
title="Delete task (Shift+click to skip confirmation)"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
if (e.shiftKey || window.confirm(`Delete "${task.name}"?`)) {
|
|
onDelete();
|
|
}
|
|
}}
|
|
>
|
|
<X className="h-3.5 w-3.5" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{/* Dependency indicator below the row */}
|
|
{blockedBy.length > 0 && (
|
|
<DependencyIndicator
|
|
blockedBy={blockedBy}
|
|
type="task"
|
|
className="ml-6"
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|