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