prjct-cli 0.18.1 → 0.19.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 (245) hide show
  1. package/CHANGELOG.md +53 -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/subagent-generation.md +8 -6
  11. package/templates/commands/done.md +57 -258
  12. package/templates/commands/now.md +72 -277
  13. package/templates/commands/ship.md +55 -261
  14. package/templates/commands/sync.md +7 -7
  15. package/templates/commands/test.md +328 -21
  16. package/templates/global/CLAUDE.md +40 -205
  17. package/templates/global/docs/agents.md +88 -0
  18. package/templates/global/docs/architecture.md +103 -0
  19. package/templates/global/docs/commands.md +98 -0
  20. package/templates/global/docs/validation.md +95 -0
  21. package/templates/mcp-config.json +36 -0
  22. package/bin/dev.js +0 -216
  23. package/bin/serve.js +0 -361
  24. package/packages/web/README.md +0 -36
  25. package/packages/web/app/api/claude/sessions/route.ts +0 -44
  26. package/packages/web/app/api/claude/status/route.ts +0 -34
  27. package/packages/web/app/api/projects/[id]/icon/route.ts +0 -33
  28. package/packages/web/app/api/projects/[id]/momentum/route.ts +0 -257
  29. package/packages/web/app/api/projects/[id]/route.ts +0 -29
  30. package/packages/web/app/api/projects/[id]/stats/route.ts +0 -41
  31. package/packages/web/app/api/projects/[id]/status/route.ts +0 -21
  32. package/packages/web/app/api/projects/route.ts +0 -16
  33. package/packages/web/app/api/sessions/current/route.ts +0 -132
  34. package/packages/web/app/api/sessions/history/route.ts +0 -204
  35. package/packages/web/app/error.tsx +0 -34
  36. package/packages/web/app/favicon.ico +0 -0
  37. package/packages/web/app/globals.css +0 -198
  38. package/packages/web/app/layout.tsx +0 -53
  39. package/packages/web/app/loading.tsx +0 -7
  40. package/packages/web/app/not-found.tsx +0 -25
  41. package/packages/web/app/page.tsx +0 -12
  42. package/packages/web/app/project/[id]/code/layout.tsx +0 -18
  43. package/packages/web/app/project/[id]/code/page.tsx +0 -408
  44. package/packages/web/app/project/[id]/error.tsx +0 -41
  45. package/packages/web/app/project/[id]/loading.tsx +0 -9
  46. package/packages/web/app/project/[id]/not-found.tsx +0 -27
  47. package/packages/web/app/project/[id]/page.tsx +0 -384
  48. package/packages/web/app/project/[id]/reports/page.tsx +0 -59
  49. package/packages/web/app/project/[id]/reports/print/page.tsx +0 -58
  50. package/packages/web/app/sessions/page.tsx +0 -165
  51. package/packages/web/app/settings/page.tsx +0 -151
  52. package/packages/web/components/ActivityTimeline/ActivityTimeline.constants.ts +0 -2
  53. package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -49
  54. package/packages/web/components/ActivityTimeline/ActivityTimeline.types.ts +0 -8
  55. package/packages/web/components/ActivityTimeline/hooks/index.ts +0 -2
  56. package/packages/web/components/ActivityTimeline/hooks/useExpandable.ts +0 -9
  57. package/packages/web/components/ActivityTimeline/hooks/useGroupedEvents.ts +0 -23
  58. package/packages/web/components/ActivityTimeline/index.ts +0 -2
  59. package/packages/web/components/AgentsCard/AgentsCard.tsx +0 -93
  60. package/packages/web/components/AgentsCard/AgentsCard.types.ts +0 -14
  61. package/packages/web/components/AgentsCard/index.ts +0 -2
  62. package/packages/web/components/AppSidebar/AppSidebar.tsx +0 -316
  63. package/packages/web/components/AppSidebar/index.ts +0 -1
  64. package/packages/web/components/BackLink/BackLink.tsx +0 -18
  65. package/packages/web/components/BackLink/BackLink.types.ts +0 -5
  66. package/packages/web/components/BackLink/index.ts +0 -2
  67. package/packages/web/components/BentoCard/BentoCard.constants.ts +0 -16
  68. package/packages/web/components/BentoCard/BentoCard.tsx +0 -48
  69. package/packages/web/components/BentoCard/BentoCard.types.ts +0 -15
  70. package/packages/web/components/BentoCard/index.ts +0 -2
  71. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.constants.ts +0 -9
  72. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.tsx +0 -18
  73. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.types.ts +0 -5
  74. package/packages/web/components/BentoCardSkeleton/index.ts +0 -2
  75. package/packages/web/components/BentoGrid/BentoGrid.tsx +0 -18
  76. package/packages/web/components/BentoGrid/BentoGrid.types.ts +0 -4
  77. package/packages/web/components/BentoGrid/index.ts +0 -2
  78. package/packages/web/components/BlockersCard/BlockersCard.tsx +0 -75
  79. package/packages/web/components/BlockersCard/BlockersCard.types.ts +0 -12
  80. package/packages/web/components/BlockersCard/index.ts +0 -2
  81. package/packages/web/components/CommandBar/CommandBar.tsx +0 -67
  82. package/packages/web/components/CommandBar/index.ts +0 -1
  83. package/packages/web/components/CommandButton/CommandButton.tsx +0 -46
  84. package/packages/web/components/CommandButton/index.ts +0 -1
  85. package/packages/web/components/ConnectionStatus/ConnectionStatus.tsx +0 -29
  86. package/packages/web/components/ConnectionStatus/index.ts +0 -1
  87. package/packages/web/components/DashboardContent/DashboardContent.tsx +0 -284
  88. package/packages/web/components/DashboardContent/index.ts +0 -1
  89. package/packages/web/components/DateGroup/DateGroup.tsx +0 -18
  90. package/packages/web/components/DateGroup/DateGroup.types.ts +0 -6
  91. package/packages/web/components/DateGroup/DateGroup.utils.ts +0 -11
  92. package/packages/web/components/DateGroup/index.ts +0 -2
  93. package/packages/web/components/EmptyState/EmptyState.tsx +0 -76
  94. package/packages/web/components/EmptyState/EmptyState.types.ts +0 -11
  95. package/packages/web/components/EmptyState/index.ts +0 -2
  96. package/packages/web/components/EventRow/EventRow.constants.ts +0 -10
  97. package/packages/web/components/EventRow/EventRow.tsx +0 -49
  98. package/packages/web/components/EventRow/EventRow.types.ts +0 -7
  99. package/packages/web/components/EventRow/EventRow.utils.ts +0 -49
  100. package/packages/web/components/EventRow/index.ts +0 -2
  101. package/packages/web/components/ExpandButton/ExpandButton.tsx +0 -18
  102. package/packages/web/components/ExpandButton/ExpandButton.types.ts +0 -6
  103. package/packages/web/components/ExpandButton/index.ts +0 -2
  104. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.tsx +0 -14
  105. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.types.ts +0 -5
  106. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.utils.ts +0 -13
  107. package/packages/web/components/HealthGradientBackground/index.ts +0 -2
  108. package/packages/web/components/HeroSection/HeroSection.tsx +0 -92
  109. package/packages/web/components/HeroSection/HeroSection.types.ts +0 -14
  110. package/packages/web/components/HeroSection/HeroSection.utils.ts +0 -11
  111. package/packages/web/components/HeroSection/hooks/index.ts +0 -2
  112. package/packages/web/components/HeroSection/hooks/useCountUp.ts +0 -45
  113. package/packages/web/components/HeroSection/hooks/useWeeklyActivity.ts +0 -18
  114. package/packages/web/components/HeroSection/index.ts +0 -2
  115. package/packages/web/components/IdeasCard/IdeasCard.tsx +0 -115
  116. package/packages/web/components/IdeasCard/IdeasCard.types.ts +0 -10
  117. package/packages/web/components/IdeasCard/index.ts +0 -2
  118. package/packages/web/components/InsightMessage/InsightMessage.tsx +0 -9
  119. package/packages/web/components/InsightMessage/InsightMessage.types.ts +0 -3
  120. package/packages/web/components/InsightMessage/index.ts +0 -2
  121. package/packages/web/components/Logo/Logo.tsx +0 -65
  122. package/packages/web/components/Logo/index.ts +0 -1
  123. package/packages/web/components/MarkdownContent/MarkdownContent.tsx +0 -123
  124. package/packages/web/components/MarkdownContent/index.ts +0 -1
  125. package/packages/web/components/MasonryGrid/MasonryGrid.tsx +0 -18
  126. package/packages/web/components/MasonryGrid/index.ts +0 -1
  127. package/packages/web/components/MomentumWidget/MomentumWidget.tsx +0 -119
  128. package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +0 -16
  129. package/packages/web/components/MomentumWidget/index.ts +0 -2
  130. package/packages/web/components/NowCard/NowCard.tsx +0 -118
  131. package/packages/web/components/NowCard/NowCard.types.ts +0 -16
  132. package/packages/web/components/NowCard/index.ts +0 -2
  133. package/packages/web/components/PageHeader/PageHeader.tsx +0 -24
  134. package/packages/web/components/PageHeader/index.ts +0 -1
  135. package/packages/web/components/ProgressRing/ProgressRing.constants.ts +0 -20
  136. package/packages/web/components/ProgressRing/ProgressRing.tsx +0 -51
  137. package/packages/web/components/ProgressRing/ProgressRing.types.ts +0 -11
  138. package/packages/web/components/ProgressRing/index.ts +0 -2
  139. package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +0 -54
  140. package/packages/web/components/ProjectAvatar/index.ts +0 -1
  141. package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +0 -37
  142. package/packages/web/components/ProjectColorDot/index.ts +0 -1
  143. package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +0 -104
  144. package/packages/web/components/ProjectSelectorModal/index.ts +0 -1
  145. package/packages/web/components/Providers/Providers.tsx +0 -48
  146. package/packages/web/components/Providers/index.ts +0 -1
  147. package/packages/web/components/QueueCard/QueueCard.tsx +0 -125
  148. package/packages/web/components/QueueCard/QueueCard.types.ts +0 -12
  149. package/packages/web/components/QueueCard/QueueCard.utils.ts +0 -12
  150. package/packages/web/components/QueueCard/index.ts +0 -2
  151. package/packages/web/components/RecoverCard/RecoverCard.tsx +0 -72
  152. package/packages/web/components/RecoverCard/RecoverCard.types.ts +0 -16
  153. package/packages/web/components/RecoverCard/index.ts +0 -2
  154. package/packages/web/components/RoadmapCard/RoadmapCard.tsx +0 -145
  155. package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +0 -16
  156. package/packages/web/components/RoadmapCard/index.ts +0 -2
  157. package/packages/web/components/ShipsCard/ShipsCard.tsx +0 -95
  158. package/packages/web/components/ShipsCard/ShipsCard.types.ts +0 -14
  159. package/packages/web/components/ShipsCard/ShipsCard.utils.ts +0 -4
  160. package/packages/web/components/ShipsCard/index.ts +0 -2
  161. package/packages/web/components/SparklineChart/SparklineChart.tsx +0 -40
  162. package/packages/web/components/SparklineChart/SparklineChart.types.ts +0 -6
  163. package/packages/web/components/SparklineChart/index.ts +0 -2
  164. package/packages/web/components/StatsMasonry/StatsMasonry.tsx +0 -95
  165. package/packages/web/components/StatsMasonry/index.ts +0 -1
  166. package/packages/web/components/StreakCard/StreakCard.constants.ts +0 -2
  167. package/packages/web/components/StreakCard/StreakCard.tsx +0 -55
  168. package/packages/web/components/StreakCard/StreakCard.types.ts +0 -4
  169. package/packages/web/components/StreakCard/index.ts +0 -2
  170. package/packages/web/components/TasksCounter/TasksCounter.tsx +0 -14
  171. package/packages/web/components/TasksCounter/TasksCounter.types.ts +0 -3
  172. package/packages/web/components/TasksCounter/index.ts +0 -2
  173. package/packages/web/components/TechStackBadges/TechStackBadges.tsx +0 -28
  174. package/packages/web/components/TechStackBadges/index.ts +0 -1
  175. package/packages/web/components/TerminalDock/DockToggleTab.tsx +0 -29
  176. package/packages/web/components/TerminalDock/TerminalDock.tsx +0 -386
  177. package/packages/web/components/TerminalDock/TerminalDockTab.tsx +0 -130
  178. package/packages/web/components/TerminalDock/TerminalTabBar.tsx +0 -142
  179. package/packages/web/components/TerminalDock/index.ts +0 -2
  180. package/packages/web/components/TerminalTabs/TerminalTab.tsx +0 -95
  181. package/packages/web/components/TerminalTabs/TerminalTabs.tsx +0 -211
  182. package/packages/web/components/TerminalTabs/index.ts +0 -1
  183. package/packages/web/components/VelocityBadge/VelocityBadge.tsx +0 -32
  184. package/packages/web/components/VelocityBadge/VelocityBadge.types.ts +0 -3
  185. package/packages/web/components/VelocityBadge/index.ts +0 -2
  186. package/packages/web/components/VelocityCard/VelocityCard.tsx +0 -73
  187. package/packages/web/components/VelocityCard/VelocityCard.types.ts +0 -7
  188. package/packages/web/components/VelocityCard/index.ts +0 -2
  189. package/packages/web/components/WeeklyReports/PrintableReport.tsx +0 -259
  190. package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +0 -187
  191. package/packages/web/components/WeeklyReports/WeekCalendar.tsx +0 -288
  192. package/packages/web/components/WeeklyReports/WeeklyReports.tsx +0 -149
  193. package/packages/web/components/WeeklyReports/index.ts +0 -4
  194. package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +0 -25
  195. package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +0 -4
  196. package/packages/web/components/WeeklySparkline/index.ts +0 -2
  197. package/packages/web/components/charts/SessionsChart.tsx +0 -175
  198. package/packages/web/components/ui/alert-dialog.tsx +0 -157
  199. package/packages/web/components/ui/badge.tsx +0 -46
  200. package/packages/web/components/ui/button.tsx +0 -60
  201. package/packages/web/components/ui/card.tsx +0 -92
  202. package/packages/web/components/ui/chart.tsx +0 -385
  203. package/packages/web/components/ui/dialog.tsx +0 -143
  204. package/packages/web/components/ui/drawer.tsx +0 -135
  205. package/packages/web/components/ui/dropdown-menu.tsx +0 -257
  206. package/packages/web/components/ui/input.tsx +0 -21
  207. package/packages/web/components/ui/scroll-area.tsx +0 -58
  208. package/packages/web/components/ui/select.tsx +0 -187
  209. package/packages/web/components/ui/sheet.tsx +0 -139
  210. package/packages/web/components/ui/tabs.tsx +0 -66
  211. package/packages/web/components/ui/tooltip.tsx +0 -61
  212. package/packages/web/components.json +0 -22
  213. package/packages/web/context/GlobalTerminalContext.tsx +0 -538
  214. package/packages/web/context/TerminalContext.tsx +0 -45
  215. package/packages/web/context/TerminalTabsContext.tsx +0 -181
  216. package/packages/web/eslint.config.mjs +0 -18
  217. package/packages/web/hooks/useClaudeTerminal.ts +0 -425
  218. package/packages/web/hooks/useProjectStats.ts +0 -93
  219. package/packages/web/hooks/useProjects.ts +0 -73
  220. package/packages/web/lib/actions/projects.ts +0 -15
  221. package/packages/web/lib/commands.ts +0 -81
  222. package/packages/web/lib/format.ts +0 -23
  223. package/packages/web/lib/generate-week-report.ts +0 -285
  224. package/packages/web/lib/parse-prjct-files.ts +0 -1123
  225. package/packages/web/lib/project-colors.ts +0 -58
  226. package/packages/web/lib/projects.ts +0 -506
  227. package/packages/web/lib/pty.ts +0 -101
  228. package/packages/web/lib/query-config.ts +0 -44
  229. package/packages/web/lib/services/index.ts +0 -9
  230. package/packages/web/lib/services/projects.server.ts +0 -66
  231. package/packages/web/lib/services/stats.server.ts +0 -562
  232. package/packages/web/lib/unified-loader.ts +0 -396
  233. package/packages/web/lib/utils.ts +0 -6
  234. package/packages/web/next-env.d.ts +0 -6
  235. package/packages/web/next.config.ts +0 -7
  236. package/packages/web/package.json +0 -57
  237. package/packages/web/postcss.config.mjs +0 -7
  238. package/packages/web/public/file.svg +0 -1
  239. package/packages/web/public/globe.svg +0 -1
  240. package/packages/web/public/next.svg +0 -1
  241. package/packages/web/public/vercel.svg +0 -1
  242. package/packages/web/public/window.svg +0 -1
  243. package/packages/web/server.ts +0 -312
  244. package/packages/web/tsconfig.json +0 -34
  245. 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'