"use client"; import { AlertTriangle, CheckCircle2, ChevronRight, Circle, Play, } from "lucide-react"; import { getLiveWorkspaceIndex, type RiskLevel, useSFWorkspaceState, } from "@/lib/sf-workspace-store"; import { cn } from "@/lib/utils"; import { getMilestoneStatus, getSliceStatus, type ItemStatus, } from "@/lib/workspace-status"; const StatusIcon = ({ status, size = "default", }: { status: ItemStatus; size?: "default" | "large"; }) => { const sizeClass = size === "large" ? "h-5 w-5" : "h-4 w-4"; if (status === "done") { return ; } if (status === "in-progress") { return ; } return ; }; const RiskBadge = ({ risk }: { risk: RiskLevel }) => { return ( {risk === "high" && } {risk} ); }; export function Roadmap() { const workspace = useSFWorkspaceState(); const liveWorkspace = getLiveWorkspaceIndex(workspace); const milestones = liveWorkspace?.milestones ?? []; const activeScope = liveWorkspace?.active ?? {}; const workspaceFreshness = workspace.live.freshness.workspace.stale ? "stale" : workspace.live.freshness.workspace.status; return (

Roadmap

Project milestone structure with slices and dependencies

Workspace freshness: {workspaceFreshness}

{workspace.bootStatus === "loading" && (
Loading workspace…
)} {workspace.bootStatus === "ready" && milestones.length === 0 && (
No milestones found. Create a milestone with{" "} /sf {" "} to get started.
)}
{milestones.map((milestone) => { const milestoneStatus = getMilestoneStatus(milestone, activeScope); const doneSlices = milestone.slices.filter((s) => s.done).length; const totalTasks = milestone.slices.reduce( (acc, s) => acc + s.tasks.length, 0, ); const doneTasks = milestone.slices.reduce( (acc, s) => acc + s.tasks.filter((t) => t.done).length, 0, ); return (
{milestone.id} {milestone.title}
{doneSlices}/{milestone.slices.length} slices
{doneTasks}/{totalTasks} tasks
{milestone.slices.map((slice) => { const sliceStatus = getSliceStatus( milestone.id, slice, activeScope, ); const sliceDoneTasks = slice.tasks.filter( (t) => t.done, ).length; const sliceTotalTasks = slice.tasks.length; return (
{slice.id} {slice.title} {slice.risk && } {slice.depends && slice.depends.length > 0 && ( depends on {slice.depends.join(", ")} )}
0 ? `${(sliceDoneTasks / sliceTotalTasks) * 100}%` : "0%", }} />
{sliceDoneTasks}/{sliceTotalTasks}
); })}
); })}
); }