prjct-cli 0.18.2 → 0.20.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.
Files changed (246) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/CLAUDE.md +74 -211
  3. package/core/agentic/prompt-builder.ts +3 -7
  4. package/core/command-registry/optional-commands.ts +0 -20
  5. package/core/infrastructure/command-installer/command-installer.ts +8 -1
  6. package/core/infrastructure/command-installer/global-config.ts +31 -1
  7. package/core/infrastructure/command-installer/index.ts +1 -1
  8. package/core/infrastructure/setup.ts +3 -0
  9. package/package.json +3 -17
  10. package/templates/agentic/agents/uxui.md +210 -0
  11. package/templates/commands/bug.md +219 -41
  12. package/templates/commands/done.md +57 -258
  13. package/templates/commands/feature.md +368 -80
  14. package/templates/commands/now.md +72 -277
  15. package/templates/commands/ship.md +167 -246
  16. package/templates/commands/sync.md +62 -3
  17. package/templates/commands/test.md +160 -20
  18. package/templates/global/CLAUDE.md +40 -205
  19. package/templates/global/docs/agents.md +88 -0
  20. package/templates/global/docs/architecture.md +103 -0
  21. package/templates/global/docs/commands.md +98 -0
  22. package/templates/global/docs/validation.md +95 -0
  23. package/bin/dev.js +0 -216
  24. package/bin/serve.js +0 -361
  25. package/packages/web/README.md +0 -36
  26. package/packages/web/app/api/claude/sessions/route.ts +0 -44
  27. package/packages/web/app/api/claude/status/route.ts +0 -34
  28. package/packages/web/app/api/projects/[id]/icon/route.ts +0 -33
  29. package/packages/web/app/api/projects/[id]/momentum/route.ts +0 -257
  30. package/packages/web/app/api/projects/[id]/route.ts +0 -29
  31. package/packages/web/app/api/projects/[id]/stats/route.ts +0 -41
  32. package/packages/web/app/api/projects/[id]/status/route.ts +0 -21
  33. package/packages/web/app/api/projects/route.ts +0 -16
  34. package/packages/web/app/api/sessions/current/route.ts +0 -132
  35. package/packages/web/app/api/sessions/history/route.ts +0 -204
  36. package/packages/web/app/error.tsx +0 -34
  37. package/packages/web/app/favicon.ico +0 -0
  38. package/packages/web/app/globals.css +0 -198
  39. package/packages/web/app/layout.tsx +0 -53
  40. package/packages/web/app/loading.tsx +0 -7
  41. package/packages/web/app/not-found.tsx +0 -25
  42. package/packages/web/app/page.tsx +0 -12
  43. package/packages/web/app/project/[id]/code/layout.tsx +0 -18
  44. package/packages/web/app/project/[id]/code/page.tsx +0 -408
  45. package/packages/web/app/project/[id]/error.tsx +0 -41
  46. package/packages/web/app/project/[id]/loading.tsx +0 -9
  47. package/packages/web/app/project/[id]/not-found.tsx +0 -27
  48. package/packages/web/app/project/[id]/page.tsx +0 -384
  49. package/packages/web/app/project/[id]/reports/page.tsx +0 -59
  50. package/packages/web/app/project/[id]/reports/print/page.tsx +0 -58
  51. package/packages/web/app/sessions/page.tsx +0 -165
  52. package/packages/web/app/settings/page.tsx +0 -151
  53. package/packages/web/components/ActivityTimeline/ActivityTimeline.constants.ts +0 -2
  54. package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -49
  55. package/packages/web/components/ActivityTimeline/ActivityTimeline.types.ts +0 -8
  56. package/packages/web/components/ActivityTimeline/hooks/index.ts +0 -2
  57. package/packages/web/components/ActivityTimeline/hooks/useExpandable.ts +0 -9
  58. package/packages/web/components/ActivityTimeline/hooks/useGroupedEvents.ts +0 -23
  59. package/packages/web/components/ActivityTimeline/index.ts +0 -2
  60. package/packages/web/components/AgentsCard/AgentsCard.tsx +0 -93
  61. package/packages/web/components/AgentsCard/AgentsCard.types.ts +0 -14
  62. package/packages/web/components/AgentsCard/index.ts +0 -2
  63. package/packages/web/components/AppSidebar/AppSidebar.tsx +0 -316
  64. package/packages/web/components/AppSidebar/index.ts +0 -1
  65. package/packages/web/components/BackLink/BackLink.tsx +0 -18
  66. package/packages/web/components/BackLink/BackLink.types.ts +0 -5
  67. package/packages/web/components/BackLink/index.ts +0 -2
  68. package/packages/web/components/BentoCard/BentoCard.constants.ts +0 -16
  69. package/packages/web/components/BentoCard/BentoCard.tsx +0 -48
  70. package/packages/web/components/BentoCard/BentoCard.types.ts +0 -15
  71. package/packages/web/components/BentoCard/index.ts +0 -2
  72. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.constants.ts +0 -9
  73. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.tsx +0 -18
  74. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.types.ts +0 -5
  75. package/packages/web/components/BentoCardSkeleton/index.ts +0 -2
  76. package/packages/web/components/BentoGrid/BentoGrid.tsx +0 -18
  77. package/packages/web/components/BentoGrid/BentoGrid.types.ts +0 -4
  78. package/packages/web/components/BentoGrid/index.ts +0 -2
  79. package/packages/web/components/BlockersCard/BlockersCard.tsx +0 -75
  80. package/packages/web/components/BlockersCard/BlockersCard.types.ts +0 -12
  81. package/packages/web/components/BlockersCard/index.ts +0 -2
  82. package/packages/web/components/CommandBar/CommandBar.tsx +0 -67
  83. package/packages/web/components/CommandBar/index.ts +0 -1
  84. package/packages/web/components/CommandButton/CommandButton.tsx +0 -46
  85. package/packages/web/components/CommandButton/index.ts +0 -1
  86. package/packages/web/components/ConnectionStatus/ConnectionStatus.tsx +0 -29
  87. package/packages/web/components/ConnectionStatus/index.ts +0 -1
  88. package/packages/web/components/DashboardContent/DashboardContent.tsx +0 -284
  89. package/packages/web/components/DashboardContent/index.ts +0 -1
  90. package/packages/web/components/DateGroup/DateGroup.tsx +0 -18
  91. package/packages/web/components/DateGroup/DateGroup.types.ts +0 -6
  92. package/packages/web/components/DateGroup/DateGroup.utils.ts +0 -11
  93. package/packages/web/components/DateGroup/index.ts +0 -2
  94. package/packages/web/components/EmptyState/EmptyState.tsx +0 -76
  95. package/packages/web/components/EmptyState/EmptyState.types.ts +0 -11
  96. package/packages/web/components/EmptyState/index.ts +0 -2
  97. package/packages/web/components/EventRow/EventRow.constants.ts +0 -10
  98. package/packages/web/components/EventRow/EventRow.tsx +0 -49
  99. package/packages/web/components/EventRow/EventRow.types.ts +0 -7
  100. package/packages/web/components/EventRow/EventRow.utils.ts +0 -49
  101. package/packages/web/components/EventRow/index.ts +0 -2
  102. package/packages/web/components/ExpandButton/ExpandButton.tsx +0 -18
  103. package/packages/web/components/ExpandButton/ExpandButton.types.ts +0 -6
  104. package/packages/web/components/ExpandButton/index.ts +0 -2
  105. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.tsx +0 -14
  106. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.types.ts +0 -5
  107. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.utils.ts +0 -13
  108. package/packages/web/components/HealthGradientBackground/index.ts +0 -2
  109. package/packages/web/components/HeroSection/HeroSection.tsx +0 -92
  110. package/packages/web/components/HeroSection/HeroSection.types.ts +0 -14
  111. package/packages/web/components/HeroSection/HeroSection.utils.ts +0 -11
  112. package/packages/web/components/HeroSection/hooks/index.ts +0 -2
  113. package/packages/web/components/HeroSection/hooks/useCountUp.ts +0 -45
  114. package/packages/web/components/HeroSection/hooks/useWeeklyActivity.ts +0 -18
  115. package/packages/web/components/HeroSection/index.ts +0 -2
  116. package/packages/web/components/IdeasCard/IdeasCard.tsx +0 -115
  117. package/packages/web/components/IdeasCard/IdeasCard.types.ts +0 -10
  118. package/packages/web/components/IdeasCard/index.ts +0 -2
  119. package/packages/web/components/InsightMessage/InsightMessage.tsx +0 -9
  120. package/packages/web/components/InsightMessage/InsightMessage.types.ts +0 -3
  121. package/packages/web/components/InsightMessage/index.ts +0 -2
  122. package/packages/web/components/Logo/Logo.tsx +0 -65
  123. package/packages/web/components/Logo/index.ts +0 -1
  124. package/packages/web/components/MarkdownContent/MarkdownContent.tsx +0 -123
  125. package/packages/web/components/MarkdownContent/index.ts +0 -1
  126. package/packages/web/components/MasonryGrid/MasonryGrid.tsx +0 -18
  127. package/packages/web/components/MasonryGrid/index.ts +0 -1
  128. package/packages/web/components/MomentumWidget/MomentumWidget.tsx +0 -119
  129. package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +0 -16
  130. package/packages/web/components/MomentumWidget/index.ts +0 -2
  131. package/packages/web/components/NowCard/NowCard.tsx +0 -118
  132. package/packages/web/components/NowCard/NowCard.types.ts +0 -16
  133. package/packages/web/components/NowCard/index.ts +0 -2
  134. package/packages/web/components/PageHeader/PageHeader.tsx +0 -24
  135. package/packages/web/components/PageHeader/index.ts +0 -1
  136. package/packages/web/components/ProgressRing/ProgressRing.constants.ts +0 -20
  137. package/packages/web/components/ProgressRing/ProgressRing.tsx +0 -51
  138. package/packages/web/components/ProgressRing/ProgressRing.types.ts +0 -11
  139. package/packages/web/components/ProgressRing/index.ts +0 -2
  140. package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +0 -54
  141. package/packages/web/components/ProjectAvatar/index.ts +0 -1
  142. package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +0 -37
  143. package/packages/web/components/ProjectColorDot/index.ts +0 -1
  144. package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +0 -104
  145. package/packages/web/components/ProjectSelectorModal/index.ts +0 -1
  146. package/packages/web/components/Providers/Providers.tsx +0 -48
  147. package/packages/web/components/Providers/index.ts +0 -1
  148. package/packages/web/components/QueueCard/QueueCard.tsx +0 -125
  149. package/packages/web/components/QueueCard/QueueCard.types.ts +0 -12
  150. package/packages/web/components/QueueCard/QueueCard.utils.ts +0 -12
  151. package/packages/web/components/QueueCard/index.ts +0 -2
  152. package/packages/web/components/RecoverCard/RecoverCard.tsx +0 -72
  153. package/packages/web/components/RecoverCard/RecoverCard.types.ts +0 -16
  154. package/packages/web/components/RecoverCard/index.ts +0 -2
  155. package/packages/web/components/RoadmapCard/RoadmapCard.tsx +0 -145
  156. package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +0 -16
  157. package/packages/web/components/RoadmapCard/index.ts +0 -2
  158. package/packages/web/components/ShipsCard/ShipsCard.tsx +0 -95
  159. package/packages/web/components/ShipsCard/ShipsCard.types.ts +0 -14
  160. package/packages/web/components/ShipsCard/ShipsCard.utils.ts +0 -4
  161. package/packages/web/components/ShipsCard/index.ts +0 -2
  162. package/packages/web/components/SparklineChart/SparklineChart.tsx +0 -40
  163. package/packages/web/components/SparklineChart/SparklineChart.types.ts +0 -6
  164. package/packages/web/components/SparklineChart/index.ts +0 -2
  165. package/packages/web/components/StatsMasonry/StatsMasonry.tsx +0 -95
  166. package/packages/web/components/StatsMasonry/index.ts +0 -1
  167. package/packages/web/components/StreakCard/StreakCard.constants.ts +0 -2
  168. package/packages/web/components/StreakCard/StreakCard.tsx +0 -55
  169. package/packages/web/components/StreakCard/StreakCard.types.ts +0 -4
  170. package/packages/web/components/StreakCard/index.ts +0 -2
  171. package/packages/web/components/TasksCounter/TasksCounter.tsx +0 -14
  172. package/packages/web/components/TasksCounter/TasksCounter.types.ts +0 -3
  173. package/packages/web/components/TasksCounter/index.ts +0 -2
  174. package/packages/web/components/TechStackBadges/TechStackBadges.tsx +0 -28
  175. package/packages/web/components/TechStackBadges/index.ts +0 -1
  176. package/packages/web/components/TerminalDock/DockToggleTab.tsx +0 -29
  177. package/packages/web/components/TerminalDock/TerminalDock.tsx +0 -386
  178. package/packages/web/components/TerminalDock/TerminalDockTab.tsx +0 -130
  179. package/packages/web/components/TerminalDock/TerminalTabBar.tsx +0 -142
  180. package/packages/web/components/TerminalDock/index.ts +0 -2
  181. package/packages/web/components/TerminalTabs/TerminalTab.tsx +0 -95
  182. package/packages/web/components/TerminalTabs/TerminalTabs.tsx +0 -211
  183. package/packages/web/components/TerminalTabs/index.ts +0 -1
  184. package/packages/web/components/VelocityBadge/VelocityBadge.tsx +0 -32
  185. package/packages/web/components/VelocityBadge/VelocityBadge.types.ts +0 -3
  186. package/packages/web/components/VelocityBadge/index.ts +0 -2
  187. package/packages/web/components/VelocityCard/VelocityCard.tsx +0 -73
  188. package/packages/web/components/VelocityCard/VelocityCard.types.ts +0 -7
  189. package/packages/web/components/VelocityCard/index.ts +0 -2
  190. package/packages/web/components/WeeklyReports/PrintableReport.tsx +0 -259
  191. package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +0 -187
  192. package/packages/web/components/WeeklyReports/WeekCalendar.tsx +0 -288
  193. package/packages/web/components/WeeklyReports/WeeklyReports.tsx +0 -149
  194. package/packages/web/components/WeeklyReports/index.ts +0 -4
  195. package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +0 -25
  196. package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +0 -4
  197. package/packages/web/components/WeeklySparkline/index.ts +0 -2
  198. package/packages/web/components/charts/SessionsChart.tsx +0 -175
  199. package/packages/web/components/ui/alert-dialog.tsx +0 -157
  200. package/packages/web/components/ui/badge.tsx +0 -46
  201. package/packages/web/components/ui/button.tsx +0 -60
  202. package/packages/web/components/ui/card.tsx +0 -92
  203. package/packages/web/components/ui/chart.tsx +0 -385
  204. package/packages/web/components/ui/dialog.tsx +0 -143
  205. package/packages/web/components/ui/drawer.tsx +0 -135
  206. package/packages/web/components/ui/dropdown-menu.tsx +0 -257
  207. package/packages/web/components/ui/input.tsx +0 -21
  208. package/packages/web/components/ui/scroll-area.tsx +0 -58
  209. package/packages/web/components/ui/select.tsx +0 -187
  210. package/packages/web/components/ui/sheet.tsx +0 -139
  211. package/packages/web/components/ui/tabs.tsx +0 -66
  212. package/packages/web/components/ui/tooltip.tsx +0 -61
  213. package/packages/web/components.json +0 -22
  214. package/packages/web/context/GlobalTerminalContext.tsx +0 -538
  215. package/packages/web/context/TerminalContext.tsx +0 -45
  216. package/packages/web/context/TerminalTabsContext.tsx +0 -181
  217. package/packages/web/eslint.config.mjs +0 -18
  218. package/packages/web/hooks/useClaudeTerminal.ts +0 -425
  219. package/packages/web/hooks/useProjectStats.ts +0 -93
  220. package/packages/web/hooks/useProjects.ts +0 -73
  221. package/packages/web/lib/actions/projects.ts +0 -15
  222. package/packages/web/lib/commands.ts +0 -81
  223. package/packages/web/lib/format.ts +0 -23
  224. package/packages/web/lib/generate-week-report.ts +0 -285
  225. package/packages/web/lib/parse-prjct-files.ts +0 -1123
  226. package/packages/web/lib/project-colors.ts +0 -58
  227. package/packages/web/lib/projects.ts +0 -506
  228. package/packages/web/lib/pty.ts +0 -101
  229. package/packages/web/lib/query-config.ts +0 -44
  230. package/packages/web/lib/services/index.ts +0 -9
  231. package/packages/web/lib/services/projects.server.ts +0 -66
  232. package/packages/web/lib/services/stats.server.ts +0 -562
  233. package/packages/web/lib/unified-loader.ts +0 -396
  234. package/packages/web/lib/utils.ts +0 -6
  235. package/packages/web/next-env.d.ts +0 -6
  236. package/packages/web/next.config.ts +0 -7
  237. package/packages/web/package.json +0 -57
  238. package/packages/web/postcss.config.mjs +0 -7
  239. package/packages/web/public/file.svg +0 -1
  240. package/packages/web/public/globe.svg +0 -1
  241. package/packages/web/public/next.svg +0 -1
  242. package/packages/web/public/vercel.svg +0 -1
  243. package/packages/web/public/window.svg +0 -1
  244. package/packages/web/server.ts +0 -312
  245. package/packages/web/tsconfig.json +0 -34
  246. package/templates/commands/serve.md +0 -121
@@ -1,142 +0,0 @@
1
- 'use client'
2
-
3
- import { useState, useRef, useEffect } from 'react'
4
- import { X, Plus } from 'lucide-react'
5
- import { cn } from '@/lib/utils'
6
- import type { GlobalTerminalSession } from '@/context/GlobalTerminalContext'
7
-
8
- interface TerminalTabBarProps {
9
- sessions: GlobalTerminalSession[]
10
- activeSessionId: string | null
11
- onSwitchSession: (sessionId: string) => void
12
- onCloseSession: (sessionId: string) => void
13
- onNewTerminal: () => void
14
- onRenameSession: (sessionId: string, newLabel: string) => void
15
- }
16
-
17
- export function TerminalTabBar({
18
- sessions,
19
- activeSessionId,
20
- onSwitchSession,
21
- onCloseSession,
22
- onNewTerminal,
23
- onRenameSession,
24
- }: TerminalTabBarProps) {
25
- const [editingSessionId, setEditingSessionId] = useState<string | null>(null)
26
- const [editValue, setEditValue] = useState('')
27
- const inputRef = useRef<HTMLInputElement>(null)
28
-
29
- // Group sessions by project
30
- const sessionsByProject = sessions.reduce((acc, session) => {
31
- const existing = acc.find(g => g.projectId === session.projectId)
32
- if (existing) {
33
- existing.sessions.push(session)
34
- } else {
35
- acc.push({
36
- projectId: session.projectId,
37
- projectName: session.projectName,
38
- sessions: [session],
39
- })
40
- }
41
- return acc
42
- }, [] as { projectId: string; projectName: string; sessions: GlobalTerminalSession[] }[])
43
-
44
- // Focus input when editing starts
45
- useEffect(() => {
46
- if (editingSessionId && inputRef.current) {
47
- inputRef.current.focus()
48
- inputRef.current.select()
49
- }
50
- }, [editingSessionId])
51
-
52
- const handleDoubleClick = (session: GlobalTerminalSession) => {
53
- setEditingSessionId(session.id)
54
- setEditValue(session.label)
55
- }
56
-
57
- const handleBlur = () => {
58
- if (editingSessionId && editValue.trim()) {
59
- onRenameSession(editingSessionId, editValue.trim())
60
- }
61
- setEditingSessionId(null)
62
- }
63
-
64
- const handleKeyDown = (e: React.KeyboardEvent) => {
65
- if (e.key === 'Enter') {
66
- handleBlur()
67
- } else if (e.key === 'Escape') {
68
- setEditingSessionId(null)
69
- }
70
- }
71
-
72
- return (
73
- <div className="flex items-center gap-0.5 flex-1 overflow-x-auto py-1 px-1">
74
- {sessionsByProject.map((group, groupIndex) => (
75
- <div key={group.projectId} className="flex items-center">
76
- {/* Group separator */}
77
- {groupIndex > 0 && (
78
- <div className="w-px h-5 bg-border mx-2" />
79
- )}
80
-
81
- {/* Tabs for this project */}
82
- {group.sessions.map((session) => {
83
- const isActive = session.id === activeSessionId
84
- const isEditing = session.id === editingSessionId
85
-
86
- return (
87
- <button
88
- key={session.id}
89
- onClick={() => onSwitchSession(session.id)}
90
- onDoubleClick={() => handleDoubleClick(session)}
91
- className={cn(
92
- 'group flex items-center gap-1.5 px-3 py-1.5 text-xs transition-all relative',
93
- // Chrome-style: rounded top corners, flat bottom
94
- 'rounded-t-md',
95
- isActive
96
- ? 'bg-card border-t border-l border-r border-border text-foreground -mb-px z-10'
97
- : 'text-muted-foreground hover:text-foreground hover:bg-muted/50'
98
- )}
99
- >
100
- {/* Label - editable or display */}
101
- {isEditing ? (
102
- <input
103
- ref={inputRef}
104
- type="text"
105
- value={editValue}
106
- onChange={(e) => setEditValue(e.target.value)}
107
- onBlur={handleBlur}
108
- onKeyDown={handleKeyDown}
109
- className="w-24 bg-transparent border-b border-orange-500 outline-none text-xs"
110
- onClick={(e) => e.stopPropagation()}
111
- />
112
- ) : (
113
- <span className="truncate max-w-[120px]">
114
- {session.label}
115
- </span>
116
- )}
117
-
118
- {/* Close button */}
119
- <X
120
- className="w-3 h-3 opacity-0 group-hover:opacity-100 hover:text-destructive transition-opacity shrink-0"
121
- onClick={(e) => {
122
- e.stopPropagation()
123
- onCloseSession(session.id)
124
- }}
125
- />
126
- </button>
127
- )
128
- })}
129
- </div>
130
- ))}
131
-
132
- {/* Add Terminal Button */}
133
- <button
134
- onClick={onNewTerminal}
135
- className="p-1.5 rounded text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors ml-1"
136
- title="New terminal"
137
- >
138
- <Plus className="w-4 h-4" />
139
- </button>
140
- </div>
141
- )
142
- }
@@ -1,2 +0,0 @@
1
- export { TerminalDock } from './TerminalDock'
2
- export { TerminalDockTab } from './TerminalDockTab'
@@ -1,95 +0,0 @@
1
- 'use client'
2
-
3
- import { useEffect, useRef, useCallback } from 'react'
4
- import { useClaudeTerminal } from '@/hooks/useClaudeTerminal'
5
- import { useTerminalTabs, type TerminalSession } from '@/context/TerminalTabsContext'
6
-
7
- interface TerminalTabProps {
8
- session: TerminalSession
9
- projectDir: string
10
- isActive: boolean
11
- }
12
-
13
- export function TerminalTab({ session, projectDir, isActive }: TerminalTabProps) {
14
- const containerRef = useRef<HTMLDivElement>(null)
15
- const hasInitializedRef = useRef(false)
16
- const hasConnectedRef = useRef(false)
17
- const { updateSession, registerSendInput, registerFocusTerminal } = useTerminalTabs()
18
-
19
- const handleConnect = useCallback(() => {
20
- updateSession(session.id, { isConnected: true, isLoading: false })
21
- }, [session.id, updateSession])
22
-
23
- const handleDisconnect = useCallback(() => {
24
- updateSession(session.id, { isConnected: false, isLoading: false })
25
- }, [session.id, updateSession])
26
-
27
- const handleError = useCallback((error: string) => {
28
- console.error(`[Terminal ${session.id}] Error:`, error)
29
- updateSession(session.id, { isLoading: false })
30
- }, [session.id, updateSession])
31
-
32
- const {
33
- initTerminal,
34
- connect,
35
- disconnect,
36
- sendInput,
37
- focusTerminal,
38
- fit,
39
- } = useClaudeTerminal({
40
- sessionId: session.id,
41
- projectDir,
42
- onConnect: handleConnect,
43
- onDisconnect: handleDisconnect,
44
- onError: handleError,
45
- })
46
-
47
- // Initialize terminal AND connect - only once
48
- useEffect(() => {
49
- if (containerRef.current && !hasInitializedRef.current) {
50
- hasInitializedRef.current = true
51
-
52
- initTerminal(containerRef.current).then(() => {
53
- if (!hasConnectedRef.current) {
54
- hasConnectedRef.current = true
55
- connect()
56
- }
57
- })
58
- }
59
- }, []) // Empty deps - run only on mount
60
-
61
- // Re-fit terminal when tab becomes active (fixes resize issues when tab was hidden)
62
- useEffect(() => {
63
- if (isActive && hasInitializedRef.current) {
64
- // Use requestAnimationFrame to ensure container is fully visible
65
- requestAnimationFrame(() => {
66
- fit()
67
- })
68
- }
69
- }, [isActive, fit])
70
-
71
- // Register sendInput and focusTerminal for this session
72
- useEffect(() => {
73
- registerSendInput(session.id, sendInput)
74
- registerFocusTerminal(session.id, focusTerminal)
75
- }, [session.id, sendInput, focusTerminal, registerSendInput, registerFocusTerminal])
76
-
77
- // Expose disconnect for external use
78
- useEffect(() => {
79
- // Store disconnect function on window for access from parent
80
- const key = `terminal_disconnect_${session.id}`
81
- ;(window as unknown as Record<string, () => void>)[key] = disconnect
82
- return () => {
83
- delete (window as unknown as Record<string, () => void>)[key]
84
- }
85
- }, [session.id, disconnect])
86
-
87
- return (
88
- <div
89
- className="absolute inset-0 bg-[#0a0a0f] px-2 py-2"
90
- style={{ display: isActive ? 'block' : 'none' }}
91
- >
92
- <div ref={containerRef} className="h-full w-full" />
93
- </div>
94
- )
95
- }
@@ -1,211 +0,0 @@
1
- 'use client'
2
-
3
- import { useCallback, useState, useRef, useEffect } from 'react'
4
- import { useTerminalTabs } from '@/context/TerminalTabsContext'
5
- import { TerminalTab } from './TerminalTab'
6
- import { Button } from '@/components/ui/button'
7
- import {
8
- AlertDialog,
9
- AlertDialogAction,
10
- AlertDialogCancel,
11
- AlertDialogContent,
12
- AlertDialogDescription,
13
- AlertDialogFooter,
14
- AlertDialogHeader,
15
- AlertDialogTitle,
16
- } from '@/components/ui/alert-dialog'
17
- import { X, Terminal as TerminalIcon, Plus, Loader2, AlertTriangle } from 'lucide-react'
18
- import { cn } from '@/lib/utils'
19
-
20
- interface TerminalTabsProps {
21
- projectDir: string
22
- }
23
-
24
- export function TerminalTabs({ projectDir }: TerminalTabsProps) {
25
- const {
26
- sessions,
27
- activeSessionId,
28
- createSession,
29
- closeSession,
30
- setActiveSession,
31
- updateSession
32
- } = useTerminalTabs()
33
-
34
- const [sessionToClose, setSessionToClose] = useState<string | null>(null)
35
- const [editingSessionId, setEditingSessionId] = useState<string | null>(null)
36
- const [editValue, setEditValue] = useState('')
37
- const inputRef = useRef<HTMLInputElement>(null)
38
-
39
- const handleCloseTab = useCallback((sessionId: string, e: React.MouseEvent) => {
40
- e.stopPropagation()
41
- const session = sessions.find(s => s.id === sessionId)
42
- if (session?.isConnected) {
43
- setSessionToClose(sessionId)
44
- } else {
45
- closeSession(sessionId)
46
- }
47
- }, [sessions, closeSession])
48
-
49
- const handleConfirmClose = useCallback(() => {
50
- if (sessionToClose) {
51
- // Call disconnect on the terminal
52
- const disconnectFn = (window as unknown as Record<string, () => void>)[`terminal_disconnect_${sessionToClose}`]
53
- if (disconnectFn) disconnectFn()
54
- closeSession(sessionToClose)
55
- setSessionToClose(null)
56
- }
57
- }, [sessionToClose, closeSession])
58
-
59
- const startEditing = useCallback((sessionId: string, currentLabel: string) => {
60
- setEditingSessionId(sessionId)
61
- setEditValue(currentLabel)
62
- }, [])
63
-
64
- const finishEditing = useCallback(() => {
65
- if (editingSessionId && editValue.trim()) {
66
- updateSession(editingSessionId, { label: editValue.trim() })
67
- }
68
- setEditingSessionId(null)
69
- setEditValue('')
70
- }, [editingSessionId, editValue, updateSession])
71
-
72
- const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
73
- if (e.key === 'Enter') {
74
- finishEditing()
75
- } else if (e.key === 'Escape') {
76
- setEditingSessionId(null)
77
- setEditValue('')
78
- }
79
- }, [finishEditing])
80
-
81
- // Focus input when editing starts
82
- useEffect(() => {
83
- if (editingSessionId && inputRef.current) {
84
- inputRef.current.focus()
85
- inputRef.current.select()
86
- }
87
- }, [editingSessionId])
88
-
89
- const hasNoSessions = sessions.length === 0
90
-
91
- return (
92
- <div className="flex flex-col h-full">
93
- {/* Tab bar - scrollable on mobile */}
94
- <div className="flex items-center gap-1 px-2 py-1.5 bg-card border-b border-border min-h-[44px] md:min-h-[40px] overflow-x-auto scrollbar-hide">
95
- {sessions.map(session => (
96
- <div
97
- key={session.id}
98
- onClick={() => setActiveSession(session.id)}
99
- onDoubleClick={() => startEditing(session.id, session.label)}
100
- role="tab"
101
- tabIndex={0}
102
- onKeyDown={(e) => e.key === 'Enter' && setActiveSession(session.id)}
103
- className={cn(
104
- 'flex items-center gap-2 px-3 py-2 md:py-1.5 rounded-md text-sm transition-colors cursor-pointer shrink-0',
105
- 'hover:bg-muted active:bg-muted/80',
106
- 'min-h-[36px] md:min-h-0', // Touch-friendly height on mobile
107
- session.id === activeSessionId
108
- ? 'bg-muted text-foreground'
109
- : 'text-muted-foreground'
110
- )}
111
- >
112
- {session.isLoading ? (
113
- <Loader2 className="w-3.5 h-3.5 animate-spin shrink-0" />
114
- ) : session.isConnected ? (
115
- <span className="relative flex h-2.5 w-2.5 shrink-0">
116
- <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
117
- <span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-green-500" />
118
- </span>
119
- ) : (
120
- <span className="h-2.5 w-2.5 rounded-full bg-muted-foreground/50 shrink-0" />
121
- )}
122
- {editingSessionId === session.id ? (
123
- <input
124
- ref={inputRef}
125
- type="text"
126
- value={editValue}
127
- onChange={(e) => setEditValue(e.target.value)}
128
- onBlur={finishEditing}
129
- onKeyDown={handleKeyDown}
130
- onClick={(e) => e.stopPropagation()}
131
- className="w-[100px] bg-background border border-border rounded px-1 py-0.5 text-sm outline-none focus:ring-1 focus:ring-ring"
132
- />
133
- ) : (
134
- <span className="truncate max-w-[80px] md:max-w-[100px]">{session.label}</span>
135
- )}
136
- <button
137
- onClick={(e) => handleCloseTab(session.id, e)}
138
- className="p-1 md:p-0.5 rounded hover:bg-background/50 text-muted-foreground hover:text-foreground shrink-0"
139
- >
140
- <X className="w-4 h-4 md:w-3 md:h-3" />
141
- </button>
142
- </div>
143
- ))}
144
-
145
- {/* New tab button - larger touch target on mobile */}
146
- <Button
147
- variant="ghost"
148
- size="icon"
149
- className="h-9 w-9 md:h-7 md:w-7 ml-1 shrink-0"
150
- onClick={createSession}
151
- >
152
- <Plus className="w-5 h-5 md:w-4 md:h-4" />
153
- </Button>
154
- </div>
155
-
156
- {/* Terminal area */}
157
- <div className="flex-1 relative">
158
- {hasNoSessions ? (
159
- <div className="absolute inset-0 flex items-center justify-center bg-background p-4">
160
- <div className="text-center max-w-xs">
161
- <div className="w-14 h-14 md:w-16 md:h-16 rounded-full bg-muted flex items-center justify-center mx-auto mb-3 md:mb-4">
162
- <TerminalIcon className="w-7 h-7 md:w-8 md:h-8 text-muted-foreground" />
163
- </div>
164
- <h2 className="text-base md:text-lg font-medium mb-1.5 md:mb-2">No active sessions</h2>
165
- <p className="text-muted-foreground text-sm mb-3 md:mb-4">
166
- Tap + to create a new terminal
167
- </p>
168
- <Button onClick={createSession} className="min-h-[44px]">
169
- <Plus className="w-4 h-4 mr-2" />
170
- New Terminal
171
- </Button>
172
- </div>
173
- </div>
174
- ) : (
175
- sessions.map(session => (
176
- <TerminalTab
177
- key={session.id}
178
- session={session}
179
- projectDir={projectDir}
180
- isActive={session.id === activeSessionId}
181
- />
182
- ))
183
- )}
184
- </div>
185
-
186
- {/* Close confirmation dialog - responsive */}
187
- <AlertDialog open={!!sessionToClose} onOpenChange={(open: boolean) => !open && setSessionToClose(null)}>
188
- <AlertDialogContent className="max-w-[calc(100vw-2rem)] sm:max-w-lg">
189
- <AlertDialogHeader>
190
- <AlertDialogTitle className="flex items-center gap-2">
191
- <AlertTriangle className="w-5 h-5 text-destructive shrink-0" />
192
- Close Terminal?
193
- </AlertDialogTitle>
194
- <AlertDialogDescription>
195
- This terminal has an active session. Closing it will terminate the connection.
196
- </AlertDialogDescription>
197
- </AlertDialogHeader>
198
- <AlertDialogFooter className="flex-col sm:flex-row gap-2">
199
- <AlertDialogCancel className="w-full sm:w-auto">Cancel</AlertDialogCancel>
200
- <AlertDialogAction
201
- onClick={handleConfirmClose}
202
- className="bg-destructive text-destructive-foreground hover:bg-destructive/90 w-full sm:w-auto"
203
- >
204
- Close Terminal
205
- </AlertDialogAction>
206
- </AlertDialogFooter>
207
- </AlertDialogContent>
208
- </AlertDialog>
209
- </div>
210
- )
211
- }
@@ -1 +0,0 @@
1
- export { TerminalTabs } from './TerminalTabs'
@@ -1,32 +0,0 @@
1
- import { TrendingUp, TrendingDown } from 'lucide-react'
2
- import { cn } from '@/lib/utils'
3
- import type { VelocityBadgeProps } from './VelocityBadge.types'
4
-
5
- function getChangeColor(change: number): string {
6
- if (change >= 10) return 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
7
- if (change >= 0) return 'bg-amber-500/10 text-amber-600 dark:text-amber-400'
8
- if (change >= -10) return 'bg-amber-500/10 text-amber-600 dark:text-amber-400'
9
- return 'bg-red-500/10 text-red-600 dark:text-red-400'
10
- }
11
-
12
- export function VelocityBadge({ change }: VelocityBadgeProps) {
13
- if (change === 0) return null
14
-
15
- const isPositive = change >= 0
16
- const Icon = isPositive ? TrendingUp : TrendingDown
17
-
18
- return (
19
- <div className="flex items-center gap-2 mt-2">
20
- <span
21
- className={cn(
22
- 'inline-flex items-center gap-1 text-xs sm:text-sm font-medium px-2 py-0.5 rounded-md',
23
- getChangeColor(change)
24
- )}
25
- >
26
- <Icon className="h-3 w-3 sm:h-3.5 sm:w-3.5" />
27
- {isPositive ? '+' : ''}{change}%
28
- </span>
29
- <span className="text-xs sm:text-sm text-muted-foreground">vs last week</span>
30
- </div>
31
- )
32
- }
@@ -1,3 +0,0 @@
1
- export interface VelocityBadgeProps {
2
- change: number
3
- }
@@ -1,2 +0,0 @@
1
- export { VelocityBadge } from './VelocityBadge'
2
- export type { VelocityBadgeProps } from './VelocityBadge.types'
@@ -1,73 +0,0 @@
1
- 'use client'
2
-
3
- import { SparklineChart } from '@/components/SparklineChart'
4
- import { Zap, TrendingUp, TrendingDown, Target } from 'lucide-react'
5
- import { cn } from '@/lib/utils'
6
- import type { VelocityCardProps } from './VelocityCard.types'
7
-
8
- function getChangeColor(change: number): string {
9
- if (change >= 10) return 'text-emerald-600 dark:text-emerald-400'
10
- if (change >= 0) return 'text-amber-600 dark:text-amber-400'
11
- if (change >= -10) return 'text-amber-600 dark:text-amber-400'
12
- return 'text-red-600 dark:text-red-400'
13
- }
14
-
15
- export function VelocityCard({
16
- tasksPerDay,
17
- weeklyData = [],
18
- change = 0,
19
- estimateAccuracy,
20
- className,
21
- }: VelocityCardProps) {
22
- return (
23
- <div className={cn(
24
- 'relative overflow-hidden rounded-xl border bg-card p-4',
25
- className
26
- )}>
27
- <div className="flex items-center justify-between mb-3">
28
- <div className="flex items-center gap-2">
29
- <Zap className="h-4 w-4 text-muted-foreground" />
30
- <span className="text-xs font-bold uppercase tracking-[0.15em] text-muted-foreground">
31
- Velocity
32
- </span>
33
- </div>
34
- {change !== 0 && (
35
- <div className={cn("flex items-center gap-1", getChangeColor(change))}>
36
- {change >= 0 ? (
37
- <TrendingUp className="h-3.5 w-3.5" />
38
- ) : (
39
- <TrendingDown className="h-3.5 w-3.5" />
40
- )}
41
- <span className="text-xs font-bold">
42
- {change >= 0 ? '+' : ''}{change}%
43
- </span>
44
- </div>
45
- )}
46
- </div>
47
-
48
- <div className="flex items-end justify-between gap-4">
49
- <div>
50
- <p className="text-3xl font-bold tabular-nums">{tasksPerDay}</p>
51
- <p className="text-xs text-muted-foreground">tasks/day avg</p>
52
- </div>
53
-
54
- {weeklyData.length > 0 && (
55
- <div className="flex-1 max-w-[120px]">
56
- <SparklineChart data={weeklyData} height={40} />
57
- <p className="text-xs text-muted-foreground text-right mt-1">Last 7 days</p>
58
- </div>
59
- )}
60
- </div>
61
-
62
- {estimateAccuracy !== undefined && estimateAccuracy > 0 && (
63
- <div className="flex items-center gap-1.5 mt-3 pt-3 border-t">
64
- <Target className="h-3 w-3 text-muted-foreground" />
65
- <span className="text-xs text-muted-foreground">Estimate accuracy:</span>
66
- <span className="text-xs font-bold text-muted-foreground">
67
- {estimateAccuracy}%
68
- </span>
69
- </div>
70
- )}
71
- </div>
72
- )
73
- }
@@ -1,7 +0,0 @@
1
- export interface VelocityCardProps {
2
- tasksPerDay: number
3
- weeklyData?: number[]
4
- change?: number
5
- estimateAccuracy?: number
6
- className?: string
7
- }
@@ -1,2 +0,0 @@
1
- export { VelocityCard } from './VelocityCard'
2
- export type { VelocityCardProps } from './VelocityCard.types'