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,408 @@
1
+ 'use client'
2
+
3
+ import { useState, use, useEffect, useRef, useCallback } from 'react'
4
+ import { useRouter, useSearchParams } from 'next/navigation'
5
+ import { useProject, useDeleteProject } from '@/hooks/useProjects'
6
+ import { useGlobalTerminal } from '@/context/GlobalTerminalContext'
7
+ import { TerminalDockTab } from '@/components/TerminalDock/TerminalDockTab'
8
+ import { TerminalTabBar } from '@/components/TerminalDock/TerminalTabBar'
9
+ import { CommandBar } from '@/components/CommandBar'
10
+ import { Button } from '@/components/ui/button'
11
+ import { Badge } from '@/components/ui/badge'
12
+ import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
13
+ import {
14
+ AlertDialog,
15
+ AlertDialogAction,
16
+ AlertDialogCancel,
17
+ AlertDialogContent,
18
+ AlertDialogDescription,
19
+ AlertDialogFooter,
20
+ AlertDialogHeader,
21
+ AlertDialogTitle,
22
+ } from '@/components/ui/alert-dialog'
23
+ import { ProjectAvatar } from '@/components/ProjectAvatar'
24
+ import { TechStackBadges } from '@/components/TechStackBadges'
25
+ import { MomentumWidget } from '@/components/MomentumWidget'
26
+ import { formatPath } from '@/lib/format'
27
+ import {
28
+ Panel,
29
+ PanelGroup,
30
+ PanelResizeHandle,
31
+ } from 'react-resizable-panels'
32
+ import {
33
+ Loader2,
34
+ AlertTriangle,
35
+ Trash2,
36
+ ArrowLeft,
37
+ FolderGit2,
38
+ Plus,
39
+ Terminal,
40
+ SplitSquareHorizontal,
41
+ Minus,
42
+ } from 'lucide-react'
43
+ import { cn } from '@/lib/utils'
44
+
45
+ // Inner component that uses the terminal context
46
+ function ProjectPageContent({ projectId, project }: { projectId: string; project: NonNullable<ReturnType<typeof useProject>['data']> }) {
47
+ const router = useRouter()
48
+ const searchParams = useSearchParams()
49
+ const commandExecutedRef = useRef(false)
50
+
51
+ // Global terminal context
52
+ const {
53
+ activeSessionId,
54
+ secondActiveSessionId,
55
+ isSplitEnabled,
56
+ setSplitEnabled,
57
+ createSessionForProject,
58
+ getProjectSessions,
59
+ switchSession,
60
+ closeSession,
61
+ sendCommandToActive,
62
+ getActiveSession,
63
+ setFullScreen,
64
+ getAllSessions,
65
+ getLeftPanelSessions,
66
+ getRightPanelSessions,
67
+ updateSession,
68
+ } = useGlobalTerminal()
69
+
70
+ const sessions = getProjectSessions(projectId)
71
+ const allSessions = getAllSessions()
72
+ const activeSession = getActiveSession()
73
+ const hasActiveSessions = sessions.length > 0
74
+ const isActiveConnected = activeSession?.isConnected ?? false
75
+
76
+ const projectPath = project.repoPath || project.path || '/tmp'
77
+ const projectName = project.name || projectId
78
+
79
+ // Set full-screen mode when entering code page
80
+ useEffect(() => {
81
+ setFullScreen(true)
82
+ return () => setFullScreen(false)
83
+ }, [setFullScreen])
84
+
85
+ // Handle new terminal - creates session directly in the specified panel
86
+ const handleNewTerminal = useCallback((panel: 'left' | 'right' = 'left') => {
87
+ createSessionForProject(projectId, projectName, projectPath, panel)
88
+ }, [projectId, projectName, projectPath, createSessionForProject])
89
+
90
+ // Handle close session
91
+ const handleCloseSession = useCallback((sessionId: string) => {
92
+ const disconnectKey = `terminal_disconnect_${sessionId}`
93
+ const disconnectFn = (window as unknown as Record<string, () => void>)[disconnectKey]
94
+ if (disconnectFn) {
95
+ disconnectFn()
96
+ }
97
+ closeSession(sessionId)
98
+ }, [closeSession])
99
+
100
+ // Handle rename session
101
+ const handleRenameSession = useCallback((sessionId: string, newLabel: string) => {
102
+ updateSession(sessionId, { label: newLabel })
103
+ }, [updateSession])
104
+
105
+ // Auto-execute command from URL param (e.g., ?cmd=p.%20done)
106
+ useEffect(() => {
107
+ const cmd = searchParams.get('cmd')
108
+ if (cmd && isActiveConnected && !commandExecutedRef.current) {
109
+ commandExecutedRef.current = true
110
+ const decoded = decodeURIComponent(cmd)
111
+ setTimeout(() => {
112
+ sendCommandToActive(decoded)
113
+ router.replace(`/project/${projectId}/code`)
114
+ }, 500)
115
+ }
116
+ }, [searchParams, isActiveConnected, sendCommandToActive, router, projectId])
117
+
118
+ // Terminal content with split support
119
+ const TerminalContent = (
120
+ <div className="flex-1 relative overflow-hidden">
121
+ {allSessions.length === 0 ? (
122
+ <div className="flex flex-col items-center justify-center h-full text-muted-foreground gap-4">
123
+ <Terminal className="w-12 h-12 opacity-50" />
124
+ <div className="text-center">
125
+ <p className="text-lg font-medium">No terminal sessions</p>
126
+ <p className="text-sm">Open a terminal to get started</p>
127
+ </div>
128
+ <button
129
+ onClick={() => handleNewTerminal('left')}
130
+ className="flex items-center gap-2 px-4 py-2 rounded-lg bg-orange-500 hover:bg-orange-600 text-white transition-colors"
131
+ >
132
+ <Plus className="w-4 h-4" />
133
+ Open Terminal
134
+ </button>
135
+ </div>
136
+ ) : isSplitEnabled ? (
137
+ <PanelGroup direction="horizontal">
138
+ <Panel defaultSize={50} minSize={20}>
139
+ <div className="h-full flex flex-col">
140
+ <div className="border-b border-border bg-muted/30">
141
+ <TerminalTabBar
142
+ sessions={getLeftPanelSessions()}
143
+ activeSessionId={activeSessionId}
144
+ onSwitchSession={(id) => switchSession(id, 'left')}
145
+ onCloseSession={handleCloseSession}
146
+ onNewTerminal={() => handleNewTerminal('left')}
147
+ onRenameSession={handleRenameSession}
148
+ />
149
+ </div>
150
+ <div className="flex-1 relative">
151
+ {getLeftPanelSessions().length > 0 ? (
152
+ getLeftPanelSessions().map((session) => (
153
+ <TerminalDockTab
154
+ key={session.id}
155
+ session={session}
156
+ isActive={session.id === activeSessionId}
157
+ />
158
+ ))
159
+ ) : (
160
+ <div className="absolute inset-0 flex items-center justify-center bg-card/95 text-muted-foreground text-sm">
161
+ <button
162
+ onClick={() => handleNewTerminal('left')}
163
+ className="text-orange-500 hover:text-orange-400 underline"
164
+ >
165
+ Open terminal in left panel
166
+ </button>
167
+ </div>
168
+ )}
169
+ </div>
170
+ </div>
171
+ </Panel>
172
+
173
+ <PanelResizeHandle className="w-1 bg-border hover:bg-orange-500/50 transition-colors" />
174
+
175
+ <Panel defaultSize={50} minSize={20}>
176
+ <div className="h-full flex flex-col">
177
+ <div className="border-b border-border bg-muted/30">
178
+ <TerminalTabBar
179
+ sessions={getRightPanelSessions()}
180
+ activeSessionId={secondActiveSessionId}
181
+ onSwitchSession={(id) => switchSession(id, 'right')}
182
+ onCloseSession={handleCloseSession}
183
+ onNewTerminal={() => handleNewTerminal('right')}
184
+ onRenameSession={handleRenameSession}
185
+ />
186
+ </div>
187
+ <div className="flex-1 relative">
188
+ {getRightPanelSessions().length > 0 ? (
189
+ getRightPanelSessions().map((session) => (
190
+ <TerminalDockTab
191
+ key={`right-${session.id}`}
192
+ session={session}
193
+ isActive={session.id === secondActiveSessionId}
194
+ />
195
+ ))
196
+ ) : (
197
+ <div className="absolute inset-0 flex items-center justify-center bg-card/95 text-muted-foreground text-sm">
198
+ <button
199
+ onClick={() => handleNewTerminal('right')}
200
+ className="text-orange-500 hover:text-orange-400 underline"
201
+ >
202
+ Open terminal in right panel
203
+ </button>
204
+ </div>
205
+ )}
206
+ </div>
207
+ </div>
208
+ </Panel>
209
+ </PanelGroup>
210
+ ) : (
211
+ <div className="h-full flex flex-col">
212
+ <div className="border-b border-border bg-muted/30">
213
+ <TerminalTabBar
214
+ sessions={allSessions}
215
+ activeSessionId={activeSessionId}
216
+ onSwitchSession={(id) => switchSession(id)}
217
+ onCloseSession={handleCloseSession}
218
+ onNewTerminal={() => handleNewTerminal('left')}
219
+ onRenameSession={handleRenameSession}
220
+ />
221
+ </div>
222
+ <div className="flex-1 relative">
223
+ {allSessions.map((session) => (
224
+ <TerminalDockTab
225
+ key={session.id}
226
+ session={session}
227
+ isActive={session.id === activeSessionId}
228
+ />
229
+ ))}
230
+ </div>
231
+ </div>
232
+ )}
233
+ </div>
234
+ )
235
+
236
+ return (
237
+ <div className="h-full">
238
+ <TooltipProvider>
239
+ <div className="flex flex-col h-full">
240
+ {/* Header - Responsive */}
241
+ <header className="h-auto md:h-14 flex flex-col md:flex-row md:items-center justify-between px-3 md:px-4 py-2 md:py-0 border-b border-border bg-card gap-2">
242
+ {/* Left: Project info */}
243
+ <div className="flex items-center gap-3">
244
+ <ProjectAvatar
245
+ projectId={projectId}
246
+ name={project.name || projectId}
247
+ iconPath={project.iconPath}
248
+ size="sm"
249
+ />
250
+ <div className="flex flex-col min-w-0">
251
+ <div className="flex items-center gap-2">
252
+ <span className="font-bold leading-tight truncate">{project.name || projectId}</span>
253
+ {project.version && (
254
+ <Badge variant="outline" className="text-[10px] px-1.5 py-0 font-mono shrink-0">
255
+ v{project.version}
256
+ </Badge>
257
+ )}
258
+ </div>
259
+ {project.repoPath && (
260
+ <span className="text-xs text-muted-foreground leading-tight flex items-center gap-1 truncate">
261
+ <FolderGit2 className="w-3 h-3 shrink-0" />
262
+ <span className="truncate">{formatPath(project.repoPath)}</span>
263
+ </span>
264
+ )}
265
+ </div>
266
+ {hasActiveSessions && (
267
+ <Badge variant="outline" className="text-green-500 border-green-500/50 shrink-0">
268
+ {sessions.filter(s => s.isConnected).length} active
269
+ </Badge>
270
+ )}
271
+ </div>
272
+
273
+ {/* Center: Momentum widget - desktop only */}
274
+ <div className="hidden md:flex items-center justify-center flex-1">
275
+ <MomentumWidget projectId={projectId} />
276
+ </div>
277
+
278
+ {/* Right: metadata and tech stack - desktop only */}
279
+ <div className="hidden md:flex items-center gap-4">
280
+ <div className="flex items-center gap-3 text-xs text-muted-foreground">
281
+ {project.filesCount && (
282
+ <span><span className="font-medium text-foreground">{project.filesCount}</span> files</span>
283
+ )}
284
+ {project.commitsCount && (
285
+ <span><span className="font-medium text-foreground">{project.commitsCount}</span> commits</span>
286
+ )}
287
+ </div>
288
+ <TechStackBadges techStack={project.techStack || []} />
289
+ </div>
290
+ </header>
291
+
292
+ {/* Command bar + actions - SAME AS DOCK */}
293
+ <div className="flex items-center justify-between border-b border-border bg-card/95 shrink-0">
294
+ <CommandBar isConnected={isActiveConnected} onCommand={sendCommandToActive} />
295
+
296
+ {/* Header actions: split toggle, back button */}
297
+ <div className="flex items-center gap-1 px-2">
298
+ <Tooltip>
299
+ <TooltipTrigger asChild>
300
+ <button
301
+ onClick={() => setSplitEnabled(!isSplitEnabled)}
302
+ className={cn(
303
+ 'p-1.5 rounded transition-colors',
304
+ isSplitEnabled
305
+ ? 'bg-accent text-accent-foreground'
306
+ : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
307
+ )}
308
+ >
309
+ <SplitSquareHorizontal className="w-4 h-4" />
310
+ </button>
311
+ </TooltipTrigger>
312
+ <TooltipContent>{isSplitEnabled ? 'Single view' : 'Split view'}</TooltipContent>
313
+ </Tooltip>
314
+
315
+ <Tooltip>
316
+ <TooltipTrigger asChild>
317
+ <button
318
+ onClick={() => router.push(`/project/${projectId}`)}
319
+ className="p-1.5 rounded text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors"
320
+ >
321
+ <Minus className="w-4 h-4" />
322
+ </button>
323
+ </TooltipTrigger>
324
+ <TooltipContent>Back to project</TooltipContent>
325
+ </Tooltip>
326
+ </div>
327
+ </div>
328
+
329
+ {/* Terminal content area */}
330
+ {TerminalContent}
331
+ </div>
332
+ </TooltipProvider>
333
+ </div>
334
+ )
335
+ }
336
+
337
+ export default function ProjectPage({ params }: { params: Promise<{ id: string }> }) {
338
+ const { id: projectId } = use(params)
339
+ const router = useRouter()
340
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
341
+
342
+ const { data: project, isLoading: projectLoading } = useProject(projectId)
343
+ const deleteMutation = useDeleteProject()
344
+
345
+ if (projectLoading) {
346
+ return (
347
+ <div className="flex items-center justify-center h-full">
348
+ <Loader2 className="w-8 h-8 animate-spin text-muted-foreground" />
349
+ </div>
350
+ )
351
+ }
352
+
353
+ if (!project) {
354
+ return (
355
+ <div className="flex items-center justify-center h-full p-4">
356
+ <div className="text-center space-y-4 max-w-sm">
357
+ <div className="w-16 h-16 rounded-full bg-muted flex items-center justify-center mx-auto">
358
+ <AlertTriangle className="w-8 h-8 text-muted-foreground" />
359
+ </div>
360
+ <div>
361
+ <h2 className="text-lg font-medium">Project not found</h2>
362
+ <p className="text-sm text-muted-foreground mt-1 break-all">ID: {projectId}</p>
363
+ </div>
364
+ <div className="flex flex-col sm:flex-row gap-2 justify-center">
365
+ <Button variant="outline" onClick={() => router.push('/')}>
366
+ <ArrowLeft className="w-4 h-4 mr-2" />
367
+ Back to Dashboard
368
+ </Button>
369
+ <Button variant="destructive" onClick={() => setShowDeleteConfirm(true)}>
370
+ <Trash2 className="w-4 h-4 mr-2" />
371
+ Delete from Storage
372
+ </Button>
373
+ </div>
374
+
375
+ <AlertDialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
376
+ <AlertDialogContent className="max-w-[calc(100vw-2rem)] sm:max-w-lg">
377
+ <AlertDialogHeader>
378
+ <AlertDialogTitle className="flex items-center gap-2">
379
+ <AlertTriangle className="w-5 h-5 text-destructive" />
380
+ Delete Project Data?
381
+ </AlertDialogTitle>
382
+ <AlertDialogDescription>
383
+ This will move the project storage to trash.
384
+ <br />
385
+ <span className="text-muted-foreground text-sm break-all">ID: {projectId}</span>
386
+ </AlertDialogDescription>
387
+ </AlertDialogHeader>
388
+ <AlertDialogFooter className="flex-col sm:flex-row gap-2">
389
+ <AlertDialogCancel disabled={deleteMutation.isPending} className="w-full sm:w-auto">
390
+ Cancel
391
+ </AlertDialogCancel>
392
+ <AlertDialogAction
393
+ onClick={() => deleteMutation.mutate(projectId, { onSuccess: () => router.push('/') })}
394
+ disabled={deleteMutation.isPending}
395
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90 w-full sm:w-auto"
396
+ >
397
+ {deleteMutation.isPending ? 'Deleting...' : 'Delete'}
398
+ </AlertDialogAction>
399
+ </AlertDialogFooter>
400
+ </AlertDialogContent>
401
+ </AlertDialog>
402
+ </div>
403
+ </div>
404
+ )
405
+ }
406
+
407
+ return <ProjectPageContent projectId={projectId} project={project} />
408
+ }