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.
- package/CHANGELOG.md +122 -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/setup.md +18 -3
- 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/templates/mcp-config.json +20 -1
- 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
|
@@ -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 {
|
|
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
|
-
<
|
|
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-
|
|
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-
|
|
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-
|
|
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 {
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
{
|
|
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
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
)
|
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
21
|
-
task_start: 'text-
|
|
22
|
-
feature_ship: 'text-
|
|
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 {
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
projectVersion,
|
|
18
|
+
totalShips,
|
|
19
|
+
completionRate,
|
|
20
|
+
streak,
|
|
20
21
|
insightMessage,
|
|
21
22
|
timeline = [],
|
|
22
23
|
}: HeroProps) {
|
|
23
|
-
const animatedCount = useCountUp(
|
|
24
|
+
const animatedCount = useCountUp(totalShips)
|
|
24
25
|
const weeklyData = useWeeklyActivity(timeline)
|
|
25
|
-
const
|
|
26
|
+
const completionColor = getCompletionColor(completionRate)
|
|
26
27
|
|
|
27
28
|
return (
|
|
28
29
|
<div className="relative mb-6 md:mb-8">
|
|
29
|
-
<HealthGradientBackground score={
|
|
30
|
-
|
|
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={
|
|
65
|
+
value={completionRate}
|
|
36
66
|
size="xl"
|
|
37
|
-
accentColor={
|
|
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
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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 { 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
|
-
|
|
8
|
-
|
|
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
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
28
|
-
{displayIdeas.map((idea, i) =>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
</
|
|
113
|
+
</div>
|
|
47
114
|
)
|
|
48
115
|
}
|
|
@@ -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'
|