@leanspec/ui 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/dist/standalone/packages/web/.next/BUILD_ID +1 -1
  2. package/dist/standalone/packages/web/.next/build-manifest.json +2 -2
  3. package/dist/standalone/packages/web/.next/prerender-manifest.json +3 -3
  4. package/dist/standalone/packages/web/.next/server/app/_global-error.html +2 -2
  5. package/dist/standalone/packages/web/.next/server/app/_global-error.rsc +1 -1
  6. package/dist/standalone/packages/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/dist/standalone/packages/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/dist/standalone/packages/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  9. package/dist/standalone/packages/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  10. package/dist/standalone/packages/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  11. package/dist/standalone/packages/web/.next/server/app/_not-found.html +2 -2
  12. package/dist/standalone/packages/web/.next/server/app/_not-found.rsc +2 -2
  13. package/dist/standalone/packages/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  14. package/dist/standalone/packages/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  15. package/dist/standalone/packages/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  16. package/dist/standalone/packages/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  17. package/dist/standalone/packages/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  18. package/dist/standalone/packages/web/.next/server/app/api/projects/[id]/specs/route.js.nft.json +1 -1
  19. package/dist/standalone/packages/web/.next/server/app/api/projects/route.js.nft.json +1 -1
  20. package/dist/standalone/packages/web/.next/server/app/api/revalidate/route.js.nft.json +1 -1
  21. package/dist/standalone/packages/web/.next/server/app/api/specs/[id]/dependency-graph/route.js.nft.json +1 -1
  22. package/dist/standalone/packages/web/.next/server/app/api/specs/[id]/route.js.nft.json +1 -1
  23. package/dist/standalone/packages/web/.next/server/app/api/specs/[id]/subspecs/[file]/route.js.nft.json +1 -1
  24. package/dist/standalone/packages/web/.next/server/app/api/stats/route.js.nft.json +1 -1
  25. package/dist/standalone/packages/web/.next/server/app/board/page.js.nft.json +1 -1
  26. package/dist/standalone/packages/web/.next/server/app/board.html +1 -1
  27. package/dist/standalone/packages/web/.next/server/app/board.rsc +2 -2
  28. package/dist/standalone/packages/web/.next/server/app/board.segments/_full.segment.rsc +2 -2
  29. package/dist/standalone/packages/web/.next/server/app/board.segments/_index.segment.rsc +1 -1
  30. package/dist/standalone/packages/web/.next/server/app/board.segments/_tree.segment.rsc +1 -1
  31. package/dist/standalone/packages/web/.next/server/app/board.segments/board/__PAGE__.segment.rsc +1 -1
  32. package/dist/standalone/packages/web/.next/server/app/board.segments/board.segment.rsc +1 -1
  33. package/dist/standalone/packages/web/.next/server/app/page.js.nft.json +1 -1
  34. package/dist/standalone/packages/web/.next/server/app/specs/[id]/page.js.nft.json +1 -1
  35. package/dist/standalone/packages/web/.next/server/app/specs/[id]/page_client-reference-manifest.js +1 -1
  36. package/dist/standalone/packages/web/.next/server/app/specs/page.js.nft.json +1 -1
  37. package/dist/standalone/packages/web/.next/server/app/stats/page.js.nft.json +1 -1
  38. package/dist/standalone/packages/web/.next/server/chunks/[root-of-the-server]__2e0f9179._.js +1 -1
  39. package/dist/standalone/packages/web/.next/server/chunks/[root-of-the-server]__577d6d08._.js +1 -1
  40. package/dist/standalone/packages/web/.next/server/chunks/[root-of-the-server]__e54bc4b8._.js +1 -1
  41. package/dist/standalone/packages/web/.next/server/chunks/[root-of-the-server]__f8978f3e._.js +1 -1
  42. package/dist/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__be46bb7c._.js +1 -1
  43. package/dist/standalone/packages/web/.next/server/chunks/ssr/_7dedc302._.js +1 -1
  44. package/dist/standalone/packages/web/.next/server/chunks/ssr/_ad71cd8c._.js +1 -1
  45. package/dist/standalone/packages/web/.next/server/chunks/ssr/_c5a5c652._.js +1 -1
  46. package/dist/standalone/packages/web/.next/server/pages/404.html +2 -2
  47. package/dist/standalone/packages/web/.next/server/pages/500.html +2 -2
  48. package/dist/standalone/packages/web/.next/server/server-reference-manifest.js +1 -1
  49. package/dist/standalone/packages/web/.next/server/server-reference-manifest.json +1 -1
  50. package/dist/{static/chunks/0de258404bcae76f.js → standalone/packages/web/.next/static/chunks/8864b47e107cbe63.js} +1 -1
  51. package/dist/{static/chunks/09ff02250dd56621.js → standalone/packages/web/.next/static/chunks/a2889ecda42c83e7.js} +1 -1
  52. package/dist/standalone/packages/web/.next/static/chunks/c22619397bb8368e.js +1 -0
  53. package/dist/standalone/packages/web/components.json +20 -0
  54. package/dist/standalone/packages/web/drizzle/0000_reflective_thena.sql +59 -0
  55. package/dist/standalone/packages/web/drizzle/0001_fresh_carmella_unuscione.sql +1 -0
  56. package/dist/standalone/packages/web/drizzle/meta/0000_snapshot.json +427 -0
  57. package/dist/standalone/packages/web/drizzle/meta/0001_snapshot.json +436 -0
  58. package/dist/standalone/packages/web/drizzle/meta/_journal.json +20 -0
  59. package/dist/standalone/packages/web/drizzle.config.ts +10 -0
  60. package/dist/standalone/packages/web/eslint.config.mjs +18 -0
  61. package/dist/standalone/packages/web/next.config.ts +7 -0
  62. package/dist/standalone/packages/web/package.json +1 -1
  63. package/dist/standalone/packages/web/postcss.config.mjs +8 -0
  64. package/dist/standalone/packages/web/src/app/api/projects/[id]/specs/route.ts +23 -0
  65. package/dist/standalone/packages/web/src/app/api/projects/route.ts +19 -0
  66. package/dist/standalone/packages/web/src/app/api/revalidate/route.ts +63 -0
  67. package/dist/standalone/packages/web/src/app/api/specs/[id]/dependency-graph/route.test.ts +51 -0
  68. package/dist/standalone/packages/web/src/app/api/specs/[id]/dependency-graph/route.ts +171 -0
  69. package/dist/standalone/packages/web/src/app/api/specs/[id]/route.ts +36 -0
  70. package/dist/standalone/packages/web/src/app/api/specs/[id]/subspecs/[file]/route.ts +46 -0
  71. package/dist/standalone/packages/web/src/app/api/stats/route.ts +19 -0
  72. package/dist/standalone/packages/web/src/app/board/board-client.tsx +162 -0
  73. package/dist/standalone/packages/web/src/app/board/loading.tsx +43 -0
  74. package/dist/standalone/packages/web/src/app/board/page.tsx +18 -0
  75. package/dist/standalone/packages/web/src/app/dashboard-client.tsx +364 -0
  76. package/dist/standalone/packages/web/src/app/error.tsx +43 -0
  77. package/dist/standalone/packages/web/src/app/globals.css +531 -0
  78. package/dist/standalone/packages/web/src/app/home-client.tsx +277 -0
  79. package/dist/standalone/packages/web/src/app/layout.tsx +70 -0
  80. package/dist/standalone/packages/web/src/app/loading.tsx +87 -0
  81. package/dist/standalone/packages/web/src/app/not-found.tsx +27 -0
  82. package/dist/standalone/packages/web/src/app/page.tsx +18 -0
  83. package/dist/standalone/packages/web/src/app/specs/[id]/loading.tsx +5 -0
  84. package/dist/standalone/packages/web/src/app/specs/[id]/page.tsx +43 -0
  85. package/dist/standalone/packages/web/src/app/specs/page.tsx +18 -0
  86. package/dist/standalone/packages/web/src/app/specs/specs-client.tsx +425 -0
  87. package/dist/standalone/packages/web/src/app/stats/page.tsx +18 -0
  88. package/dist/standalone/packages/web/src/app/stats/stats-client.tsx +283 -0
  89. package/dist/standalone/packages/web/src/components/back-to-top.tsx +46 -0
  90. package/dist/standalone/packages/web/src/components/empty-state.tsx +52 -0
  91. package/dist/standalone/packages/web/src/components/main-sidebar.tsx +175 -0
  92. package/dist/standalone/packages/web/src/components/markdown-link.test.ts +96 -0
  93. package/dist/standalone/packages/web/src/components/markdown-link.tsx +95 -0
  94. package/dist/standalone/packages/web/src/components/navigation.tsx +210 -0
  95. package/dist/standalone/packages/web/src/components/priority-badge.tsx +53 -0
  96. package/dist/standalone/packages/web/src/components/quick-search.tsx +180 -0
  97. package/dist/standalone/packages/web/src/components/skeletons.tsx +119 -0
  98. package/dist/standalone/packages/web/src/components/spec-dependency-graph.tsx +369 -0
  99. package/dist/standalone/packages/web/src/components/spec-detail-client.tsx +372 -0
  100. package/dist/standalone/packages/web/src/components/spec-detail-loading-shell.tsx +42 -0
  101. package/dist/standalone/packages/web/src/components/spec-detail-wrapper.tsx +70 -0
  102. package/dist/standalone/packages/web/src/components/spec-metadata.tsx +136 -0
  103. package/dist/standalone/packages/web/src/components/spec-sidebar.tsx +127 -0
  104. package/dist/standalone/packages/web/src/components/spec-timeline.tsx +186 -0
  105. package/dist/standalone/packages/web/src/components/specs-nav-sidebar.tsx +561 -0
  106. package/dist/standalone/packages/web/src/components/status-badge.tsx +53 -0
  107. package/dist/standalone/packages/web/src/components/sub-spec-tabs.tsx +143 -0
  108. package/dist/standalone/packages/web/src/components/table-of-contents.tsx +130 -0
  109. package/dist/standalone/packages/web/src/components/theme-provider.tsx +11 -0
  110. package/dist/standalone/packages/web/src/components/theme-toggle.tsx +37 -0
  111. package/dist/standalone/packages/web/src/components/ui/avatar.tsx +50 -0
  112. package/dist/standalone/packages/web/src/components/ui/badge.tsx +36 -0
  113. package/dist/standalone/packages/web/src/components/ui/breadcrumb.tsx +110 -0
  114. package/dist/standalone/packages/web/src/components/ui/button.tsx +57 -0
  115. package/dist/standalone/packages/web/src/components/ui/card.tsx +76 -0
  116. package/dist/standalone/packages/web/src/components/ui/command.tsx +153 -0
  117. package/dist/standalone/packages/web/src/components/ui/dialog.tsx +122 -0
  118. package/dist/standalone/packages/web/src/components/ui/input.tsx +24 -0
  119. package/dist/standalone/packages/web/src/components/ui/select.tsx +159 -0
  120. package/dist/standalone/packages/web/src/components/ui/separator.tsx +31 -0
  121. package/dist/standalone/packages/web/src/components/ui/skeleton.tsx +15 -0
  122. package/dist/standalone/packages/web/src/components/ui/tabs.tsx +55 -0
  123. package/dist/standalone/packages/web/src/components/ui/toast.tsx +30 -0
  124. package/dist/standalone/packages/web/src/components/ui/tooltip.tsx +32 -0
  125. package/dist/standalone/packages/web/src/lib/date-utils.ts +76 -0
  126. package/dist/standalone/packages/web/src/lib/db/index.ts +42 -0
  127. package/dist/standalone/packages/web/src/lib/db/migrate.ts +18 -0
  128. package/dist/standalone/packages/web/src/lib/db/queries.ts +114 -0
  129. package/dist/standalone/packages/web/src/lib/db/schema.ts +123 -0
  130. package/dist/standalone/packages/web/src/lib/db/seed.ts +156 -0
  131. package/dist/standalone/packages/web/src/lib/db/service-queries.ts +216 -0
  132. package/dist/standalone/packages/web/src/lib/dependency-graph.ts +105 -0
  133. package/dist/standalone/packages/web/src/lib/specs/service.ts +120 -0
  134. package/dist/standalone/packages/web/src/lib/specs/sources/database-source.ts +94 -0
  135. package/dist/standalone/packages/web/src/lib/specs/sources/filesystem-source.ts +249 -0
  136. package/dist/standalone/packages/web/src/lib/specs/types.ts +55 -0
  137. package/dist/standalone/packages/web/src/lib/stores/specs-sidebar-store.ts +152 -0
  138. package/dist/standalone/packages/web/src/lib/sub-specs.ts +171 -0
  139. package/dist/standalone/packages/web/src/lib/utils.ts +17 -0
  140. package/dist/standalone/packages/web/src/types/specs.ts +18 -0
  141. package/dist/standalone/packages/web/tailwind.config.ts +58 -0
  142. package/dist/standalone/packages/web/tsconfig.json +34 -0
  143. package/dist/standalone/packages/web/vitest.config.ts +14 -0
  144. package/dist/standalone/specs/100-release-process-typecheck-failure/README.md +266 -0
  145. package/dist/standalone/specs/101-sidebar-scroll-position-drift/README.md +100 -0
  146. package/package.json +5 -3
  147. package/dist/BUILD_ID +0 -1
  148. package/dist/static/chunks/a3e649fcddd3d715.js +0 -1
  149. /package/dist/{static/Z_BxkB0KJViCNbULN1S5p → standalone/packages/web/.next/static/DDHAmoL_2Poji-whvFjry}/_buildManifest.js +0 -0
  150. /package/dist/{static/Z_BxkB0KJViCNbULN1S5p → standalone/packages/web/.next/static/DDHAmoL_2Poji-whvFjry}/_clientMiddlewareManifest.json +0 -0
  151. /package/dist/{static/Z_BxkB0KJViCNbULN1S5p → standalone/packages/web/.next/static/DDHAmoL_2Poji-whvFjry}/_ssgManifest.js +0 -0
  152. /package/dist/{static → standalone/packages/web/.next/static}/chunks/0c19c69aa7625475.js +0 -0
  153. /package/dist/{static → standalone/packages/web/.next/static}/chunks/116800b03245a1e5.js +0 -0
  154. /package/dist/{static → standalone/packages/web/.next/static}/chunks/19e80edf527aef5c.js +0 -0
  155. /package/dist/{static → standalone/packages/web/.next/static}/chunks/2ece90370908f56c.js +0 -0
  156. /package/dist/{static → standalone/packages/web/.next/static}/chunks/36fd2dddb486f6bc.js +0 -0
  157. /package/dist/{static → standalone/packages/web/.next/static}/chunks/5c2072ad938de8ed.js +0 -0
  158. /package/dist/{static → standalone/packages/web/.next/static}/chunks/6577fe797a336bab.js +0 -0
  159. /package/dist/{static → standalone/packages/web/.next/static}/chunks/6a05a93ec8fa7b83.js +0 -0
  160. /package/dist/{static → standalone/packages/web/.next/static}/chunks/7f732ea69e643219.js +0 -0
  161. /package/dist/{static → standalone/packages/web/.next/static}/chunks/a02c1f50ff00204f.js +0 -0
  162. /package/dist/{static → standalone/packages/web/.next/static}/chunks/a45464b9776dd88e.js +0 -0
  163. /package/dist/{static → standalone/packages/web/.next/static}/chunks/a6dad97d9634a72d.js +0 -0
  164. /package/dist/{static → standalone/packages/web/.next/static}/chunks/ae04dcd433be6dab.js +0 -0
  165. /package/dist/{static → standalone/packages/web/.next/static}/chunks/b20313408e970968.css +0 -0
  166. /package/dist/{static → standalone/packages/web/.next/static}/chunks/c46095e1a421d93f.js +0 -0
  167. /package/dist/{static → standalone/packages/web/.next/static}/chunks/c48dd4c72d7c5ef4.js +0 -0
  168. /package/dist/{static → standalone/packages/web/.next/static}/chunks/c557ac675be79771.js +0 -0
  169. /package/dist/{static → standalone/packages/web/.next/static}/chunks/dca0c854c59234cd.js +0 -0
  170. /package/dist/{static → standalone/packages/web/.next/static}/chunks/df1731c03abf1aee.css +0 -0
  171. /package/dist/{static → standalone/packages/web/.next/static}/chunks/dfd41488ad062cd5.js +0 -0
  172. /package/dist/{static → standalone/packages/web/.next/static}/chunks/ebd89051637b9a47.js +0 -0
  173. /package/dist/{static → standalone/packages/web/.next/static}/chunks/f3ec9fd77a8618b1.js +0 -0
  174. /package/dist/{static → standalone/packages/web/.next/static}/chunks/turbopack-7450632b40b2e378.js +0 -0
  175. /package/dist/{public → standalone/packages/web/public}/f864aa7e7061c0600e35cf3d879b27cf.txt +0 -0
  176. /package/dist/{public → standalone/packages/web/public}/favicon.ico +0 -0
  177. /package/dist/{public → standalone/packages/web/public}/file.svg +0 -0
  178. /package/dist/{public → standalone/packages/web/public}/github-mark-white.svg +0 -0
  179. /package/dist/{public → standalone/packages/web/public}/github-mark.svg +0 -0
  180. /package/dist/{public → standalone/packages/web/public}/globe.svg +0 -0
  181. /package/dist/{public → standalone/packages/web/public}/icon.svg +0 -0
  182. /package/dist/{public → standalone/packages/web/public}/logo-dark-bg.svg +0 -0
  183. /package/dist/{public → standalone/packages/web/public}/logo-with-bg.svg +0 -0
  184. /package/dist/{public → standalone/packages/web/public}/logo.svg +0 -0
  185. /package/dist/{public → standalone/packages/web/public}/next.svg +0 -0
  186. /package/dist/{public → standalone/packages/web/public}/vercel.svg +0 -0
  187. /package/dist/{public → standalone/packages/web/public}/window.svg +0 -0
@@ -0,0 +1,127 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import Link from "next/link"
5
+ import { FileText, Search, ChevronLeft, ChevronRight } from "lucide-react"
6
+ import { Input } from "@/components/ui/input"
7
+ import { Button } from "@/components/ui/button"
8
+ import { StatusBadge } from "@/components/status-badge"
9
+ import { cn } from "@/lib/utils"
10
+
11
+ interface Spec {
12
+ id: string
13
+ specNumber: number | null
14
+ title: string | null
15
+ specName: string
16
+ status: string | null
17
+ priority: string | null
18
+ }
19
+
20
+ interface SpecSidebarProps {
21
+ specs: Spec[]
22
+ currentSpecId: string
23
+ }
24
+
25
+ export function SpecSidebar({ specs, currentSpecId }: SpecSidebarProps) {
26
+ const [collapsed, setCollapsed] = React.useState(false)
27
+ const [searchQuery, setSearchQuery] = React.useState("")
28
+
29
+ const filteredSpecs = React.useMemo(() => {
30
+ if (!searchQuery) return specs
31
+ const query = searchQuery.toLowerCase()
32
+ return specs.filter(
33
+ (spec) =>
34
+ spec.title?.toLowerCase().includes(query) ||
35
+ spec.specName.toLowerCase().includes(query) ||
36
+ spec.specNumber?.toString().includes(query)
37
+ )
38
+ }, [specs, searchQuery])
39
+
40
+ if (collapsed) {
41
+ return (
42
+ <div className="sticky top-14 h-[calc(100vh-3.5rem)] w-12 border-r bg-background flex flex-col items-center py-4">
43
+ <Button
44
+ variant="ghost"
45
+ size="sm"
46
+ onClick={() => setCollapsed(false)}
47
+ className="h-8 w-8 p-0"
48
+ >
49
+ <ChevronRight className="h-4 w-4" />
50
+ <span className="sr-only">Expand sidebar</span>
51
+ </Button>
52
+ </div>
53
+ )
54
+ }
55
+
56
+ return (
57
+ <aside className="sticky top-14 h-[calc(100vh-3.5rem)] w-64 border-r bg-background flex flex-col">
58
+ <div className="p-4 border-b flex items-center justify-between">
59
+ <h2 className="font-semibold text-sm">All Specs</h2>
60
+ <Button
61
+ variant="ghost"
62
+ size="sm"
63
+ onClick={() => setCollapsed(true)}
64
+ className="h-8 w-8 p-0"
65
+ >
66
+ <ChevronLeft className="h-4 w-4" />
67
+ <span className="sr-only">Collapse sidebar</span>
68
+ </Button>
69
+ </div>
70
+
71
+ <div className="p-4 border-b">
72
+ <div className="relative">
73
+ <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
74
+ <Input
75
+ placeholder="Search specs..."
76
+ value={searchQuery}
77
+ onChange={(e) => setSearchQuery(e.target.value)}
78
+ className="pl-8 h-9"
79
+ />
80
+ </div>
81
+ </div>
82
+
83
+ <div className="flex-1 overflow-y-auto">
84
+ <div className="p-2">
85
+ {filteredSpecs.length === 0 ? (
86
+ <div className="text-center py-8 text-sm text-muted-foreground">
87
+ No specs found
88
+ </div>
89
+ ) : (
90
+ filteredSpecs.map((spec) => {
91
+ const isActive = spec.id === currentSpecId
92
+ const displayTitle = spec.title || spec.specName
93
+
94
+ return (
95
+ <Link
96
+ key={spec.id}
97
+ href={`/specs/${spec.id}`}
98
+ className={cn(
99
+ "block p-2 rounded-md text-sm transition-colors mb-1",
100
+ isActive
101
+ ? "bg-accent text-accent-foreground font-medium"
102
+ : "hover:bg-accent/50"
103
+ )}
104
+ >
105
+ <div className="flex items-start gap-2">
106
+ <FileText className="h-4 w-4 mt-0.5 shrink-0" />
107
+ <div className="flex-1 min-w-0">
108
+ <div className="font-medium truncate">
109
+ {spec.specNumber && `#${spec.specNumber} `}
110
+ {displayTitle}
111
+ </div>
112
+ {spec.status && (
113
+ <div className="mt-1">
114
+ <StatusBadge status={spec.status} />
115
+ </div>
116
+ )}
117
+ </div>
118
+ </div>
119
+ </Link>
120
+ )
121
+ })
122
+ )}
123
+ </div>
124
+ </div>
125
+ </aside>
126
+ )
127
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Timeline component to visualize spec evolution (vertical layout)
3
+ */
4
+
5
+ import { Clock, PlayCircle, CheckCircle2, Archive, Circle } from 'lucide-react';
6
+ import { formatRelativeTime, formatDuration } from '@/lib/date-utils';
7
+ import { cn } from '@/lib/utils';
8
+
9
+ interface TimelineEvent {
10
+ label: string;
11
+ date: Date | string | number | null | undefined;
12
+ isActive?: boolean;
13
+ isFuture?: boolean;
14
+ icon?: typeof Clock | typeof PlayCircle | typeof CheckCircle2 | typeof Archive | typeof Circle;
15
+ color?: string;
16
+ }
17
+
18
+ interface SpecTimelineProps {
19
+ createdAt: Date | string | number | null | undefined;
20
+ updatedAt: Date | string | number | null | undefined;
21
+ completedAt?: Date | string | number | null | undefined;
22
+ status: string;
23
+ className?: string;
24
+ }
25
+
26
+ export function SpecTimeline({
27
+ createdAt,
28
+ updatedAt,
29
+ completedAt,
30
+ status,
31
+ className
32
+ }: SpecTimelineProps) {
33
+ const events: TimelineEvent[] = [];
34
+
35
+ // Always include created
36
+ if (createdAt) {
37
+ events.push({
38
+ label: 'Created',
39
+ date: createdAt,
40
+ isActive: true,
41
+ isFuture: false,
42
+ icon: Clock,
43
+ color: 'text-blue-600',
44
+ });
45
+ }
46
+
47
+ // Add in-progress
48
+ if (status === 'in-progress' || status === 'complete' || status === 'archived') {
49
+ events.push({
50
+ label: 'In Progress',
51
+ date: updatedAt || createdAt,
52
+ isActive: true,
53
+ isFuture: false,
54
+ icon: PlayCircle,
55
+ color: 'text-orange-600',
56
+ });
57
+ } else {
58
+ events.push({
59
+ label: 'In Progress',
60
+ date: null,
61
+ isActive: false,
62
+ isFuture: true,
63
+ icon: Circle,
64
+ color: 'text-muted-foreground',
65
+ });
66
+ }
67
+
68
+ // Add completed
69
+ if (status === 'complete' || status === 'archived') {
70
+ events.push({
71
+ label: 'Complete',
72
+ date: completedAt || updatedAt,
73
+ isActive: true,
74
+ isFuture: false,
75
+ icon: CheckCircle2,
76
+ color: 'text-green-600',
77
+ });
78
+ } else {
79
+ events.push({
80
+ label: 'Complete',
81
+ date: null,
82
+ isActive: false,
83
+ isFuture: true,
84
+ icon: Circle,
85
+ color: 'text-muted-foreground',
86
+ });
87
+ }
88
+
89
+ // Add archived if status is archived
90
+ if (status === 'archived') {
91
+ events.push({
92
+ label: 'Archived',
93
+ date: updatedAt,
94
+ isActive: true,
95
+ isFuture: false,
96
+ icon: Archive,
97
+ color: 'text-gray-600',
98
+ });
99
+ }
100
+
101
+ if (events.length === 0) return null;
102
+
103
+ return (
104
+ <div className={cn('flex items-start gap-2', className)}>
105
+ {events.map((event, i) => {
106
+ const Icon = event.icon;
107
+ const isLast = i === events.length - 1;
108
+ const nextEvent = !isLast ? events[i + 1] : null;
109
+ const duration = event.date && nextEvent?.date && !nextEvent.isFuture
110
+ ? formatDuration(event.date, nextEvent.date)
111
+ : '';
112
+
113
+ return (
114
+ <div key={i} className="flex items-center gap-2 flex-1">
115
+ {/* Event content */}
116
+ <div className="flex flex-col items-center gap-1 min-w-0">
117
+ {/* Icon */}
118
+ <div
119
+ className={cn(
120
+ "w-8 h-8 rounded-full border-2 bg-background flex items-center justify-center shrink-0",
121
+ event.isActive && !event.isFuture
122
+ ? "border-primary"
123
+ : "border-muted-foreground/40"
124
+ )}
125
+ >
126
+ {Icon && (
127
+ <Icon
128
+ className={cn(
129
+ "h-4 w-4",
130
+ event.isActive && !event.isFuture ? "text-primary" : "text-muted-foreground/60"
131
+ )}
132
+ />
133
+ )}
134
+ </div>
135
+
136
+ {/* Label */}
137
+ <div
138
+ className={cn(
139
+ "text-xs font-medium text-center whitespace-nowrap",
140
+ event.isActive && !event.isFuture ? "text-foreground" : "text-muted-foreground"
141
+ )}
142
+ >
143
+ {event.label}
144
+ </div>
145
+
146
+ {/* Date row - reserve space even when pending */}
147
+ <div className="text-[10px] text-center min-h-[14px]">
148
+ {event.date && !event.isFuture && (
149
+ <span className="text-muted-foreground">{formatRelativeTime(event.date)}</span>
150
+ )}
151
+ {!event.date && event.isFuture && (
152
+ <span className="text-muted-foreground/70">Awaiting start</span>
153
+ )}
154
+ {event.date && event.isFuture && (
155
+ <span className="text-muted-foreground/70">Queued</span>
156
+ )}
157
+ {!event.date && !event.isFuture && (
158
+ <span className="text-muted-foreground/60">Pending update</span>
159
+ )}
160
+ </div>
161
+ </div>
162
+
163
+ {/* Connecting line with duration */}
164
+ {!isLast && (
165
+ <div className="flex flex-col items-center flex-1 min-w-4 gap-0.5">
166
+ <div
167
+ className={cn(
168
+ "h-0.5 w-full",
169
+ event.isActive && !event.isFuture
170
+ ? "bg-primary"
171
+ : "bg-muted-foreground/40"
172
+ )}
173
+ />
174
+ {duration && (
175
+ <div className="text-[10px] text-muted-foreground font-medium whitespace-nowrap">
176
+ {duration}
177
+ </div>
178
+ )}
179
+ </div>
180
+ )}
181
+ </div>
182
+ );
183
+ })}
184
+ </div>
185
+ );
186
+ }