{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "project-roadmap",
  "title": "Project Roadmap",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-svelte/blocks/project-roadmap/ProjectRoadmap.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { GanttScale, GanttTask } from '$lib/components/ui/gantt'\n\n  export interface ProjectRoadmapProps {\n    initialTasks?: GanttTask[]\n    title?: string\n    description?: string\n  }\n</script>\n\n<script lang=\"ts\">\n  import { Plus, Search, Download } from '@lucide/svelte'\n  import { Button } from '$lib/components/ui/button'\n  import { Input, InputGroup, InputGroupAddon } from '$lib/components/ui/input'\n  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '$lib/components/ui/select'\n  import { Gantt, GanttHeader, GanttTree, GanttTimeline } from '$lib/components/ui/gantt'\n  import RoadmapMetrics from './RoadmapMetrics.svelte'\n  import RoadmapTaskDetail from './RoadmapTaskDetail.svelte'\n  import RoadmapAddTaskDialog from './RoadmapAddTaskDialog.svelte'\n\n  const DEFAULT_TASKS: GanttTask[] = [\n    {\n      id: 'task-1',\n      name: 'System Architecture & Database Schema',\n      startDate: '2026-08-01',\n      endDate: '2026-08-08',\n      progress: 100,\n      status: 'done',\n      priority: 'high',\n      assignee: { name: 'Sarah Connor', initials: 'SC' },\n    },\n    {\n      id: 'task-2',\n      name: 'Auth, RBAC & Multi-Tenant Session Engine',\n      startDate: '2026-08-06',\n      endDate: '2026-08-16',\n      progress: 85,\n      status: 'in-progress',\n      priority: 'urgent',\n      assignee: { name: 'Marcus Rivera', initials: 'MR' },\n    },\n    {\n      id: 'task-3',\n      name: 'Real-time WebSocket Notification Bus',\n      startDate: '2026-08-12',\n      endDate: '2026-08-22',\n      progress: 50,\n      status: 'in-progress',\n      priority: 'high',\n      assignee: { name: 'Priya Nair', initials: 'PN' },\n    },\n    {\n      id: 'task-4',\n      name: 'Alpha Core API Release Milestone',\n      startDate: '2026-08-23',\n      endDate: '2026-08-23',\n      isMilestone: true,\n      status: 'todo',\n      priority: 'urgent',\n    },\n    {\n      id: 'task-5',\n      name: 'Stripe Billing & Invoicing Integration',\n      startDate: '2026-08-24',\n      endDate: '2026-09-04',\n      progress: 15,\n      status: 'todo',\n      priority: 'medium',\n      assignee: { name: 'Sundar Krishnan', initials: 'SK' },\n    },\n    {\n      id: 'task-6',\n      name: 'End-to-End Playwright Automated Testing',\n      startDate: '2026-08-28',\n      endDate: '2026-09-08',\n      progress: 0,\n      status: 'todo',\n      priority: 'low',\n      assignee: { name: 'Diane Cho', initials: 'DC' },\n    },\n    {\n      id: 'task-7',\n      name: 'Production Cloudflare Pages Deployment',\n      startDate: '2026-09-10',\n      endDate: '2026-09-10',\n      isMilestone: true,\n      status: 'todo',\n    },\n  ]\n\n  let {\n    initialTasks = DEFAULT_TASKS,\n    title = 'Engineering Q3 Deliverables',\n    description = 'Track roadmap work breakdown, sprint milestones, and cross-team dependencies.',\n  }: ProjectRoadmapProps = $props()\n\n  // svelte-ignore state_referenced_locally -- intentionally initial-only; tasks are locally owned after mount.\n  let tasks = $state<GanttTask[]>([...initialTasks])\n  let searchQuery = $state('')\n  let statusFilter = $state('all')\n  let assigneeFilter = $state('all')\n  let scale = $state<GanttScale>('day')\n\n  let selectedTask = $state<GanttTask | null>(null)\n  let sheetOpen = $state(false)\n  let addDialogOpen = $state(false)\n\n  const filteredTasks = $derived(\n    tasks.filter((t) => {\n      const matchesSearch = !searchQuery || t.name.toLowerCase().includes(searchQuery.toLowerCase())\n      const matchesStatus = statusFilter === 'all' || t.status === statusFilter\n      const matchesAssignee = assigneeFilter === 'all' || t.assignee?.name === assigneeFilter\n      return matchesSearch && matchesStatus && matchesAssignee\n    }),\n  )\n\n  function handleTaskClick(task: GanttTask) {\n    selectedTask = task\n    sheetOpen = true\n  }\n\n  function handleAddTask(newTask: GanttTask) {\n    tasks.push(newTask)\n  }\n</script>\n\n<div data-uipkge data-slot=\"project-roadmap\" class=\"space-y-6\">\n  <!-- Section Top Header -->\n  <div class=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n    <div>\n      <h2 class=\"text-foreground text-xl font-bold tracking-tight sm:text-2xl\">{title}</h2>\n      <p class=\"text-muted-foreground mt-0.5 text-sm\">{description}</p>\n    </div>\n\n    <div class=\"flex items-center gap-2\">\n      <Button aria-label=\"Download attachment\" variant=\"outline\" size=\"sm\">\n        <Download class=\"mr-1 size-3.5\" />\n        Export\n      </Button>\n      <Button size=\"sm\" onclick={() => (addDialogOpen = true)}>\n        <Plus class=\"mr-1 size-3.5\" />\n        Add Task\n      </Button>\n    </div>\n  </div>\n\n  <!-- Summary Metrics -->\n  <RoadmapMetrics {tasks} />\n\n  <!-- Filters & Action Bar -->\n  <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n    <div class=\"flex max-w-lg flex-1 flex-wrap items-center gap-2\">\n      <InputGroup size=\"small\" class=\"w-56\">\n        <InputGroupAddon>\n          <Search class=\"size-3.5\" />\n        </InputGroupAddon>\n        <Input bind:value={searchQuery} placeholder=\"Filter tasks by name...\" />\n      </InputGroup>\n\n      <Select bind:value={statusFilter}>\n        <SelectTrigger class=\"h-8 w-32 text-xs\">\n          <SelectValue placeholder=\"All Status\" />\n        </SelectTrigger>\n        <SelectContent>\n          <SelectItem value=\"all\">All Status</SelectItem>\n          <SelectItem value=\"done\">Completed</SelectItem>\n          <SelectItem value=\"in-progress\">In Progress</SelectItem>\n          <SelectItem value=\"todo\">To Do</SelectItem>\n        </SelectContent>\n      </Select>\n\n      <Select bind:value={assigneeFilter}>\n        <SelectTrigger class=\"h-8 w-36 text-xs\">\n          <SelectValue placeholder=\"All Assignees\" />\n        </SelectTrigger>\n        <SelectContent>\n          <SelectItem value=\"all\">All Assignees</SelectItem>\n          <SelectItem value=\"Sarah Connor\">Sarah Connor</SelectItem>\n          <SelectItem value=\"Marcus Rivera\">Marcus Rivera</SelectItem>\n          <SelectItem value=\"Priya Nair\">Priya Nair</SelectItem>\n          <SelectItem value=\"Sundar Krishnan\">Sundar Krishnan</SelectItem>\n          <SelectItem value=\"Diane Cho\">Diane Cho</SelectItem>\n        </SelectContent>\n      </Select>\n    </div>\n\n    <p class=\"text-muted-foreground font-mono text-xs\">\n      Showing <span class=\"text-foreground font-bold\">{filteredTasks.length}</span> of\n      {tasks.length} deliverables\n      <span class=\"text-muted-foreground/60 ml-2\">(Right-click task for quick actions)</span>\n    </p>\n  </div>\n\n  <!-- Main Interactive Gantt Workspace with Right-Click Context Actions -->\n  <Gantt tasks={filteredTasks} {scale} onscalechange={(s) => (scale = s)} ontaskclick={handleTaskClick} class=\"h-[480px]\">\n    <GanttHeader {title} />\n    <div class=\"flex min-h-0 flex-1 overflow-hidden\">\n      <GanttTree />\n      <GanttTimeline />\n    </div>\n  </Gantt>\n\n  <!-- Task Detail Drawer -->\n  <RoadmapTaskDetail bind:open={sheetOpen} task={selectedTask} />\n\n  <!-- Add Task Modal Dialog -->\n  <RoadmapAddTaskDialog bind:open={addDialogOpen} onAddTask={handleAddTask} />\n</div>\n",
      "type": "registry:block",
      "target": "~/src/lib/components/blocks/project-roadmap/ProjectRoadmap.svelte"
    },
    {
      "path": "packages/registry-svelte/blocks/project-roadmap/RoadmapMetrics.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { GanttTask } from '$lib/components/ui/gantt'\n\n  export interface RoadmapMetricsProps {\n    tasks: GanttTask[]\n  }\n</script>\n\n<script lang=\"ts\">\n  import { CheckCircle2, Clock, AlertCircle, Milestone } from '@lucide/svelte'\n  import { Card, CardContent } from '$lib/components/ui/card'\n\n  let { tasks }: RoadmapMetricsProps = $props()\n\n  const total = $derived(tasks.length)\n  const completed = $derived(tasks.filter((t) => t.status === 'done').length)\n  const inProgress = $derived(tasks.filter((t) => t.status === 'in-progress').length)\n  const milestones = $derived(tasks.filter((t) => t.isMilestone).length)\n  const progress = $derived.by(() => {\n    if (total === 0) return 0\n    const sum = tasks.reduce((acc, t) => acc + (t.progress ?? 0), 0)\n    return Math.round(sum / total)\n  })\n</script>\n\n<div data-slot=\"roadmap-metrics\" class=\"grid grid-cols-2 gap-3 sm:grid-cols-4\">\n  <Card class=\"bg-card/50 border-border/80 shadow-none\">\n    <CardContent class=\"flex items-center justify-between p-3.5\">\n      <div>\n        <p class=\"text-muted-foreground text-xs font-medium\">Overall Progress</p>\n        <p class=\"text-foreground mt-0.5 font-mono text-xl font-bold\">{progress}%</p>\n      </div>\n      <div class=\"bg-primary/10 text-primary flex size-8 items-center justify-center rounded-full\">\n        <Clock class=\"size-4\" />\n      </div>\n    </CardContent>\n  </Card>\n\n  <Card class=\"bg-card/50 border-border/80 shadow-none\">\n    <CardContent class=\"flex items-center justify-between p-3.5\">\n      <div>\n        <p class=\"text-muted-foreground text-xs font-medium\">In Progress</p>\n        <p class=\"text-foreground mt-0.5 font-mono text-xl font-bold\">{inProgress}</p>\n      </div>\n      <div class=\"bg-info/10 text-info flex size-8 items-center justify-center rounded-full\">\n        <AlertCircle class=\"size-4\" />\n      </div>\n    </CardContent>\n  </Card>\n\n  <Card class=\"bg-card/50 border-border/80 shadow-none\">\n    <CardContent class=\"flex items-center justify-between p-3.5\">\n      <div>\n        <p class=\"text-muted-foreground text-xs font-medium\">Completed</p>\n        <p class=\"text-foreground mt-0.5 font-mono text-xl font-bold\">{completed}</p>\n      </div>\n      <div class=\"bg-success/10 text-success flex size-8 items-center justify-center rounded-full\">\n        <CheckCircle2 class=\"size-4\" />\n      </div>\n    </CardContent>\n  </Card>\n\n  <Card class=\"bg-card/50 border-border/80 shadow-none\">\n    <CardContent class=\"flex items-center justify-between p-3.5\">\n      <div>\n        <p class=\"text-muted-foreground text-xs font-medium\">Milestones</p>\n        <p class=\"text-foreground mt-0.5 font-mono text-xl font-bold\">{milestones}</p>\n      </div>\n      <div class=\"bg-warning/10 text-warning flex size-8 items-center justify-center rounded-full\">\n        <Milestone class=\"size-4\" />\n      </div>\n    </CardContent>\n  </Card>\n</div>\n",
      "type": "registry:block",
      "target": "~/src/lib/components/blocks/project-roadmap/RoadmapMetrics.svelte"
    },
    {
      "path": "packages/registry-svelte/blocks/project-roadmap/RoadmapTaskDetail.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { GanttTask } from '$lib/components/ui/gantt'\n\n  export interface RoadmapTaskDetailProps {\n    task: GanttTask | null\n    open?: boolean\n  }\n</script>\n\n<script lang=\"ts\">\n  import { Calendar } from '@lucide/svelte'\n  import { Badge } from '$lib/components/ui/badge'\n  import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '$lib/components/ui/sheet'\n\n  let { task, open = $bindable(false) }: RoadmapTaskDetailProps = $props()\n\n  const statusBadge = $derived.by(() => {\n    if (!task?.status) return { label: 'Active', variant: 'secondary' as const, class: undefined as string | undefined }\n    switch (task.status) {\n      case 'done':\n        return { label: 'Completed', variant: 'outline' as const, class: 'text-success border-success/30' }\n      case 'in-progress':\n        return { label: 'In Progress', variant: 'default' as const, class: undefined as string | undefined }\n      case 'blocked':\n        return { label: 'Blocked', variant: 'destructive' as const, class: undefined as string | undefined }\n      default:\n        return { label: 'To Do', variant: 'secondary' as const, class: undefined as string | undefined }\n    }\n  })\n</script>\n\n<!-- Sheet renders no DOM of its own, so data-slot lives on the content. -->\n<Sheet bind:open>\n  {#if task}\n    <SheetContent data-slot=\"project-roadmap\" class=\"sm:max-w-md\">\n      <SheetHeader class=\"border-border space-y-2 border-b pb-4\">\n        <div class=\"flex items-center gap-2\">\n          <Badge variant={statusBadge.variant} class={statusBadge.class}>\n            {statusBadge.label}\n          </Badge>\n          {#if task.isMilestone}\n            <Badge variant=\"outline\" class=\"border-warning/30 text-warning\">Milestone</Badge>\n          {/if}\n        </div>\n        <SheetTitle class=\"text-foreground text-lg font-semibold\">\n          {task.name}\n        </SheetTitle>\n        <SheetDescription class=\"text-muted-foreground text-xs\">\n          Task ID: <span class=\"font-mono\">{task.id}</span>\n        </SheetDescription>\n      </SheetHeader>\n\n      <div class=\"space-y-5 py-5 text-sm\">\n        <!-- Date Schedule -->\n        <div class=\"space-y-1.5\">\n          <span class=\"text-muted-foreground text-xs font-medium tracking-wider uppercase\">Schedule</span>\n          <div class=\"text-foreground flex items-center gap-2 font-mono text-xs\">\n            <Calendar class=\"text-muted-foreground size-4\" />\n            <span>{task.startDate}</span>\n            <span>&rarr;</span>\n            <span>{task.endDate}</span>\n          </div>\n        </div>\n\n        <!-- Progress -->\n        {#if task.progress != null}\n          <div class=\"space-y-1.5\">\n            <div class=\"flex items-center justify-between text-xs\">\n              <span class=\"text-muted-foreground font-medium tracking-wider uppercase\">Completion</span>\n              <span class=\"font-mono font-bold\">{task.progress}%</span>\n            </div>\n            <div class=\"bg-muted h-2 w-full overflow-hidden rounded-full\">\n              <div\n                style:width={`${task.progress}%`}\n                class=\"bg-primary h-full rounded-full transition-[width] duration-300\"\n              ></div>\n            </div>\n          </div>\n        {/if}\n\n        <!-- Assignee -->\n        {#if task.assignee}\n          <div class=\"space-y-1.5\">\n            <span class=\"text-muted-foreground text-xs font-medium tracking-wider uppercase\">Owner</span>\n            <div class=\"flex items-center gap-2.5\">\n              <div\n                class=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-full text-xs font-medium\"\n              >\n                {task.assignee.initials || task.assignee.name.charAt(0)}\n              </div>\n              <span class=\"text-foreground text-sm font-medium\">{task.assignee.name}</span>\n            </div>\n          </div>\n        {/if}\n      </div>\n    </SheetContent>\n  {/if}\n</Sheet>\n",
      "type": "registry:block",
      "target": "~/src/lib/components/blocks/project-roadmap/RoadmapTaskDetail.svelte"
    },
    {
      "path": "packages/registry-svelte/blocks/project-roadmap/RoadmapAddTaskDialog.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { GanttTask } from '$lib/components/ui/gantt'\n\n  export interface RoadmapAddTaskDialogProps {\n    open?: boolean\n    onAddTask?: (task: GanttTask) => void\n  }\n</script>\n\n<script lang=\"ts\">\n  import { Plus } from '@lucide/svelte'\n  import { Button } from '$lib/components/ui/button'\n  import { Input } from '$lib/components/ui/input'\n  import { Label } from '$lib/components/ui/label'\n  import { Checkbox } from '$lib/components/ui/checkbox'\n  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '$lib/components/ui/select'\n  import {\n    Dialog,\n    DialogContent,\n    DialogDescription,\n    DialogFooter,\n    DialogHeader,\n    DialogTitle,\n  } from '$lib/components/ui/dialog'\n\n  let { open = $bindable(false), onAddTask }: RoadmapAddTaskDialogProps = $props()\n\n  let name = $state('')\n  let startDate = $state('2026-08-20')\n  let endDate = $state('2026-08-28')\n  let status = $state<'todo' | 'in-progress' | 'done'>('todo')\n  let progress = $state(0)\n  let isMilestone = $state(false)\n  let assigneeName = $state('Marcus Rivera')\n\n  function handleSubmit() {\n    if (!name.trim()) return\n\n    const newTask: GanttTask = {\n      id: `task-${Date.now()}`,\n      name: name.trim(),\n      startDate,\n      endDate: isMilestone ? startDate : endDate,\n      status,\n      progress: isMilestone ? 100 : Number(progress) || 0,\n      isMilestone,\n      assignee: {\n        name: assigneeName,\n        initials: assigneeName\n          .split(' ')\n          .map((p) => p[0])\n          .join(''),\n      },\n    }\n\n    onAddTask?.(newTask)\n    open = false\n\n    // Reset form\n    name = ''\n    progress = 0\n    isMilestone = false\n  }\n</script>\n\n<!-- Dialog renders no DOM of its own, so data-slot lives on the content. -->\n<Dialog bind:open>\n  <DialogContent data-slot=\"project-roadmap\" class=\"max-w-[480px] sm:max-w-full\">\n    <DialogHeader>\n      <DialogTitle>Add Roadmap Task</DialogTitle>\n      <DialogDescription>\n        Create a new scheduled deliverable or critical milestone in the project Gantt timeline.\n      </DialogDescription>\n    </DialogHeader>\n\n    <form\n      onsubmit={(e) => {\n        e.preventDefault()\n        handleSubmit()\n      }}\n      class=\"space-y-4 py-2\"\n    >\n      <!-- Task Name -->\n      <div class=\"space-y-1.5\">\n        <Label for=\"task-name\">Task Name</Label>\n        <Input id=\"task-name\" bind:value={name} placeholder=\"e.g. Design Token Hierarchy & Specs\" required />\n      </div>\n\n      <!-- Date Range -->\n      <div class=\"grid grid-cols-2 gap-3\">\n        <div class=\"space-y-1.5\">\n          <Label for=\"start-date\">Start Date</Label>\n          <Input id=\"start-date\" bind:value={startDate} type=\"date\" required />\n        </div>\n        <div class=\"space-y-1.5\">\n          <Label for=\"end-date\">End Date</Label>\n          <Input id=\"end-date\" bind:value={endDate} type=\"date\" disabled={isMilestone} required />\n        </div>\n      </div>\n\n      <!-- Milestone toggle -->\n      <div class=\"border-border/80 bg-muted/20 flex items-center space-x-2 rounded-md border p-3\">\n        <Checkbox id=\"milestone-toggle\" bind:checked={isMilestone} />\n        <div class=\"grid gap-0.5 leading-none\">\n          <label for=\"milestone-toggle\" class=\"text-foreground cursor-pointer text-xs font-semibold select-none\">\n            Key Project Milestone\n          </label>\n          <p class=\"text-muted-foreground text-xs\">\n            Marks a zero-duration target deliverable (e.g. Beta Release, Audit sign-off)\n          </p>\n        </div>\n      </div>\n\n      <!-- Status & Assignee -->\n      <div class=\"grid grid-cols-2 gap-3\">\n        <div class=\"space-y-1.5\">\n          <Label>Initial Status</Label>\n          <Select value={status} onValueChange={(v) => (status = v as typeof status)}>\n            <SelectTrigger>\n              <SelectValue placeholder=\"Status\" />\n            </SelectTrigger>\n            <SelectContent>\n              <SelectItem value=\"todo\">To Do</SelectItem>\n              <SelectItem value=\"in-progress\">In Progress</SelectItem>\n              <SelectItem value=\"done\">Completed</SelectItem>\n            </SelectContent>\n          </Select>\n        </div>\n\n        <div class=\"space-y-1.5\">\n          <Label>Assignee</Label>\n          <Select bind:value={assigneeName}>\n            <SelectTrigger>\n              <SelectValue placeholder=\"Assignee\" />\n            </SelectTrigger>\n            <SelectContent>\n              <SelectItem value=\"Marcus Rivera\">Marcus Rivera</SelectItem>\n              <SelectItem value=\"Sarah Connor\">Sarah Connor</SelectItem>\n              <SelectItem value=\"Priya Nair\">Priya Nair</SelectItem>\n              <SelectItem value=\"Sundar Krishnan\">Sundar Krishnan</SelectItem>\n              <SelectItem value=\"Diane Cho\">Diane Cho</SelectItem>\n            </SelectContent>\n          </Select>\n        </div>\n      </div>\n\n      <DialogFooter class=\"pt-4\">\n        <Button variant=\"outline\" type=\"button\" onclick={() => (open = false)}>Cancel</Button>\n        <Button type=\"submit\">\n          <Plus class=\"mr-1.5 size-3.5\" />\n          Create Task\n        </Button>\n      </DialogFooter>\n    </form>\n  </DialogContent>\n</Dialog>\n",
      "type": "registry:block",
      "target": "~/src/lib/components/blocks/project-roadmap/RoadmapAddTaskDialog.svelte"
    },
    {
      "path": "packages/registry-svelte/blocks/project-roadmap/types.ts",
      "content": "import type { GanttTask } from '$lib/components/ui/gantt'\n\nexport interface RoadmapProject {\n  id: string\n  name: string\n  description?: string\n  tasks: GanttTask[]\n}\n",
      "type": "registry:block",
      "target": "~/src/lib/components/blocks/project-roadmap/types.ts"
    }
  ],
  "dependencies": [
    "@lucide/svelte"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/svelte/gantt.json",
    "https://uipkge.dev/r/svelte/button.json",
    "https://uipkge.dev/r/svelte/card.json",
    "https://uipkge.dev/r/svelte/badge.json",
    "https://uipkge.dev/r/svelte/input.json",
    "https://uipkge.dev/r/svelte/select.json",
    "https://uipkge.dev/r/svelte/dialog.json",
    "https://uipkge.dev/r/svelte/sheet.json",
    "https://uipkge.dev/r/svelte/checkbox.json",
    "https://uipkge.dev/r/svelte/label.json"
  ],
  "description": "Full interactive project roadmap and deliverables surface: Gantt timeline with multi-scale zooming, work breakdown task tree, overall progress metrics, search and status/assignee filters, task detail slide-over Sheet, and Add Task modal dialog.",
  "categories": [
    "productivity",
    "dashboard",
    "data"
  ]
}