prjct-cli 0.13.2 → 0.15.0
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.
- package/CHANGELOG.md +106 -0
- package/bin/prjct +10 -13
- package/core/agentic/memory-system/semantic-memories.ts +2 -1
- package/core/agentic/plan-mode/plan-mode.ts +2 -1
- package/core/agentic/prompt-builder.ts +22 -43
- package/core/agentic/services.ts +5 -5
- package/core/agentic/smart-context.ts +7 -2
- package/core/command-registry/core-commands.ts +54 -29
- package/core/command-registry/optional-commands.ts +64 -0
- package/core/command-registry/setup-commands.ts +18 -3
- package/core/commands/analysis.ts +21 -68
- package/core/commands/analytics.ts +247 -213
- package/core/commands/base.ts +1 -1
- package/core/commands/index.ts +41 -36
- package/core/commands/maintenance.ts +300 -31
- package/core/commands/planning.ts +233 -22
- package/core/commands/setup.ts +3 -8
- package/core/commands/shipping.ts +14 -18
- package/core/commands/types.ts +8 -6
- package/core/commands/workflow.ts +105 -100
- package/core/context/generator.ts +317 -0
- package/core/context-sync.ts +7 -350
- package/core/data/index.ts +13 -32
- package/core/data/md-ideas-manager.ts +155 -0
- package/core/data/md-queue-manager.ts +4 -3
- package/core/data/md-shipped-manager.ts +90 -0
- package/core/data/md-state-manager.ts +11 -7
- package/core/domain/agent-generator.ts +23 -63
- package/core/events/index.ts +143 -0
- package/core/index.ts +17 -14
- package/core/infrastructure/capability-installer.ts +13 -149
- package/core/infrastructure/migrator/project-scanner.ts +2 -1
- package/core/infrastructure/path-manager.ts +4 -6
- package/core/infrastructure/setup.ts +3 -0
- package/core/infrastructure/uuid-migration.ts +750 -0
- package/core/outcomes/recorder.ts +2 -1
- package/core/plugin/loader.ts +4 -7
- package/core/plugin/registry.ts +3 -3
- package/core/schemas/index.ts +23 -25
- package/core/schemas/state.ts +1 -0
- package/core/serializers/ideas-serializer.ts +187 -0
- package/core/serializers/index.ts +16 -0
- package/core/serializers/shipped-serializer.ts +108 -0
- package/core/session/utils.ts +3 -9
- package/core/storage/ideas-storage.ts +273 -0
- package/core/storage/index.ts +204 -0
- package/core/storage/queue-storage.ts +297 -0
- package/core/storage/shipped-storage.ts +223 -0
- package/core/storage/state-storage.ts +235 -0
- package/core/storage/storage-manager.ts +175 -0
- package/package.json +1 -1
- package/packages/web/app/api/projects/[id]/momentum/route.ts +257 -0
- package/packages/web/app/api/sessions/current/route.ts +132 -0
- package/packages/web/app/api/sessions/history/route.ts +96 -14
- package/packages/web/app/globals.css +5 -0
- package/packages/web/app/layout.tsx +2 -0
- package/packages/web/app/project/[id]/code/layout.tsx +18 -0
- package/packages/web/app/project/[id]/code/page.tsx +408 -0
- package/packages/web/app/project/[id]/page.tsx +359 -389
- package/packages/web/app/project/[id]/reports/page.tsx +59 -0
- package/packages/web/app/project/[id]/reports/print/page.tsx +58 -0
- package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -1
- package/packages/web/components/AgentsCard/AgentsCard.tsx +64 -34
- package/packages/web/components/AgentsCard/AgentsCard.types.ts +1 -0
- package/packages/web/components/AppSidebar/AppSidebar.tsx +135 -11
- package/packages/web/components/BentoCard/BentoCard.constants.ts +3 -3
- package/packages/web/components/BentoCard/BentoCard.tsx +2 -1
- package/packages/web/components/BentoGrid/BentoGrid.tsx +2 -2
- package/packages/web/components/BlockersCard/BlockersCard.tsx +65 -57
- package/packages/web/components/BlockersCard/BlockersCard.types.ts +1 -0
- package/packages/web/components/CommandBar/CommandBar.tsx +67 -0
- package/packages/web/components/CommandBar/index.ts +1 -0
- package/packages/web/components/DashboardContent/DashboardContent.tsx +35 -5
- package/packages/web/components/DateGroup/DateGroup.tsx +1 -1
- package/packages/web/components/EmptyState/EmptyState.tsx +39 -21
- package/packages/web/components/EmptyState/EmptyState.types.ts +1 -0
- package/packages/web/components/EventRow/EventRow.tsx +4 -4
- package/packages/web/components/EventRow/EventRow.utils.ts +3 -3
- package/packages/web/components/HeroSection/HeroSection.tsx +52 -15
- package/packages/web/components/HeroSection/HeroSection.types.ts +4 -4
- package/packages/web/components/HeroSection/HeroSection.utils.ts +7 -3
- package/packages/web/components/IdeasCard/IdeasCard.tsx +94 -27
- package/packages/web/components/IdeasCard/IdeasCard.types.ts +1 -0
- package/packages/web/components/MasonryGrid/MasonryGrid.tsx +18 -0
- package/packages/web/components/MasonryGrid/index.ts +1 -0
- package/packages/web/components/MomentumWidget/MomentumWidget.tsx +119 -0
- package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +16 -0
- package/packages/web/components/MomentumWidget/index.ts +2 -0
- package/packages/web/components/NowCard/NowCard.tsx +81 -56
- package/packages/web/components/NowCard/NowCard.types.ts +1 -0
- package/packages/web/components/PageHeader/PageHeader.tsx +24 -0
- package/packages/web/components/PageHeader/index.ts +1 -0
- package/packages/web/components/ProgressRing/ProgressRing.constants.ts +2 -2
- package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +2 -2
- package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +37 -0
- package/packages/web/components/ProjectColorDot/index.ts +1 -0
- package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +104 -0
- package/packages/web/components/ProjectSelectorModal/index.ts +1 -0
- package/packages/web/components/Providers/Providers.tsx +4 -1
- package/packages/web/components/QueueCard/QueueCard.tsx +78 -25
- package/packages/web/components/QueueCard/QueueCard.types.ts +1 -0
- package/packages/web/components/QueueCard/QueueCard.utils.ts +3 -3
- package/packages/web/components/RecoverCard/RecoverCard.tsx +72 -0
- package/packages/web/components/RecoverCard/RecoverCard.types.ts +16 -0
- package/packages/web/components/RecoverCard/index.ts +2 -0
- package/packages/web/components/RoadmapCard/RoadmapCard.tsx +101 -33
- package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +1 -0
- package/packages/web/components/ShipsCard/ShipsCard.tsx +71 -28
- package/packages/web/components/ShipsCard/ShipsCard.types.ts +2 -0
- package/packages/web/components/SparklineChart/SparklineChart.tsx +20 -18
- package/packages/web/components/StatsMasonry/StatsMasonry.tsx +95 -0
- package/packages/web/components/StatsMasonry/index.ts +1 -0
- package/packages/web/components/StreakCard/StreakCard.tsx +37 -35
- package/packages/web/components/TasksCounter/TasksCounter.tsx +1 -1
- package/packages/web/components/TechStackBadges/TechStackBadges.tsx +12 -4
- package/packages/web/components/TerminalDock/DockToggleTab.tsx +29 -0
- package/packages/web/components/TerminalDock/TerminalDock.tsx +386 -0
- package/packages/web/components/TerminalDock/TerminalDockTab.tsx +130 -0
- package/packages/web/components/TerminalDock/TerminalTabBar.tsx +142 -0
- package/packages/web/components/TerminalDock/index.ts +2 -0
- package/packages/web/components/VelocityBadge/VelocityBadge.tsx +8 -3
- package/packages/web/components/VelocityCard/VelocityCard.tsx +49 -47
- package/packages/web/components/WeeklyReports/PrintableReport.tsx +259 -0
- package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +187 -0
- package/packages/web/components/WeeklyReports/WeekCalendar.tsx +288 -0
- package/packages/web/components/WeeklyReports/WeeklyReports.tsx +149 -0
- package/packages/web/components/WeeklyReports/index.ts +4 -0
- package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +16 -4
- package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +1 -0
- package/packages/web/components/charts/SessionsChart.tsx +6 -3
- package/packages/web/components/ui/dialog.tsx +143 -0
- package/packages/web/components/ui/drawer.tsx +135 -0
- package/packages/web/components/ui/select.tsx +187 -0
- package/packages/web/context/GlobalTerminalContext.tsx +538 -0
- package/packages/web/lib/commands.ts +81 -0
- package/packages/web/lib/generate-week-report.ts +285 -0
- package/packages/web/lib/parse-prjct-files.ts +56 -55
- package/packages/web/lib/project-colors.ts +58 -0
- package/packages/web/lib/projects.ts +58 -5
- package/packages/web/lib/services/projects.server.ts +11 -1
- package/packages/web/next-env.d.ts +1 -1
- package/packages/web/package.json +5 -1
- package/templates/commands/analyze.md +39 -3
- package/templates/commands/ask.md +58 -3
- package/templates/commands/bug.md +117 -26
- package/templates/commands/dash.md +95 -158
- package/templates/commands/done.md +130 -148
- package/templates/commands/feature.md +125 -103
- package/templates/commands/git.md +18 -3
- package/templates/commands/idea.md +121 -38
- package/templates/commands/init.md +124 -20
- package/templates/commands/migrate-all.md +63 -28
- package/templates/commands/migrate.md +140 -0
- package/templates/commands/next.md +115 -5
- package/templates/commands/now.md +146 -82
- package/templates/commands/pause.md +89 -74
- package/templates/commands/redo.md +6 -4
- package/templates/commands/resume.md +141 -59
- package/templates/commands/ship.md +103 -231
- package/templates/commands/spec.md +98 -8
- package/templates/commands/suggest.md +22 -2
- package/templates/commands/sync.md +192 -203
- package/templates/commands/undo.md +6 -4
- package/core/data/agents-manager.ts +0 -76
- package/core/data/analysis-manager.ts +0 -83
- package/core/data/base-manager.ts +0 -156
- package/core/data/ideas-manager.ts +0 -81
- package/core/data/outcomes-manager.ts +0 -96
- package/core/data/project-manager.ts +0 -75
- package/core/data/roadmap-manager.ts +0 -118
- package/core/data/shipped-manager.ts +0 -65
- package/core/data/state-manager.ts +0 -214
- package/core/state/index.ts +0 -25
- package/core/state/manager.ts +0 -376
- package/core/state/types.ts +0 -185
- package/core/utils/project-capabilities.ts +0 -156
- package/core/view-generator.ts +0 -536
- package/packages/web/app/project/[id]/stats/loading.tsx +0 -43
- package/packages/web/app/project/[id]/stats/page.tsx +0 -253
- package/templates/agent-assignment.md +0 -72
- package/templates/analysis/project-analysis.md +0 -78
- package/templates/checklists/accessibility.md +0 -33
- package/templates/commands/build.md +0 -17
- package/templates/commands/decision.md +0 -226
- package/templates/commands/fix.md +0 -79
- package/templates/commands/help.md +0 -61
- package/templates/commands/progress.md +0 -14
- package/templates/commands/recap.md +0 -14
- package/templates/commands/roadmap.md +0 -52
- package/templates/commands/status.md +0 -17
- package/templates/commands/task.md +0 -63
- package/templates/commands/work.md +0 -44
- package/templates/commands/workflow.md +0 -12
|
@@ -1,72 +1,125 @@
|
|
|
1
|
-
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import Link from 'next/link'
|
|
2
5
|
import { EmptyState } from '@/components/EmptyState'
|
|
3
|
-
import {
|
|
6
|
+
import { ExpandButton } from '@/components/ExpandButton'
|
|
7
|
+
import { ListTodo, Bot, Play, X } from 'lucide-react'
|
|
4
8
|
import { cn } from '@/lib/utils'
|
|
5
9
|
import { getPriorityColor } from './QueueCard.utils'
|
|
6
10
|
import type { QueueCardProps } from './QueueCard.types'
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
const COLLAPSED_LIMIT = 10
|
|
13
|
+
const EXPANDED_LIMIT = 50
|
|
14
|
+
|
|
15
|
+
export function QueueCard({ queue, codeHref, className }: QueueCardProps) {
|
|
16
|
+
const [expanded, setExpanded] = useState(false)
|
|
17
|
+
const limit = expanded ? EXPANDED_LIMIT : COLLAPSED_LIMIT
|
|
18
|
+
const displayItems = queue.slice(0, limit)
|
|
19
|
+
const hasMore = queue.length > limit
|
|
11
20
|
|
|
12
21
|
return (
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
+
<ListTodo className="h-4 w-4 text-muted-foreground" />
|
|
29
|
+
<span className="text-xs font-bold uppercase tracking-[0.15em] text-muted-foreground">
|
|
30
|
+
Queue
|
|
31
|
+
</span>
|
|
32
|
+
</div>
|
|
33
|
+
<span className="text-xs font-medium text-muted-foreground tabular-nums">
|
|
34
|
+
{queue.length} tasks
|
|
35
|
+
</span>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
20
38
|
{queue.length === 0 ? (
|
|
21
39
|
<EmptyState
|
|
22
40
|
icon={ListTodo}
|
|
23
41
|
title="Queue empty"
|
|
24
42
|
description="Plan your next tasks"
|
|
25
43
|
command="/p:next"
|
|
44
|
+
href={codeHref}
|
|
26
45
|
compact
|
|
27
46
|
/>
|
|
28
47
|
) : (
|
|
29
|
-
<div className="space-y-
|
|
48
|
+
<div className="space-y-1.5">
|
|
30
49
|
{displayItems.map((item, i) => {
|
|
31
50
|
const priorityColor = getPriorityColor(item.priority)
|
|
51
|
+
const startHref = codeHref
|
|
52
|
+
? `${codeHref}?cmd=${encodeURIComponent(`p. now "${item.task}"`)}`
|
|
53
|
+
: undefined
|
|
54
|
+
const deleteHref = codeHref
|
|
55
|
+
? `${codeHref}?cmd=${encodeURIComponent(`p. queue remove ${i + 1}`)}`
|
|
56
|
+
: undefined
|
|
57
|
+
|
|
32
58
|
return (
|
|
33
|
-
<div
|
|
59
|
+
<div
|
|
60
|
+
key={i}
|
|
61
|
+
className="flex items-start gap-2 group py-1.5 hover:bg-muted/50 rounded px-1 -mx-1"
|
|
62
|
+
>
|
|
34
63
|
<span className={cn(
|
|
35
|
-
"text-
|
|
64
|
+
"text-xs font-bold w-5 shrink-0 pt-0.5 tabular-nums",
|
|
36
65
|
priorityColor
|
|
37
66
|
)}>
|
|
38
|
-
{i + 1}
|
|
67
|
+
{i + 1}.
|
|
39
68
|
</span>
|
|
40
69
|
<div className="flex-1 min-w-0">
|
|
41
|
-
<p className="text-sm leading-tight
|
|
70
|
+
<p className="text-sm leading-tight">
|
|
42
71
|
{item.task}
|
|
43
72
|
</p>
|
|
44
73
|
{(item.suggestedAgent || item.estimatedDuration) && (
|
|
45
74
|
<div className="flex items-center gap-2 mt-0.5">
|
|
46
75
|
{item.suggestedAgent && (
|
|
47
|
-
<span className="inline-flex items-center gap-0.5 text-
|
|
48
|
-
<Bot className="h-
|
|
76
|
+
<span className="inline-flex items-center gap-0.5 text-xs text-muted-foreground">
|
|
77
|
+
<Bot className="h-3 w-3" />
|
|
49
78
|
{item.suggestedAgent}
|
|
50
79
|
</span>
|
|
51
80
|
)}
|
|
52
81
|
{item.estimatedDuration && (
|
|
53
|
-
<span className="text-
|
|
82
|
+
<span className="text-xs text-muted-foreground">
|
|
54
83
|
~{item.estimatedDuration}
|
|
55
84
|
</span>
|
|
56
85
|
)}
|
|
57
86
|
</div>
|
|
58
87
|
)}
|
|
59
88
|
</div>
|
|
89
|
+
{/* Always visible action buttons */}
|
|
90
|
+
<div className="flex items-center gap-1 shrink-0">
|
|
91
|
+
{startHref && (
|
|
92
|
+
<Link
|
|
93
|
+
href={startHref}
|
|
94
|
+
className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
|
|
95
|
+
title="Start now"
|
|
96
|
+
>
|
|
97
|
+
<Play className="h-3.5 w-3.5" />
|
|
98
|
+
</Link>
|
|
99
|
+
)}
|
|
100
|
+
{deleteHref && (
|
|
101
|
+
<Link
|
|
102
|
+
href={deleteHref}
|
|
103
|
+
className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
|
|
104
|
+
title="Remove from queue"
|
|
105
|
+
>
|
|
106
|
+
<X className="h-3.5 w-3.5" />
|
|
107
|
+
</Link>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
60
110
|
</div>
|
|
61
111
|
)
|
|
62
112
|
})}
|
|
63
|
-
{
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
113
|
+
{(hasMore || expanded) && queue.length > COLLAPSED_LIMIT && (
|
|
114
|
+
<ExpandButton
|
|
115
|
+
expanded={expanded}
|
|
116
|
+
totalCount={queue.length}
|
|
117
|
+
collapsedLimit={COLLAPSED_LIMIT}
|
|
118
|
+
onToggle={() => setExpanded(!expanded)}
|
|
119
|
+
/>
|
|
67
120
|
)}
|
|
68
121
|
</div>
|
|
69
122
|
)}
|
|
70
|
-
</
|
|
123
|
+
</div>
|
|
71
124
|
)
|
|
72
125
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export function getPriorityColor(priority?: 'low' | 'medium' | 'high' | 'critical' | number): string {
|
|
2
2
|
if (typeof priority === 'string') {
|
|
3
3
|
const colors: Record<string, string> = {
|
|
4
|
-
critical: 'text-
|
|
5
|
-
high: 'text-
|
|
6
|
-
medium: 'text-
|
|
4
|
+
critical: 'text-foreground font-bold',
|
|
5
|
+
high: 'text-foreground',
|
|
6
|
+
medium: 'text-muted-foreground',
|
|
7
7
|
low: 'text-muted-foreground',
|
|
8
8
|
}
|
|
9
9
|
return colors[priority] ?? 'text-muted-foreground'
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link'
|
|
4
|
+
import { AlertCircle, ChevronRight, Clock, MessageSquare } from 'lucide-react'
|
|
5
|
+
import { Badge } from '@/components/ui/badge'
|
|
6
|
+
import { cn } from '@/lib/utils'
|
|
7
|
+
import type { RecoverCardProps } from './RecoverCard.types'
|
|
8
|
+
|
|
9
|
+
export function RecoverCard({ abandonedSessions, codeHref, className }: RecoverCardProps) {
|
|
10
|
+
if (abandonedSessions.length === 0) return null
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className={cn(
|
|
14
|
+
'relative overflow-hidden rounded-xl border border-yellow-500/30 bg-yellow-500/5 p-4 min-w-0 max-w-full',
|
|
15
|
+
className
|
|
16
|
+
)}>
|
|
17
|
+
<div className="flex items-center justify-between mb-3 min-w-0">
|
|
18
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
19
|
+
<AlertCircle className="h-4 w-4 text-yellow-500 shrink-0" />
|
|
20
|
+
<span className="text-xs font-bold uppercase tracking-[0.15em] text-yellow-600 dark:text-yellow-500 truncate">
|
|
21
|
+
Recover
|
|
22
|
+
</span>
|
|
23
|
+
</div>
|
|
24
|
+
<Badge variant="outline" className="text-yellow-600 dark:text-yellow-500 border-yellow-500/50 shrink-0">
|
|
25
|
+
{abandonedSessions.length}
|
|
26
|
+
</Badge>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div className="space-y-2 min-w-0 max-w-full">
|
|
30
|
+
{abandonedSessions.slice(0, 3).map((session) => (
|
|
31
|
+
<Link
|
|
32
|
+
key={session.id}
|
|
33
|
+
href={`${codeHref}?cmd=p.%20recover`}
|
|
34
|
+
className="block py-2 px-2 -mx-2 hover:bg-yellow-500/10 rounded-lg transition-colors group"
|
|
35
|
+
>
|
|
36
|
+
<div className="flex items-start justify-between gap-2">
|
|
37
|
+
<div className="flex-1 min-w-0">
|
|
38
|
+
<p className="text-sm font-medium leading-tight truncate group-hover:text-foreground transition-colors">
|
|
39
|
+
{session.task}
|
|
40
|
+
</p>
|
|
41
|
+
<div className="flex items-center gap-2 mt-1 text-xs text-muted-foreground">
|
|
42
|
+
<Clock className="h-3 w-3 shrink-0" />
|
|
43
|
+
<span>{session.hoursAgo}h ago</span>
|
|
44
|
+
{session.projectName && (
|
|
45
|
+
<>
|
|
46
|
+
<span className="text-muted-foreground/50">|</span>
|
|
47
|
+
<span className="truncate">{session.projectName}</span>
|
|
48
|
+
</>
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
{session.prompt && (
|
|
52
|
+
<div className="flex items-start gap-1.5 mt-1.5">
|
|
53
|
+
<MessageSquare className="h-3 w-3 text-muted-foreground/70 shrink-0 mt-0.5" />
|
|
54
|
+
<p className="text-xs text-muted-foreground/70 italic line-clamp-2">
|
|
55
|
+
"{session.prompt.slice(0, 80)}{session.prompt.length > 80 ? '...' : ''}"
|
|
56
|
+
</p>
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
<ChevronRight className="h-4 w-4 text-muted-foreground/50 opacity-0 group-hover:opacity-100 transition-opacity shrink-0 mt-1" />
|
|
61
|
+
</div>
|
|
62
|
+
</Link>
|
|
63
|
+
))}
|
|
64
|
+
{abandonedSessions.length > 3 && (
|
|
65
|
+
<p className="text-xs text-yellow-600/70 dark:text-yellow-500/70 text-center pt-1">
|
|
66
|
+
+{abandonedSessions.length - 3} more abandoned sessions
|
|
67
|
+
</p>
|
|
68
|
+
)}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface AbandonedSession {
|
|
2
|
+
id: string
|
|
3
|
+
task: string
|
|
4
|
+
projectId: string
|
|
5
|
+
projectName?: string
|
|
6
|
+
startedAt: string
|
|
7
|
+
lastActivity?: string
|
|
8
|
+
hoursAgo: number
|
|
9
|
+
prompt?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface RecoverCardProps {
|
|
13
|
+
abandonedSessions: AbandonedSession[]
|
|
14
|
+
codeHref: string
|
|
15
|
+
className?: string
|
|
16
|
+
}
|
|
@@ -1,77 +1,145 @@
|
|
|
1
|
-
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link'
|
|
2
4
|
import { EmptyState } from '@/components/EmptyState'
|
|
3
|
-
import { Map } from 'lucide-react'
|
|
5
|
+
import { Map, ChevronRight, CheckCircle2, Circle } from 'lucide-react'
|
|
4
6
|
import { cn } from '@/lib/utils'
|
|
5
7
|
import type { RoadmapCardProps } from './RoadmapCard.types'
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
// Traffic light colors for progress: green=100%, yellow=50-99%, red=<50%
|
|
10
|
+
function getProgressColor(progress: number): string {
|
|
11
|
+
if (progress >= 100) return 'bg-emerald-500'
|
|
12
|
+
if (progress >= 50) return 'bg-amber-500'
|
|
13
|
+
return 'bg-red-500'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function RoadmapCard({ roadmap, codeHref, className }: RoadmapCardProps) {
|
|
8
17
|
const hasPhases = roadmap?.phases && roadmap.phases.length > 0
|
|
9
18
|
|
|
10
19
|
return (
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
<div className={cn(
|
|
21
|
+
'relative overflow-hidden rounded-xl border bg-card p-4',
|
|
22
|
+
className
|
|
23
|
+
)}>
|
|
24
|
+
<div className="flex items-center justify-between mb-3">
|
|
25
|
+
<div className="flex items-center gap-2">
|
|
26
|
+
<Map className="h-4 w-4 text-muted-foreground" />
|
|
27
|
+
<span className="text-xs font-bold uppercase tracking-[0.15em] text-muted-foreground">
|
|
28
|
+
Roadmap
|
|
29
|
+
</span>
|
|
30
|
+
</div>
|
|
31
|
+
{hasPhases && (
|
|
32
|
+
<span className="text-xs font-bold tabular-nums">
|
|
33
|
+
{roadmap.progress}%
|
|
34
|
+
</span>
|
|
35
|
+
)}
|
|
36
|
+
</div>
|
|
37
|
+
|
|
18
38
|
{!hasPhases ? (
|
|
19
39
|
<EmptyState
|
|
20
40
|
icon={Map}
|
|
21
41
|
title="No roadmap yet"
|
|
22
42
|
description="Plan your features"
|
|
23
43
|
command="/p:feature"
|
|
44
|
+
href={codeHref}
|
|
24
45
|
/>
|
|
25
46
|
) : (
|
|
26
|
-
<div className="
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
<span className="
|
|
47
|
+
<div className="space-y-4">
|
|
48
|
+
{/* Overall progress */}
|
|
49
|
+
<div>
|
|
50
|
+
<div className="flex items-center justify-between mb-2">
|
|
51
|
+
<span className="text-xs text-muted-foreground">Overall progress</span>
|
|
52
|
+
<span className="text-sm font-bold tabular-nums">{roadmap.progress}%</span>
|
|
31
53
|
</div>
|
|
32
|
-
<div className="h-
|
|
54
|
+
<div className="h-3 bg-muted rounded-full overflow-hidden">
|
|
33
55
|
<div
|
|
34
|
-
className={cn(
|
|
35
|
-
'h-full rounded-full transition-all duration-500',
|
|
36
|
-
roadmap.progress === 100 ? 'bg-emerald-500' : 'bg-foreground'
|
|
37
|
-
)}
|
|
56
|
+
className={cn("h-full rounded-full transition-all duration-500", getProgressColor(roadmap.progress))}
|
|
38
57
|
style={{ width: `${roadmap.progress}%` }}
|
|
39
58
|
/>
|
|
40
59
|
</div>
|
|
41
60
|
</div>
|
|
42
61
|
|
|
43
|
-
|
|
62
|
+
{/* Phases with features */}
|
|
63
|
+
<div className="space-y-4">
|
|
44
64
|
{roadmap.phases
|
|
45
65
|
.filter(p => (p.features || []).length > 0)
|
|
46
|
-
.slice(0,
|
|
66
|
+
.slice(0, 6)
|
|
47
67
|
.map((phase) => (
|
|
48
|
-
<div key={phase.name}>
|
|
49
|
-
<div className="flex items-center justify-between
|
|
50
|
-
<
|
|
51
|
-
|
|
68
|
+
<div key={phase.name} className="group">
|
|
69
|
+
<div className="flex items-center justify-between mb-2">
|
|
70
|
+
<div className="flex items-center gap-2">
|
|
71
|
+
{phase.progress === 100 ? (
|
|
72
|
+
<CheckCircle2 className="h-4 w-4 text-muted-foreground" />
|
|
73
|
+
) : (
|
|
74
|
+
<Circle className="h-4 w-4 text-muted-foreground" />
|
|
75
|
+
)}
|
|
76
|
+
<span className="text-sm font-medium">
|
|
77
|
+
{phase.name}
|
|
78
|
+
</span>
|
|
79
|
+
</div>
|
|
80
|
+
<span className="text-xs font-bold tabular-nums shrink-0 ml-2 text-muted-foreground">
|
|
52
81
|
{phase.progress}%
|
|
53
82
|
</span>
|
|
54
83
|
</div>
|
|
55
|
-
<div className="h-
|
|
84
|
+
<div className="h-2 bg-muted rounded-full overflow-hidden ml-6">
|
|
56
85
|
<div
|
|
57
|
-
className={cn(
|
|
58
|
-
'h-full rounded-full transition-all duration-300',
|
|
59
|
-
phase.progress === 100 ? 'bg-emerald-500' : 'bg-foreground/70'
|
|
60
|
-
)}
|
|
86
|
+
className={cn("h-full rounded-full transition-all duration-300", getProgressColor(phase.progress))}
|
|
61
87
|
style={{ width: `${phase.progress}%` }}
|
|
62
88
|
/>
|
|
63
89
|
</div>
|
|
90
|
+
{/* Show features under each phase */}
|
|
91
|
+
{phase.features && phase.features.length > 0 && (
|
|
92
|
+
<div className="ml-6 mt-2 space-y-1">
|
|
93
|
+
{phase.features.slice(0, 3).map((feature, i) => {
|
|
94
|
+
const isCompleted = feature.status === 'completed'
|
|
95
|
+
const cmdHref = codeHref && !isCompleted
|
|
96
|
+
? `${codeHref}?cmd=${encodeURIComponent(`p. work "${feature.name}"`)}`
|
|
97
|
+
: undefined
|
|
98
|
+
|
|
99
|
+
const content = (
|
|
100
|
+
<>
|
|
101
|
+
<ChevronRight className="h-3 w-3 shrink-0" />
|
|
102
|
+
<span className={cn(
|
|
103
|
+
'truncate',
|
|
104
|
+
isCompleted && 'line-through opacity-60'
|
|
105
|
+
)}>
|
|
106
|
+
{feature.name}
|
|
107
|
+
</span>
|
|
108
|
+
</>
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return cmdHref ? (
|
|
112
|
+
<Link
|
|
113
|
+
key={i}
|
|
114
|
+
href={cmdHref}
|
|
115
|
+
className="flex items-center gap-2 text-xs text-muted-foreground hover:text-foreground hover:bg-muted/50 rounded px-1 -mx-1 py-0.5 transition-colors cursor-pointer"
|
|
116
|
+
>
|
|
117
|
+
{content}
|
|
118
|
+
</Link>
|
|
119
|
+
) : (
|
|
120
|
+
<div key={i} className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
121
|
+
{content}
|
|
122
|
+
</div>
|
|
123
|
+
)
|
|
124
|
+
})}
|
|
125
|
+
{phase.features.length > 3 && (
|
|
126
|
+
<span className="text-xs text-muted-foreground/70 ml-5">
|
|
127
|
+
+{phase.features.length - 3} more
|
|
128
|
+
</span>
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
)}
|
|
64
132
|
</div>
|
|
65
133
|
))}
|
|
66
134
|
</div>
|
|
67
135
|
|
|
68
136
|
{roadmap.phases.length > 0 && (
|
|
69
|
-
<p className="text-
|
|
137
|
+
<p className="text-xs text-muted-foreground border-t pt-3 mt-3">
|
|
70
138
|
{roadmap.phases.reduce((acc, p) => acc + (p.features?.length || 0), 0)} features across {roadmap.phases.length} phases
|
|
71
139
|
</p>
|
|
72
140
|
)}
|
|
73
141
|
</div>
|
|
74
142
|
)}
|
|
75
|
-
</
|
|
143
|
+
</div>
|
|
76
144
|
)
|
|
77
145
|
}
|
|
@@ -1,52 +1,95 @@
|
|
|
1
|
-
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
2
4
|
import { EmptyState } from '@/components/EmptyState'
|
|
3
|
-
import {
|
|
5
|
+
import { ExpandButton } from '@/components/ExpandButton'
|
|
6
|
+
import { Rocket, Clock, FileCode, Check } from 'lucide-react'
|
|
4
7
|
import { Badge } from '@/components/ui/badge'
|
|
8
|
+
import { cn } from '@/lib/utils'
|
|
5
9
|
import { formatShipDate } from './ShipsCard.utils'
|
|
6
10
|
import type { ShipsCardProps } from './ShipsCard.types'
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
const COLLAPSED_LIMIT = 10
|
|
13
|
+
|
|
14
|
+
export function ShipsCard({ ships, totalShips = 0, codeHref, className }: ShipsCardProps) {
|
|
15
|
+
const [expanded, setExpanded] = useState(false)
|
|
16
|
+
const displayShips = expanded ? ships : ships.slice(0, COLLAPSED_LIMIT)
|
|
17
|
+
const hasMore = ships.length > COLLAPSED_LIMIT
|
|
10
18
|
|
|
11
19
|
return (
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
<div className={cn(
|
|
21
|
+
'relative overflow-hidden rounded-xl border bg-card p-4',
|
|
22
|
+
className
|
|
23
|
+
)}>
|
|
24
|
+
<div className="flex items-center justify-between mb-3">
|
|
25
|
+
<div className="flex items-center gap-2">
|
|
26
|
+
<Rocket className="h-4 w-4 text-muted-foreground" />
|
|
27
|
+
<span className="text-xs font-bold uppercase tracking-[0.15em] text-muted-foreground">
|
|
28
|
+
Shipped
|
|
29
|
+
</span>
|
|
30
|
+
</div>
|
|
31
|
+
<span className="text-xs font-medium text-muted-foreground tabular-nums">
|
|
32
|
+
{totalShips} total
|
|
33
|
+
</span>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
20
36
|
{ships.length === 0 ? (
|
|
21
37
|
<EmptyState
|
|
22
38
|
icon={Rocket}
|
|
23
39
|
title="Nothing shipped yet"
|
|
24
40
|
description="Ship your first feature"
|
|
25
41
|
command="/p:ship"
|
|
42
|
+
href={codeHref}
|
|
26
43
|
compact
|
|
27
44
|
/>
|
|
28
45
|
) : (
|
|
29
|
-
<div className="space-y-
|
|
46
|
+
<div className="space-y-2">
|
|
30
47
|
{displayShips.map((ship, i) => (
|
|
31
|
-
<div key={i} className="group">
|
|
32
|
-
<div className="flex items-
|
|
33
|
-
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
<div key={i} className="group py-1.5 hover:bg-muted/50 rounded px-2 -mx-2 border-l-2 border-transparent hover:border-foreground/20">
|
|
49
|
+
<div className="flex items-start justify-between gap-2">
|
|
50
|
+
<div className="flex-1 min-w-0">
|
|
51
|
+
<div className="flex items-center gap-2">
|
|
52
|
+
<Check className="h-3 w-3 text-muted-foreground shrink-0" />
|
|
53
|
+
<p className="text-sm font-medium truncate">
|
|
54
|
+
{ship.name}
|
|
55
|
+
</p>
|
|
56
|
+
{ship.version && (
|
|
57
|
+
<Badge variant="outline" className="text-xs px-1 py-0 font-mono shrink-0 h-4">
|
|
58
|
+
{ship.version}
|
|
59
|
+
</Badge>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
<div className="flex items-center gap-3 mt-1 ml-5">
|
|
63
|
+
<span className="text-xs text-muted-foreground">
|
|
64
|
+
{formatShipDate(ship.date)}
|
|
65
|
+
</span>
|
|
66
|
+
{ship.duration && (
|
|
67
|
+
<span className="inline-flex items-center gap-0.5 text-xs text-muted-foreground">
|
|
68
|
+
<Clock className="h-2.5 w-2.5" />
|
|
69
|
+
{ship.duration}
|
|
70
|
+
</span>
|
|
71
|
+
)}
|
|
72
|
+
{ship.filesChanged && (
|
|
73
|
+
<span className="inline-flex items-center gap-0.5 text-xs text-muted-foreground">
|
|
74
|
+
<FileCode className="h-2.5 w-2.5" />
|
|
75
|
+
{ship.filesChanged} files
|
|
76
|
+
</span>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
41
80
|
</div>
|
|
42
|
-
<p className="text-[10px] text-muted-foreground mt-0.5">
|
|
43
|
-
{formatShipDate(ship.date)}
|
|
44
|
-
{ship.duration && ` · ${ship.duration}`}
|
|
45
|
-
</p>
|
|
46
81
|
</div>
|
|
47
82
|
))}
|
|
83
|
+
{hasMore && (
|
|
84
|
+
<ExpandButton
|
|
85
|
+
expanded={expanded}
|
|
86
|
+
totalCount={ships.length}
|
|
87
|
+
collapsedLimit={COLLAPSED_LIMIT}
|
|
88
|
+
onToggle={() => setExpanded(!expanded)}
|
|
89
|
+
/>
|
|
90
|
+
)}
|
|
48
91
|
</div>
|
|
49
92
|
)}
|
|
50
|
-
</
|
|
93
|
+
</div>
|
|
51
94
|
)
|
|
52
95
|
}
|
|
@@ -16,23 +16,25 @@ export function SparklineChart({
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
|
-
<
|
|
20
|
-
<
|
|
21
|
-
<
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
19
|
+
<div className="w-full min-w-0" style={{ height }}>
|
|
20
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
21
|
+
<AreaChart data={chartData} margin={{ top: 0, right: 0, bottom: 0, left: 0 }}>
|
|
22
|
+
<defs>
|
|
23
|
+
<linearGradient id="sparklineGradient" x1="0" y1="0" x2="0" y2="1">
|
|
24
|
+
<stop offset="0%" stopColor={color} stopOpacity={0.3} />
|
|
25
|
+
<stop offset="100%" stopColor={color} stopOpacity={0} />
|
|
26
|
+
</linearGradient>
|
|
27
|
+
</defs>
|
|
28
|
+
<Area
|
|
29
|
+
type="monotone"
|
|
30
|
+
dataKey="value"
|
|
31
|
+
stroke={color}
|
|
32
|
+
strokeWidth={1.5}
|
|
33
|
+
fill={showArea ? 'url(#sparklineGradient)' : 'none'}
|
|
34
|
+
isAnimationActive={false}
|
|
35
|
+
/>
|
|
36
|
+
</AreaChart>
|
|
37
|
+
</ResponsiveContainer>
|
|
38
|
+
</div>
|
|
37
39
|
)
|
|
38
40
|
}
|