prjct-cli 0.13.3 → 0.15.1

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 (195) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/bin/prjct +10 -13
  3. package/core/agentic/memory-system/semantic-memories.ts +2 -1
  4. package/core/agentic/plan-mode/plan-mode.ts +2 -1
  5. package/core/agentic/prompt-builder.ts +22 -43
  6. package/core/agentic/services.ts +5 -5
  7. package/core/agentic/smart-context.ts +7 -2
  8. package/core/command-registry/core-commands.ts +54 -29
  9. package/core/command-registry/optional-commands.ts +64 -0
  10. package/core/command-registry/setup-commands.ts +18 -3
  11. package/core/commands/analysis.ts +21 -68
  12. package/core/commands/analytics.ts +247 -213
  13. package/core/commands/base.ts +1 -1
  14. package/core/commands/index.ts +41 -36
  15. package/core/commands/maintenance.ts +300 -31
  16. package/core/commands/planning.ts +233 -22
  17. package/core/commands/setup.ts +3 -8
  18. package/core/commands/shipping.ts +14 -18
  19. package/core/commands/types.ts +8 -6
  20. package/core/commands/workflow.ts +105 -100
  21. package/core/context/generator.ts +317 -0
  22. package/core/context-sync.ts +7 -350
  23. package/core/data/index.ts +13 -32
  24. package/core/data/md-ideas-manager.ts +155 -0
  25. package/core/data/md-queue-manager.ts +4 -3
  26. package/core/data/md-shipped-manager.ts +90 -0
  27. package/core/data/md-state-manager.ts +11 -7
  28. package/core/domain/agent-generator.ts +23 -63
  29. package/core/events/index.ts +143 -0
  30. package/core/index.ts +17 -14
  31. package/core/infrastructure/capability-installer.ts +13 -149
  32. package/core/infrastructure/migrator/project-scanner.ts +2 -1
  33. package/core/infrastructure/path-manager.ts +4 -6
  34. package/core/infrastructure/setup.ts +3 -0
  35. package/core/infrastructure/uuid-migration.ts +750 -0
  36. package/core/outcomes/recorder.ts +2 -1
  37. package/core/plugin/loader.ts +4 -7
  38. package/core/plugin/registry.ts +3 -3
  39. package/core/schemas/index.ts +23 -25
  40. package/core/schemas/state.ts +1 -0
  41. package/core/serializers/ideas-serializer.ts +187 -0
  42. package/core/serializers/index.ts +16 -0
  43. package/core/serializers/shipped-serializer.ts +108 -0
  44. package/core/session/utils.ts +3 -9
  45. package/core/storage/ideas-storage.ts +273 -0
  46. package/core/storage/index.ts +204 -0
  47. package/core/storage/queue-storage.ts +297 -0
  48. package/core/storage/shipped-storage.ts +223 -0
  49. package/core/storage/state-storage.ts +235 -0
  50. package/core/storage/storage-manager.ts +175 -0
  51. package/package.json +1 -1
  52. package/packages/web/app/api/projects/[id]/momentum/route.ts +257 -0
  53. package/packages/web/app/api/sessions/current/route.ts +132 -0
  54. package/packages/web/app/api/sessions/history/route.ts +96 -14
  55. package/packages/web/app/globals.css +5 -0
  56. package/packages/web/app/layout.tsx +2 -0
  57. package/packages/web/app/project/[id]/code/layout.tsx +18 -0
  58. package/packages/web/app/project/[id]/code/page.tsx +408 -0
  59. package/packages/web/app/project/[id]/page.tsx +359 -389
  60. package/packages/web/app/project/[id]/reports/page.tsx +59 -0
  61. package/packages/web/app/project/[id]/reports/print/page.tsx +58 -0
  62. package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -1
  63. package/packages/web/components/AgentsCard/AgentsCard.tsx +64 -34
  64. package/packages/web/components/AgentsCard/AgentsCard.types.ts +1 -0
  65. package/packages/web/components/AppSidebar/AppSidebar.tsx +135 -11
  66. package/packages/web/components/BentoCard/BentoCard.constants.ts +3 -3
  67. package/packages/web/components/BentoCard/BentoCard.tsx +2 -1
  68. package/packages/web/components/BentoGrid/BentoGrid.tsx +2 -2
  69. package/packages/web/components/BlockersCard/BlockersCard.tsx +65 -57
  70. package/packages/web/components/BlockersCard/BlockersCard.types.ts +1 -0
  71. package/packages/web/components/CommandBar/CommandBar.tsx +67 -0
  72. package/packages/web/components/CommandBar/index.ts +1 -0
  73. package/packages/web/components/DashboardContent/DashboardContent.tsx +35 -5
  74. package/packages/web/components/DateGroup/DateGroup.tsx +1 -1
  75. package/packages/web/components/EmptyState/EmptyState.tsx +39 -21
  76. package/packages/web/components/EmptyState/EmptyState.types.ts +1 -0
  77. package/packages/web/components/EventRow/EventRow.tsx +4 -4
  78. package/packages/web/components/EventRow/EventRow.utils.ts +3 -3
  79. package/packages/web/components/HeroSection/HeroSection.tsx +52 -15
  80. package/packages/web/components/HeroSection/HeroSection.types.ts +4 -4
  81. package/packages/web/components/HeroSection/HeroSection.utils.ts +7 -3
  82. package/packages/web/components/IdeasCard/IdeasCard.tsx +94 -27
  83. package/packages/web/components/IdeasCard/IdeasCard.types.ts +1 -0
  84. package/packages/web/components/MasonryGrid/MasonryGrid.tsx +18 -0
  85. package/packages/web/components/MasonryGrid/index.ts +1 -0
  86. package/packages/web/components/MomentumWidget/MomentumWidget.tsx +119 -0
  87. package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +16 -0
  88. package/packages/web/components/MomentumWidget/index.ts +2 -0
  89. package/packages/web/components/NowCard/NowCard.tsx +81 -56
  90. package/packages/web/components/NowCard/NowCard.types.ts +1 -0
  91. package/packages/web/components/PageHeader/PageHeader.tsx +24 -0
  92. package/packages/web/components/PageHeader/index.ts +1 -0
  93. package/packages/web/components/ProgressRing/ProgressRing.constants.ts +2 -2
  94. package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +2 -2
  95. package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +37 -0
  96. package/packages/web/components/ProjectColorDot/index.ts +1 -0
  97. package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +104 -0
  98. package/packages/web/components/ProjectSelectorModal/index.ts +1 -0
  99. package/packages/web/components/Providers/Providers.tsx +4 -1
  100. package/packages/web/components/QueueCard/QueueCard.tsx +78 -25
  101. package/packages/web/components/QueueCard/QueueCard.types.ts +1 -0
  102. package/packages/web/components/QueueCard/QueueCard.utils.ts +3 -3
  103. package/packages/web/components/RecoverCard/RecoverCard.tsx +72 -0
  104. package/packages/web/components/RecoverCard/RecoverCard.types.ts +16 -0
  105. package/packages/web/components/RecoverCard/index.ts +2 -0
  106. package/packages/web/components/RoadmapCard/RoadmapCard.tsx +101 -33
  107. package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +1 -0
  108. package/packages/web/components/ShipsCard/ShipsCard.tsx +71 -28
  109. package/packages/web/components/ShipsCard/ShipsCard.types.ts +2 -0
  110. package/packages/web/components/SparklineChart/SparklineChart.tsx +20 -18
  111. package/packages/web/components/StatsMasonry/StatsMasonry.tsx +95 -0
  112. package/packages/web/components/StatsMasonry/index.ts +1 -0
  113. package/packages/web/components/StreakCard/StreakCard.tsx +37 -35
  114. package/packages/web/components/TasksCounter/TasksCounter.tsx +1 -1
  115. package/packages/web/components/TechStackBadges/TechStackBadges.tsx +12 -4
  116. package/packages/web/components/TerminalDock/DockToggleTab.tsx +29 -0
  117. package/packages/web/components/TerminalDock/TerminalDock.tsx +386 -0
  118. package/packages/web/components/TerminalDock/TerminalDockTab.tsx +130 -0
  119. package/packages/web/components/TerminalDock/TerminalTabBar.tsx +142 -0
  120. package/packages/web/components/TerminalDock/index.ts +2 -0
  121. package/packages/web/components/VelocityBadge/VelocityBadge.tsx +8 -3
  122. package/packages/web/components/VelocityCard/VelocityCard.tsx +49 -47
  123. package/packages/web/components/WeeklyReports/PrintableReport.tsx +259 -0
  124. package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +187 -0
  125. package/packages/web/components/WeeklyReports/WeekCalendar.tsx +288 -0
  126. package/packages/web/components/WeeklyReports/WeeklyReports.tsx +149 -0
  127. package/packages/web/components/WeeklyReports/index.ts +4 -0
  128. package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +16 -4
  129. package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +1 -0
  130. package/packages/web/components/charts/SessionsChart.tsx +6 -3
  131. package/packages/web/components/ui/dialog.tsx +143 -0
  132. package/packages/web/components/ui/drawer.tsx +135 -0
  133. package/packages/web/components/ui/select.tsx +187 -0
  134. package/packages/web/context/GlobalTerminalContext.tsx +538 -0
  135. package/packages/web/lib/commands.ts +81 -0
  136. package/packages/web/lib/generate-week-report.ts +285 -0
  137. package/packages/web/lib/parse-prjct-files.ts +56 -55
  138. package/packages/web/lib/project-colors.ts +58 -0
  139. package/packages/web/lib/projects.ts +58 -5
  140. package/packages/web/lib/services/projects.server.ts +11 -1
  141. package/packages/web/next-env.d.ts +1 -1
  142. package/packages/web/package.json +5 -1
  143. package/templates/commands/analyze.md +39 -3
  144. package/templates/commands/ask.md +58 -3
  145. package/templates/commands/bug.md +117 -26
  146. package/templates/commands/dash.md +95 -158
  147. package/templates/commands/done.md +130 -148
  148. package/templates/commands/feature.md +125 -103
  149. package/templates/commands/git.md +18 -3
  150. package/templates/commands/idea.md +121 -38
  151. package/templates/commands/init.md +124 -20
  152. package/templates/commands/migrate-all.md +63 -28
  153. package/templates/commands/migrate.md +140 -0
  154. package/templates/commands/next.md +115 -5
  155. package/templates/commands/now.md +146 -82
  156. package/templates/commands/pause.md +89 -74
  157. package/templates/commands/redo.md +6 -4
  158. package/templates/commands/resume.md +141 -59
  159. package/templates/commands/setup.md +18 -3
  160. package/templates/commands/ship.md +103 -231
  161. package/templates/commands/spec.md +98 -8
  162. package/templates/commands/suggest.md +22 -2
  163. package/templates/commands/sync.md +192 -203
  164. package/templates/commands/undo.md +6 -4
  165. package/templates/mcp-config.json +20 -1
  166. package/core/data/agents-manager.ts +0 -76
  167. package/core/data/analysis-manager.ts +0 -83
  168. package/core/data/base-manager.ts +0 -156
  169. package/core/data/ideas-manager.ts +0 -81
  170. package/core/data/outcomes-manager.ts +0 -96
  171. package/core/data/project-manager.ts +0 -75
  172. package/core/data/roadmap-manager.ts +0 -118
  173. package/core/data/shipped-manager.ts +0 -65
  174. package/core/data/state-manager.ts +0 -214
  175. package/core/state/index.ts +0 -25
  176. package/core/state/manager.ts +0 -376
  177. package/core/state/types.ts +0 -185
  178. package/core/utils/project-capabilities.ts +0 -156
  179. package/core/view-generator.ts +0 -536
  180. package/packages/web/app/project/[id]/stats/loading.tsx +0 -43
  181. package/packages/web/app/project/[id]/stats/page.tsx +0 -253
  182. package/templates/agent-assignment.md +0 -72
  183. package/templates/analysis/project-analysis.md +0 -78
  184. package/templates/checklists/accessibility.md +0 -33
  185. package/templates/commands/build.md +0 -17
  186. package/templates/commands/decision.md +0 -226
  187. package/templates/commands/fix.md +0 -79
  188. package/templates/commands/help.md +0 -61
  189. package/templates/commands/progress.md +0 -14
  190. package/templates/commands/recap.md +0 -14
  191. package/templates/commands/roadmap.md +0 -52
  192. package/templates/commands/status.md +0 -17
  193. package/templates/commands/task.md +0 -63
  194. package/templates/commands/work.md +0 -44
  195. package/templates/commands/workflow.md +0 -12
@@ -0,0 +1,67 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@/lib/utils'
4
+ import {
5
+ WORKFLOW_COMMANDS,
6
+ COMMAND_GROUPS,
7
+ SYNC_COMMAND,
8
+ } from '@/lib/commands'
9
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
10
+
11
+ interface CommandBarProps {
12
+ isConnected: boolean
13
+ onCommand: (cmd: string) => void
14
+ }
15
+
16
+ export function CommandBar({ isConnected, onCommand }: CommandBarProps) {
17
+ return (
18
+ <div className="flex items-center gap-0.5 px-2 py-1">
19
+ <Tooltip>
20
+ <TooltipTrigger asChild>
21
+ <button
22
+ onClick={() => onCommand(SYNC_COMMAND.cmd)}
23
+ disabled={!isConnected}
24
+ className={cn(
25
+ 'p-1.5 rounded transition-colors',
26
+ isConnected
27
+ ? 'text-orange-500 hover:bg-orange-500/10'
28
+ : 'text-muted-foreground/50 cursor-not-allowed'
29
+ )}
30
+ >
31
+ <SYNC_COMMAND.icon className="w-4 h-4" />
32
+ </button>
33
+ </TooltipTrigger>
34
+ <TooltipContent>{SYNC_COMMAND.tip}</TooltipContent>
35
+ </Tooltip>
36
+
37
+ <div className="w-px h-4 bg-border mx-1" />
38
+
39
+ {COMMAND_GROUPS.map((group, groupIdx) => (
40
+ <div key={group} className="flex items-center">
41
+ {WORKFLOW_COMMANDS.filter(c => c.group === group).map(({ cmd, icon: Icon, tip }) => (
42
+ <Tooltip key={cmd}>
43
+ <TooltipTrigger asChild>
44
+ <button
45
+ onClick={() => onCommand(cmd)}
46
+ disabled={!isConnected}
47
+ className={cn(
48
+ 'p-1.5 rounded transition-colors',
49
+ isConnected
50
+ ? 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
51
+ : 'text-muted-foreground/50 cursor-not-allowed'
52
+ )}
53
+ >
54
+ <Icon className="w-3.5 h-3.5" />
55
+ </button>
56
+ </TooltipTrigger>
57
+ <TooltipContent>{tip}</TooltipContent>
58
+ </Tooltip>
59
+ ))}
60
+ {groupIdx < COMMAND_GROUPS.length - 1 && (
61
+ <div className="w-px h-4 bg-border mx-0.5" />
62
+ )}
63
+ </div>
64
+ ))}
65
+ </div>
66
+ )
67
+ }
@@ -0,0 +1 @@
1
+ export { CommandBar } from './CommandBar'
@@ -5,7 +5,7 @@ import Link from 'next/link'
5
5
  import { useRouter } from 'next/navigation'
6
6
  import { SessionsChart } from '@/components/charts/SessionsChart'
7
7
  import { Button } from '@/components/ui/button'
8
- import { ProjectAvatar } from '@/components/ProjectAvatar'
8
+ import { ProgressRing } from '@/components/ProgressRing'
9
9
  import { TechStackBadges } from '@/components/TechStackBadges'
10
10
  import { formatRelativeTime, formatPath } from '@/lib/format'
11
11
  import {
@@ -16,7 +16,9 @@ import {
16
16
  Lightbulb,
17
17
  ListTodo,
18
18
  Zap,
19
- FolderGit2
19
+ FolderGit2,
20
+ Play,
21
+ FileText
20
22
  } from 'lucide-react'
21
23
  import {
22
24
  DropdownMenu,
@@ -36,6 +38,14 @@ import {
36
38
  } from '@/components/ui/alert-dialog'
37
39
  import { deleteProject } from '@/lib/actions/projects'
38
40
 
41
+ // Traffic light colors based on completion rate
42
+ type AccentColor = 'default' | 'success' | 'warning' | 'destructive'
43
+ function getCompletionColor(rate: number): AccentColor {
44
+ if (rate >= 75) return 'success'
45
+ if (rate >= 25) return 'warning'
46
+ return 'destructive'
47
+ }
48
+
39
49
  export interface Project {
40
50
  id: string
41
51
  name: string
@@ -46,6 +56,7 @@ export interface Project {
46
56
  lastActivity?: string | null
47
57
  ideasCount?: number
48
58
  nextTasksCount?: number
59
+ shippedCount?: number
49
60
  techStack?: string[]
50
61
  iconPath?: string | null
51
62
  version?: string
@@ -161,13 +172,20 @@ interface ProjectCardProps {
161
172
  function ProjectCard({ project, onDeleteClick }: ProjectCardProps) {
162
173
  const hasStats = project.currentTask || project.nextTasksCount || project.ideasCount || project.lastActivity
163
174
 
175
+ // Calculate completion rate
176
+ const shipped = project.shippedCount ?? 0
177
+ const pending = project.nextTasksCount ?? 0
178
+ const total = shipped + pending
179
+ const completionRate = total > 0 ? Math.round((shipped / total) * 100) : 0
180
+ const completionColor = getCompletionColor(completionRate)
181
+
164
182
  return (
165
183
  <div className="group relative bg-card border border-border rounded-lg overflow-hidden hover:border-primary/50 active:scale-[0.99] transition-all">
166
184
  {project.hasActiveSession && <div className="absolute top-0 left-0 right-0 h-0.5 bg-green-500" />}
167
185
 
168
186
  <Link href={`/project/${project.id}`} className="block p-3 sm:p-4">
169
187
  <div className="flex items-start gap-3">
170
- <ProjectAvatar projectId={project.id} name={project.name} iconPath={project.iconPath} size="lg" />
188
+ <ProgressRing value={completionRate} size="md" accentColor={completionColor} className="shrink-0" />
171
189
 
172
190
  <div className="flex-1 min-w-0">
173
191
  <div className="flex items-center gap-2">
@@ -180,7 +198,7 @@ function ProjectCard({ project, onDeleteClick }: ProjectCardProps) {
180
198
  )}
181
199
  </div>
182
200
  {project.repoPath && (
183
- <p className="text-[11px] sm:text-xs text-muted-foreground truncate mt-0.5 flex items-center gap-1">
201
+ <p className="text-xs text-muted-foreground truncate mt-0.5 flex items-center gap-1">
184
202
  <FolderGit2 className="w-3 h-3 shrink-0" />
185
203
  {formatPath(project.repoPath)}
186
204
  </p>
@@ -200,6 +218,18 @@ function ProjectCard({ project, onDeleteClick }: ProjectCardProps) {
200
218
  </Button>
201
219
  </DropdownMenuTrigger>
202
220
  <DropdownMenuContent align="end">
221
+ <DropdownMenuItem asChild className="min-h-[44px] sm:min-h-0">
222
+ <Link href={`/project/${project.id}/code`}>
223
+ <Play className="w-4 h-4 mr-2" />
224
+ Start working
225
+ </Link>
226
+ </DropdownMenuItem>
227
+ <DropdownMenuItem asChild className="min-h-[44px] sm:min-h-0">
228
+ <Link href={`/project/${project.id}/reports`}>
229
+ <FileText className="w-4 h-4 mr-2" />
230
+ Reports
231
+ </Link>
232
+ </DropdownMenuItem>
203
233
  <DropdownMenuItem
204
234
  className="text-destructive focus:text-destructive min-h-[44px] sm:min-h-0"
205
235
  onClick={(e: React.MouseEvent) => onDeleteClick(project, e)}
@@ -221,7 +251,7 @@ function ProjectCard({ project, onDeleteClick }: ProjectCardProps) {
221
251
  )}
222
252
 
223
253
  {hasStats && (
224
- <div className="mt-3 flex flex-wrap items-center gap-2 sm:gap-3 text-[11px] sm:text-xs text-muted-foreground">
254
+ <div className="mt-3 flex flex-wrap items-center gap-2 sm:gap-3 text-xs text-muted-foreground">
225
255
  {(project.nextTasksCount ?? 0) > 0 && (
226
256
  <div className="flex items-center gap-1">
227
257
  <ListTodo className="w-3.5 h-3.5" />
@@ -5,7 +5,7 @@ import type { DateGroupProps } from './DateGroup.types'
5
5
  export function DateGroup({ dateKey, events }: DateGroupProps) {
6
6
  return (
7
7
  <div>
8
- <p className="text-[11px] sm:text-[10px] font-bold uppercase tracking-wider text-muted-foreground mb-2">
8
+ <p className="text-xs font-bold uppercase tracking-wider text-muted-foreground mb-2">
9
9
  {formatTimelineDate(dateKey + 'T00:00:00')}
10
10
  </p>
11
11
  <div className="space-y-1">
@@ -1,7 +1,8 @@
1
1
  'use client'
2
2
 
3
+ import Link from 'next/link'
3
4
  import { cn } from '@/lib/utils'
4
- import { Copy } from 'lucide-react'
5
+ import { ArrowRight } from 'lucide-react'
5
6
  import type { EmptyStateProps } from './EmptyState.types'
6
7
 
7
8
  export function EmptyState({
@@ -9,28 +10,34 @@ export function EmptyState({
9
10
  title,
10
11
  description,
11
12
  command,
13
+ href,
12
14
  className,
13
15
  compact = false,
14
16
  }: EmptyStateProps) {
15
- const handleCopy = () => {
16
- if (command) {
17
- navigator.clipboard.writeText(command)
18
- }
19
- }
17
+ // Command button content
18
+ const CommandButton = href ? (
19
+ <Link
20
+ href={href}
21
+ className="inline-flex items-center gap-1.5 px-2 py-1 rounded-md bg-emerald-500/10 text-xs font-mono text-emerald-600 dark:text-emerald-400 hover:bg-emerald-500/20 transition-colors group border border-emerald-500/20"
22
+ >
23
+ {command}
24
+ <ArrowRight className="h-3 w-3 group-hover:translate-x-0.5 transition-transform" />
25
+ </Link>
26
+ ) : command ? (
27
+ <button
28
+ onClick={() => navigator.clipboard.writeText(command)}
29
+ className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-muted text-xs font-mono hover:bg-muted/80"
30
+ >
31
+ {command}
32
+ </button>
33
+ ) : null
20
34
 
21
35
  if (compact) {
22
36
  return (
23
37
  <div className={cn('flex items-center gap-2 text-muted-foreground', className)}>
24
38
  <Icon className="h-4 w-4" />
25
39
  <span className="text-sm">{title}</span>
26
- {command && (
27
- <button
28
- onClick={handleCopy}
29
- className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-muted text-xs font-mono hover:bg-muted/80"
30
- >
31
- {command}
32
- </button>
33
- )}
40
+ {CommandButton}
34
41
  </div>
35
42
  )
36
43
  }
@@ -45,13 +52,24 @@ export function EmptyState({
45
52
  <p className="text-xs text-muted-foreground/70 mt-1">{description}</p>
46
53
  )}
47
54
  {command && (
48
- <button
49
- onClick={handleCopy}
50
- className="mt-2 inline-flex items-center gap-1.5 px-2 py-1 rounded-md bg-muted text-xs font-mono text-muted-foreground hover:bg-muted/80 hover:text-foreground transition-colors group"
51
- >
52
- {command}
53
- <Copy className="h-3 w-3 opacity-0 group-hover:opacity-100 transition-opacity" />
54
- </button>
55
+ <div className="mt-2">
56
+ {href ? (
57
+ <Link
58
+ href={href}
59
+ className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md bg-emerald-500/10 text-xs font-mono text-emerald-600 dark:text-emerald-400 hover:bg-emerald-500/20 transition-colors group border border-emerald-500/20"
60
+ >
61
+ {command}
62
+ <ArrowRight className="h-3 w-3 group-hover:translate-x-0.5 transition-transform" />
63
+ </Link>
64
+ ) : (
65
+ <button
66
+ onClick={() => navigator.clipboard.writeText(command)}
67
+ className="inline-flex items-center gap-1.5 px-2 py-1 rounded-md bg-muted text-xs font-mono text-muted-foreground hover:bg-muted/80 hover:text-foreground transition-colors"
68
+ >
69
+ {command}
70
+ </button>
71
+ )}
72
+ </div>
55
73
  )}
56
74
  </div>
57
75
  )
@@ -5,6 +5,7 @@ export interface EmptyStateProps {
5
5
  title: string
6
6
  description?: string
7
7
  command?: string
8
+ href?: string
8
9
  className?: string
9
10
  compact?: boolean
10
11
  }
@@ -17,7 +17,7 @@ export function EventRow({ event }: EventRowProps) {
17
17
  return (
18
18
  <div className="flex items-center gap-2 sm:gap-3 py-2 sm:py-1.5 px-2 -mx-2 rounded-md hover:bg-muted/50 active:bg-muted/70 transition-colors group">
19
19
  {event.ts && (
20
- <span className="hidden sm:block text-[10px] text-muted-foreground w-14 shrink-0 tabular-nums">
20
+ <span className="hidden sm:block text-xs text-muted-foreground w-14 shrink-0 tabular-nums">
21
21
  {formatTime(event.ts)}
22
22
  </span>
23
23
  )}
@@ -29,18 +29,18 @@ export function EventRow({ event }: EventRowProps) {
29
29
  {getEventLabel(event)}
30
30
  </span>
31
31
  {event.ts && (
32
- <span className="sm:hidden text-[10px] text-muted-foreground">
32
+ <span className="sm:hidden text-xs text-muted-foreground">
33
33
  {formatTime(event.ts)}
34
34
  </span>
35
35
  )}
36
36
  </div>
37
37
 
38
- <span className="text-[9px] font-bold tracking-wider text-muted-foreground shrink-0">
38
+ <span className="text-xs font-bold tracking-wider text-muted-foreground shrink-0">
39
39
  {getEventBadge(event.type)}
40
40
  </span>
41
41
 
42
42
  {duration && (
43
- <span className="text-[10px] text-muted-foreground shrink-0">
43
+ <span className="text-xs text-muted-foreground shrink-0">
44
44
  {duration}
45
45
  </span>
46
46
  )}
@@ -17,9 +17,9 @@ export function getEventIconName(type: string): EventIconName {
17
17
 
18
18
  export function getEventColor(type: string): string {
19
19
  const colorMap: Record<string, string> = {
20
- task_complete: 'text-emerald-500',
21
- task_start: 'text-amber-500',
22
- feature_ship: 'text-blue-500',
20
+ task_complete: 'text-muted-foreground',
21
+ task_start: 'text-muted-foreground',
22
+ feature_ship: 'text-muted-foreground',
23
23
  sync: 'text-muted-foreground',
24
24
  }
25
25
  return colorMap[type] ?? 'text-muted-foreground'
@@ -1,53 +1,90 @@
1
1
  'use client'
2
2
 
3
+ import Link from 'next/link'
4
+ import { Play, FileText, Flame } from 'lucide-react'
3
5
  import { ProgressRing } from '@/components/ProgressRing'
4
- import { BackLink } from '@/components/BackLink'
5
6
  import { TasksCounter } from '@/components/TasksCounter'
6
- import { VelocityBadge } from '@/components/VelocityBadge'
7
7
  import { InsightMessage } from '@/components/InsightMessage'
8
8
  import { WeeklySparkline } from '@/components/WeeklySparkline'
9
9
  import { HealthGradientBackground } from '@/components/HealthGradientBackground'
10
10
  import { useCountUp, useWeeklyActivity } from './hooks'
11
- import { getHealthColor } from './HeroSection.utils'
11
+ import { getCompletionColor } from './HeroSection.utils'
12
12
  import type { HeroProps } from './HeroSection.types'
13
13
 
14
14
  export function HeroSection({
15
15
  projectId,
16
16
  projectName,
17
- tasksCompleted,
18
- healthScore,
19
- velocityChange,
17
+ projectVersion,
18
+ totalShips,
19
+ completionRate,
20
+ streak,
20
21
  insightMessage,
21
22
  timeline = [],
22
23
  }: HeroProps) {
23
- const animatedCount = useCountUp(tasksCompleted)
24
+ const animatedCount = useCountUp(totalShips)
24
25
  const weeklyData = useWeeklyActivity(timeline)
25
- const healthColor = getHealthColor(healthScore)
26
+ const completionColor = getCompletionColor(completionRate)
26
27
 
27
28
  return (
28
29
  <div className="relative mb-6 md:mb-8">
29
- <HealthGradientBackground score={healthScore} />
30
- <BackLink projectId={projectId} projectName={projectName} className="md:hidden mb-4" />
30
+ <HealthGradientBackground score={completionRate} />
31
+
32
+ <div className="relative z-10 flex items-center justify-between my-16">
33
+ <div className="flex items-baseline gap-3">
34
+ <h1 className="text-5xl md:text-6xl font-bold tracking-tight">{projectName}</h1>
35
+ {projectVersion && (
36
+ <span className="text-lg md:text-xl font-mono text-muted-foreground">v{projectVersion}</span>
37
+ )}
38
+ </div>
39
+
40
+ <div className="flex items-center gap-3">
41
+ {/* Reports Link */}
42
+ <Link
43
+ href={`/project/${projectId}/reports`}
44
+ className="inline-flex items-center gap-2 px-4 py-3 bg-muted hover:bg-muted/80 text-foreground font-medium rounded-xl transition-all duration-200 hover:scale-105"
45
+ >
46
+ <FileText className="h-5 w-5" />
47
+ <span>Reports</span>
48
+ </Link>
49
+
50
+ {/* Start Working CTA */}
51
+ <Link
52
+ href={`/project/${projectId}/code`}
53
+ className="group relative inline-flex items-center gap-2 px-6 py-3 bg-emerald-500 hover:bg-emerald-600 text-white font-semibold rounded-xl shadow-lg shadow-emerald-500/25 hover:shadow-emerald-500/40 transition-all duration-200 hover:scale-105"
54
+ >
55
+ <Play className="h-5 w-5 fill-current" />
56
+ <span>Start Working</span>
57
+ <div className="absolute inset-0 rounded-xl bg-gradient-to-r from-emerald-400 to-emerald-600 opacity-0 group-hover:opacity-100 transition-opacity -z-10" />
58
+ </Link>
59
+ </div>
60
+ </div>
31
61
 
32
62
  <div className="relative flex flex-col md:flex-row md:items-start md:justify-between gap-4 md:gap-0">
33
63
  <div className="flex items-center md:items-start gap-4 md:gap-6">
34
64
  <ProgressRing
35
- value={healthScore}
65
+ value={completionRate}
36
66
  size="xl"
37
- accentColor={healthColor}
67
+ accentColor={completionColor}
38
68
  className="shrink-0"
39
69
  />
40
70
 
41
71
  <div className="flex-1 min-w-0">
42
72
  <TasksCounter count={animatedCount} />
43
- <VelocityBadge change={velocityChange} />
73
+ {/* Streak badge */}
74
+ {streak > 0 && (
75
+ <div className="flex items-center gap-1.5 mt-1">
76
+ <Flame className="h-4 w-4 text-muted-foreground" />
77
+ <span className="text-sm font-medium text-muted-foreground">
78
+ {streak} day streak
79
+ </span>
80
+ </div>
81
+ )}
44
82
  <InsightMessage message={insightMessage} />
45
83
  </div>
46
84
  </div>
47
85
 
48
86
  <div className="flex flex-col items-start md:items-end gap-3 md:gap-4">
49
- <BackLink projectId={projectId} projectName={projectName} className="hidden md:flex" />
50
- <WeeklySparkline data={weeklyData} />
87
+ <WeeklySparkline data={weeklyData} size="lg" />
51
88
  </div>
52
89
  </div>
53
90
  </div>
@@ -5,10 +5,10 @@ export type { TimelineEvent }
5
5
  export interface HeroProps {
6
6
  projectId: string
7
7
  projectName: string
8
- tasksCompleted: number
9
- healthScore: number
10
- velocity: number
11
- velocityChange: number
8
+ projectVersion?: string | null
9
+ totalShips: number
10
+ completionRate: number
11
+ streak: number
12
12
  insightMessage: string
13
13
  timeline?: TimelineEvent[]
14
14
  }
@@ -1,7 +1,11 @@
1
1
  export type AccentColor = 'default' | 'success' | 'warning' | 'destructive'
2
2
 
3
- export function getHealthColor(score: number): AccentColor {
4
- if (score >= 70) return 'success'
5
- if (score >= 40) return 'warning'
3
+ // Completion rate color: traffic light based on project progress
4
+ // Green >= 75% (almost done!)
5
+ // Yellow 25-74% (good progress)
6
+ // Red < 25% (just getting started)
7
+ export function getCompletionColor(rate: number): AccentColor {
8
+ if (rate >= 75) return 'success'
9
+ if (rate >= 25) return 'warning'
6
10
  return 'destructive'
7
11
  }
@@ -1,48 +1,115 @@
1
- import { BentoCard } from '@/components/BentoCard'
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import Link from 'next/link'
2
5
  import { EmptyState } from '@/components/EmptyState'
3
- import { Lightbulb } from 'lucide-react'
6
+ import { ExpandButton } from '@/components/ExpandButton'
7
+ import { Lightbulb, Sparkles, Rocket, X } from 'lucide-react'
4
8
  import { cn } from '@/lib/utils'
5
9
  import type { IdeasCardProps } from './IdeasCard.types'
6
10
 
7
- export function IdeasCard({ ideas, className }: IdeasCardProps) {
8
- const displayIdeas = ideas.slice(0, 4)
11
+ const COLLAPSED_LIMIT = 8
12
+ const EXPANDED_LIMIT = 30
13
+
14
+ export function IdeasCard({ ideas, codeHref, className }: IdeasCardProps) {
15
+ const [expanded, setExpanded] = useState(false)
16
+ const limit = expanded ? EXPANDED_LIMIT : COLLAPSED_LIMIT
17
+ const displayIdeas = ideas.slice(0, limit)
9
18
  const highImpactCount = ideas.filter(i => i.impact === 'HIGH').length
19
+ const hasMore = ideas.length > limit
10
20
 
11
21
  return (
12
- <BentoCard
13
- size="1x1"
14
- title="Ideas"
15
- icon={Lightbulb}
16
- count={ideas.length}
17
- className={className}
18
- >
22
+ <div className={cn(
23
+ 'relative overflow-hidden rounded-xl border bg-card p-4',
24
+ className
25
+ )}>
26
+ <div className="flex items-center justify-between mb-3">
27
+ <div className="flex items-center gap-2">
28
+ <Lightbulb className="h-4 w-4 text-muted-foreground" />
29
+ <span className="text-xs font-bold uppercase tracking-[0.15em] text-muted-foreground">
30
+ Ideas
31
+ </span>
32
+ </div>
33
+ <div className="flex items-center gap-2">
34
+ {highImpactCount > 0 && (
35
+ <span className="text-xs font-medium text-muted-foreground bg-muted px-1.5 py-0.5 rounded">
36
+ {highImpactCount} high impact
37
+ </span>
38
+ )}
39
+ <span className="text-xs font-medium text-muted-foreground tabular-nums">
40
+ {ideas.length}
41
+ </span>
42
+ </div>
43
+ </div>
44
+
19
45
  {ideas.length === 0 ? (
20
46
  <EmptyState
21
47
  icon={Lightbulb}
22
48
  title="No ideas yet"
23
49
  command="/p:idea"
50
+ href={codeHref}
24
51
  compact
25
52
  />
26
53
  ) : (
27
- <div className="space-y-1.5">
28
- {displayIdeas.map((idea, i) => (
29
- <div key={i} className="flex items-start gap-2">
30
- <Lightbulb
31
- className={cn(
32
- 'h-3 w-3 mt-0.5 shrink-0',
33
- idea.impact === 'HIGH' ? 'text-amber-500' : 'text-muted-foreground'
54
+ <div className="space-y-1">
55
+ {displayIdeas.map((idea, i) => {
56
+ const featureHref = codeHref
57
+ ? `${codeHref}?cmd=${encodeURIComponent(`p. feature "${idea.title}"`)}`
58
+ : undefined
59
+ const deleteHref = codeHref
60
+ ? `${codeHref}?cmd=${encodeURIComponent(`p. idea remove ${i + 1}`)}`
61
+ : undefined
62
+
63
+ return (
64
+ <div
65
+ key={i}
66
+ className="flex items-start gap-2 py-1.5 group hover:bg-muted/50 rounded px-1 -mx-1"
67
+ >
68
+ {idea.impact === 'HIGH' ? (
69
+ <Sparkles className="h-3 w-3 mt-0.5 shrink-0 text-foreground" />
70
+ ) : (
71
+ <Lightbulb className="h-3 w-3 mt-0.5 shrink-0 text-muted-foreground" />
34
72
  )}
35
- />
36
- <p className="text-sm truncate">{idea.title}</p>
37
- </div>
38
- ))}
39
- {highImpactCount > 0 && (
40
- <p className="text-[10px] text-amber-600 dark:text-amber-400 mt-2 pl-5 font-medium">
41
- {highImpactCount} high impact
42
- </p>
73
+ <p className={cn(
74
+ 'text-sm flex-1 min-w-0',
75
+ idea.impact === 'HIGH' && 'font-medium'
76
+ )}>
77
+ {idea.title}
78
+ </p>
79
+ {/* Always visible action buttons */}
80
+ <div className="flex items-center gap-1 shrink-0">
81
+ {featureHref && (
82
+ <Link
83
+ href={featureHref}
84
+ className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
85
+ title="Convert to feature"
86
+ >
87
+ <Rocket className="h-3.5 w-3.5" />
88
+ </Link>
89
+ )}
90
+ {deleteHref && (
91
+ <Link
92
+ href={deleteHref}
93
+ className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
94
+ title="Delete idea"
95
+ >
96
+ <X className="h-3.5 w-3.5" />
97
+ </Link>
98
+ )}
99
+ </div>
100
+ </div>
101
+ )
102
+ })}
103
+ {(hasMore || expanded) && ideas.length > COLLAPSED_LIMIT && (
104
+ <ExpandButton
105
+ expanded={expanded}
106
+ totalCount={ideas.length}
107
+ collapsedLimit={COLLAPSED_LIMIT}
108
+ onToggle={() => setExpanded(!expanded)}
109
+ />
43
110
  )}
44
111
  </div>
45
112
  )}
46
- </BentoCard>
113
+ </div>
47
114
  )
48
115
  }
@@ -5,5 +5,6 @@ export interface Idea {
5
5
 
6
6
  export interface IdeasCardProps {
7
7
  ideas: Idea[]
8
+ codeHref?: string
8
9
  className?: string
9
10
  }
@@ -0,0 +1,18 @@
1
+ import { cn } from '@/lib/utils'
2
+
3
+ interface MasonryGridProps {
4
+ children: React.ReactNode
5
+ className?: string
6
+ }
7
+
8
+ export function MasonryGrid({ children, className }: MasonryGridProps) {
9
+ return (
10
+ <div className={cn(
11
+ 'columns-1 sm:columns-2 xl:columns-3 gap-4 max-w-full overflow-x-hidden',
12
+ '[&>*]:mb-4 [&>*]:break-inside-avoid',
13
+ className
14
+ )}>
15
+ {children}
16
+ </div>
17
+ )
18
+ }
@@ -0,0 +1 @@
1
+ export { MasonryGrid } from './MasonryGrid'