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,28 +0,0 @@
1
- import { Badge } from '@/components/ui/badge'
2
-
3
- interface TechStackBadgesProps {
4
- techStack: string[] | Record<string, string[]> | undefined
5
- max?: number
6
- }
7
-
8
- function normalizeTechStack(techStack: string[] | Record<string, string[]> | undefined): string[] {
9
- if (!techStack) return []
10
- if (Array.isArray(techStack)) return techStack
11
- // Handle object format: { languages: [...], frameworks: [...] }
12
- return Object.values(techStack).flat()
13
- }
14
-
15
- export function TechStackBadges({ techStack, max = 4 }: TechStackBadgesProps) {
16
- const normalized = normalizeTechStack(techStack)
17
- if (normalized.length === 0) return null
18
-
19
- return (
20
- <div className="flex gap-1">
21
- {normalized.slice(0, max).map((tech) => (
22
- <Badge key={tech} variant="secondary" className="text-xs px-1.5 py-0">
23
- {tech}
24
- </Badge>
25
- ))}
26
- </div>
27
- )
28
- }
@@ -1 +0,0 @@
1
- export { TechStackBadges } from './TechStackBadges'
@@ -1,29 +0,0 @@
1
- 'use client'
2
-
3
- import { ChevronUp } from 'lucide-react'
4
- import { cn } from '@/lib/utils'
5
-
6
- interface DockToggleTabProps {
7
- sessionCount: number
8
- connectedCount: number
9
- onClick: () => void
10
- }
11
-
12
- export function DockToggleTab({ sessionCount, connectedCount, onClick }: DockToggleTabProps) {
13
- const hasActiveSessions = sessionCount > 0
14
-
15
- return (
16
- <button
17
- onClick={onClick}
18
- className={cn(
19
- 'fixed bottom-0 left-1/2 -translate-x-1/2 z-40',
20
- 'flex items-center gap-2 px-4 py-2 rounded-t-lg',
21
- 'bg-orange-500 hover:bg-orange-600 text-white',
22
- 'shadow-lg transition-all duration-200',
23
- 'hover:-translate-y-0.5'
24
- )}
25
- >
26
- <ChevronUp className="w-4 h-4" />
27
- </button>
28
- )
29
- }
@@ -1,386 +0,0 @@
1
- 'use client'
2
-
3
- /**
4
- * TerminalDock v3.1
5
- * - Drawer with native overlay when >50% (respects sidebar)
6
- * - Commands on left side inside drawer
7
- * - Chrome-style tabs
8
- * - Rename tabs via double-click
9
- */
10
-
11
- import { useCallback, useEffect, useRef, useState } from 'react'
12
- import { usePathname, useParams } from 'next/navigation'
13
- import { useGlobalTerminal } from '@/context/GlobalTerminalContext'
14
- import { TerminalDockTab } from './TerminalDockTab'
15
- import { TerminalTabBar } from './TerminalTabBar'
16
- import { DockToggleTab } from './DockToggleTab'
17
- import { ProjectSelectorModal } from '@/components/ProjectSelectorModal'
18
- import { CommandBar } from '@/components/CommandBar'
19
- import { cn } from '@/lib/utils'
20
- import { Minus, SplitSquareHorizontal } from 'lucide-react'
21
- import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from '@/components/ui/tooltip'
22
- import {
23
- Drawer,
24
- DrawerContent,
25
- DrawerTitle,
26
- } from '@/components/ui/drawer'
27
- import { VisuallyHidden } from '@radix-ui/react-visually-hidden'
28
- import {
29
- Panel,
30
- PanelGroup,
31
- PanelResizeHandle,
32
- } from 'react-resizable-panels'
33
-
34
- // Threshold for showing drawer with overlay (50% of viewport)
35
- const DRAWER_THRESHOLD = 0.5
36
-
37
- export function TerminalDock() {
38
- const pathname = usePathname()
39
- const params = useParams()
40
- const {
41
- isDockOpen,
42
- dockHeight,
43
- isFullScreen,
44
- isSplitEnabled,
45
- setDockHeight,
46
- openDock,
47
- closeDock,
48
- setSplitEnabled,
49
- projectSessions,
50
- activeSessionId,
51
- secondActiveSessionId,
52
- switchSession,
53
- closeSession,
54
- createSessionForProject,
55
- sendCommandToActive,
56
- getActiveSession,
57
- getAllSessions,
58
- getLeftPanelSessions,
59
- getRightPanelSessions,
60
- getTotalSessionCount,
61
- getConnectedSessionCount,
62
- updateSession,
63
- } = useGlobalTerminal()
64
-
65
- const dockRef = useRef<HTMLDivElement>(null)
66
- const [isResizing, setIsResizing] = useState(false)
67
- const [showProjectSelector, setShowProjectSelector] = useState(false)
68
- const [projectSelectorPanel, setProjectSelectorPanel] = useState<'left' | 'right'>('left')
69
- const [viewportHeight, setViewportHeight] = useState(800)
70
-
71
-
72
- // Update viewport height on mount and resize
73
- useEffect(() => {
74
- const updateViewport = () => setViewportHeight(window.innerHeight)
75
- updateViewport()
76
- window.addEventListener('resize', updateViewport)
77
- return () => window.removeEventListener('resize', updateViewport)
78
- }, [])
79
-
80
- // Get all sessions
81
- const allSessions = getAllSessions()
82
- const activeSession = getActiveSession()
83
- const isConnected = activeSession?.isConnected ?? false
84
- const totalSessions = getTotalSessionCount()
85
- const connectedSessions = getConnectedSessionCount()
86
-
87
- // Always use drawer mode
88
- const useDrawerMode = true
89
-
90
- // Hide dock on code page (full-screen terminal)
91
- const isCodePage = pathname?.includes('/code')
92
-
93
- // Get current project context from URL
94
- const currentProjectId = params?.id as string | undefined
95
-
96
- // Resize handlers (only for non-drawer mode)
97
- const startResize = useCallback((e: React.MouseEvent) => {
98
- e.preventDefault()
99
- setIsResizing(true)
100
- }, [])
101
-
102
- useEffect(() => {
103
- if (!isResizing) return
104
-
105
- const handleMouseMove = (e: MouseEvent) => {
106
- const newHeight = window.innerHeight - e.clientY
107
- setDockHeight(newHeight)
108
- }
109
-
110
- const handleMouseUp = () => {
111
- setIsResizing(false)
112
- }
113
-
114
- document.addEventListener('mousemove', handleMouseMove)
115
- document.addEventListener('mouseup', handleMouseUp)
116
-
117
- return () => {
118
- document.removeEventListener('mousemove', handleMouseMove)
119
- document.removeEventListener('mouseup', handleMouseUp)
120
- }
121
- }, [isResizing, setDockHeight])
122
-
123
- // Handle new terminal - creates session directly in the specified panel
124
- const handleNewTerminal = useCallback((panel: 'left' | 'right' = 'left') => {
125
- if (currentProjectId) {
126
- const project = projectSessions.get(currentProjectId)
127
- if (project) {
128
- createSessionForProject(project.projectId, project.projectName, project.projectPath, panel)
129
- return
130
- }
131
- }
132
- setProjectSelectorPanel(panel)
133
- setShowProjectSelector(true)
134
- }, [currentProjectId, projectSessions, createSessionForProject])
135
-
136
- // Handle project selection from modal
137
- const handleSelectProject = useCallback((projectId: string, projectName: string, projectPath: string) => {
138
- createSessionForProject(projectId, projectName, projectPath, projectSelectorPanel)
139
- setShowProjectSelector(false)
140
- }, [createSessionForProject, projectSelectorPanel])
141
-
142
- // Handle command click
143
- const handleCommand = useCallback((cmd: string) => {
144
- sendCommandToActive(cmd)
145
- }, [sendCommandToActive])
146
-
147
- // Handle close session
148
- const handleCloseSession = useCallback((sessionId: string) => {
149
- const disconnectKey = `terminal_disconnect_${sessionId}`
150
- const disconnectFn = (window as unknown as Record<string, () => void>)[disconnectKey]
151
- if (disconnectFn) {
152
- disconnectFn()
153
- }
154
- closeSession(sessionId)
155
- }, [closeSession])
156
-
157
- // Handle rename session
158
- const handleRenameSession = useCallback((sessionId: string, newLabel: string) => {
159
- updateSession(sessionId, { label: newLabel })
160
- }, [updateSession])
161
-
162
- // Don't render if on code page (full-screen mode)
163
- if (isCodePage || isFullScreen) {
164
- return null
165
- }
166
-
167
- // Show toggle tab when dock is closed
168
- if (!isDockOpen) {
169
- return (
170
- <DockToggleTab
171
- sessionCount={totalSessions}
172
- connectedCount={connectedSessions}
173
- onClick={openDock}
174
- />
175
- )
176
- }
177
-
178
-
179
- // Terminal content (sessions)
180
- const TerminalContent = (
181
- <div className="flex-1 relative overflow-hidden">
182
- {allSessions.length === 0 ? (
183
- <div className="flex flex-col items-center justify-center h-full text-muted-foreground text-sm gap-2">
184
- <span>No terminal sessions</span>
185
- <button
186
- onClick={() => handleNewTerminal('left')}
187
- className="text-muted-foreground hover:text-foreground underline"
188
- >
189
- Open a terminal
190
- </button>
191
- </div>
192
- ) : isSplitEnabled ? (
193
- <PanelGroup direction="horizontal">
194
- <Panel defaultSize={50} minSize={20}>
195
- <div className="h-full flex flex-col">
196
- <div className="border-b border-border bg-muted/30">
197
- <TerminalTabBar
198
- sessions={getLeftPanelSessions()}
199
- activeSessionId={activeSessionId}
200
- onSwitchSession={(id) => switchSession(id, 'left')}
201
- onCloseSession={handleCloseSession}
202
- onNewTerminal={() => handleNewTerminal('left')}
203
- onRenameSession={handleRenameSession}
204
- />
205
- </div>
206
- <div className="flex-1 relative">
207
- {getLeftPanelSessions().length > 0 ? (
208
- getLeftPanelSessions().map((session) => (
209
- <TerminalDockTab
210
- key={session.id}
211
- session={session}
212
- isActive={session.id === activeSessionId}
213
- />
214
- ))
215
- ) : (
216
- <div className="absolute inset-0 flex items-center justify-center bg-card/95 text-muted-foreground text-sm">
217
- <button
218
- onClick={() => handleNewTerminal('left')}
219
- className="text-muted-foreground hover:text-foreground underline"
220
- >
221
- Open terminal in left panel
222
- </button>
223
- </div>
224
- )}
225
- </div>
226
- </div>
227
- </Panel>
228
-
229
- <PanelResizeHandle className="w-1 bg-border hover:bg-muted transition-colors" />
230
-
231
- <Panel defaultSize={50} minSize={20}>
232
- <div className="h-full flex flex-col">
233
- <div className="border-b border-border bg-muted/30">
234
- <TerminalTabBar
235
- sessions={getRightPanelSessions()}
236
- activeSessionId={secondActiveSessionId}
237
- onSwitchSession={(id) => switchSession(id, 'right')}
238
- onCloseSession={handleCloseSession}
239
- onNewTerminal={() => handleNewTerminal('right')}
240
- onRenameSession={handleRenameSession}
241
- />
242
- </div>
243
- <div className="flex-1 relative">
244
- {getRightPanelSessions().length > 0 ? (
245
- getRightPanelSessions().map((session) => (
246
- <TerminalDockTab
247
- key={`right-${session.id}`}
248
- session={session}
249
- isActive={session.id === secondActiveSessionId}
250
- />
251
- ))
252
- ) : (
253
- <div className="absolute inset-0 flex items-center justify-center bg-card/95 text-muted-foreground text-sm">
254
- <button
255
- onClick={() => handleNewTerminal('right')}
256
- className="text-muted-foreground hover:text-foreground underline"
257
- >
258
- Open terminal in right panel
259
- </button>
260
- </div>
261
- )}
262
- </div>
263
- </div>
264
- </Panel>
265
- </PanelGroup>
266
- ) : (
267
- <div className="h-full flex flex-col">
268
- <div className="border-b border-border bg-muted/30">
269
- <TerminalTabBar
270
- sessions={allSessions}
271
- activeSessionId={activeSessionId}
272
- onSwitchSession={(id) => switchSession(id)}
273
- onCloseSession={handleCloseSession}
274
- onNewTerminal={() => handleNewTerminal('left')}
275
- onRenameSession={handleRenameSession}
276
- />
277
- </div>
278
- <div className="flex-1 relative">
279
- {allSessions.map((session) => (
280
- <TerminalDockTab
281
- key={session.id}
282
- session={session}
283
- isActive={session.id === activeSessionId}
284
- />
285
- ))}
286
- </div>
287
- </div>
288
- )}
289
- </div>
290
- )
291
-
292
- // Header with split/minimize (right side)
293
- const HeaderActions = (
294
- <div className="flex items-center gap-1 px-2">
295
- <Tooltip>
296
- <TooltipTrigger asChild>
297
- <button
298
- onClick={() => setSplitEnabled(!isSplitEnabled)}
299
- className={cn(
300
- 'p-1.5 rounded transition-colors',
301
- isSplitEnabled
302
- ? 'bg-accent text-accent-foreground'
303
- : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
304
- )}
305
- >
306
- <SplitSquareHorizontal className="w-4 h-4" />
307
- </button>
308
- </TooltipTrigger>
309
- <TooltipContent>{isSplitEnabled ? 'Single view' : 'Split view'}</TooltipContent>
310
- </Tooltip>
311
-
312
- <Tooltip>
313
- <TooltipTrigger asChild>
314
- <button
315
- onClick={closeDock}
316
- className="p-1.5 rounded text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors"
317
- >
318
- <Minus className="w-4 h-4" />
319
- </button>
320
- </TooltipTrigger>
321
- <TooltipContent>Minimize</TooltipContent>
322
- </Tooltip>
323
- </div>
324
- )
325
-
326
- return (
327
- <TooltipProvider>
328
- {useDrawerMode ? (
329
- // Simple drawer
330
- <Drawer open={isDockOpen} onOpenChange={(open) => !open && closeDock()}>
331
- <DrawerContent className="h-[60vh] flex flex-col">
332
- <VisuallyHidden>
333
- <DrawerTitle>Terminal</DrawerTitle>
334
- </VisuallyHidden>
335
-
336
- {/* Header: commands left, actions right */}
337
- <div className="flex items-center justify-between border-b border-border bg-card/95 shrink-0">
338
- <CommandBar isConnected={isConnected} onCommand={handleCommand} />
339
- {HeaderActions}
340
- </div>
341
-
342
- {TerminalContent}
343
- </DrawerContent>
344
- </Drawer>
345
- ) : (
346
- // Regular dock when <=50%
347
- <div
348
- ref={dockRef}
349
- className={cn(
350
- 'relative flex flex-col shrink-0',
351
- 'bg-card border-t border-border',
352
- isResizing && 'select-none'
353
- )}
354
- style={{ height: dockHeight }}
355
- >
356
- {/* Resize Handle */}
357
- <div
358
- className={cn(
359
- 'absolute top-0 left-0 right-0 h-2 cursor-ns-resize z-10',
360
- 'hover:bg-muted transition-colors',
361
- 'before:absolute before:inset-x-0 before:top-0 before:h-px before:bg-border',
362
- isResizing && 'bg-muted'
363
- )}
364
- onMouseDown={startResize}
365
- >
366
- <div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-12 h-1 rounded-full bg-muted-foreground/30" />
367
- </div>
368
-
369
- {/* Header: commands left, actions right */}
370
- <div className="flex items-center justify-between border-b border-border bg-card/95 shrink-0 mt-2">
371
- <CommandBar isConnected={isConnected} onCommand={handleCommand} />
372
- {HeaderActions}
373
- </div>
374
-
375
- {TerminalContent}
376
- </div>
377
- )}
378
-
379
- <ProjectSelectorModal
380
- isOpen={showProjectSelector}
381
- onClose={() => setShowProjectSelector(false)}
382
- onSelectProject={handleSelectProject}
383
- />
384
- </TooltipProvider>
385
- )
386
- }
@@ -1,130 +0,0 @@
1
- 'use client'
2
-
3
- /**
4
- * TerminalDockTab - Single terminal instance for the dock
5
- * With ResizeObserver for proper fit on resize
6
- */
7
-
8
- import { useEffect, useRef, useCallback } from 'react'
9
- import { useClaudeTerminal } from '@/hooks/useClaudeTerminal'
10
- import { useGlobalTerminal, type GlobalTerminalSession } from '@/context/GlobalTerminalContext'
11
-
12
- interface TerminalDockTabProps {
13
- session: GlobalTerminalSession
14
- isActive: boolean
15
- }
16
-
17
- export function TerminalDockTab({ session, isActive }: TerminalDockTabProps) {
18
- const containerRef = useRef<HTMLDivElement>(null)
19
- const wrapperRef = useRef<HTMLDivElement>(null)
20
- const hasInitializedRef = useRef(false)
21
- const hasConnectedRef = useRef(false)
22
- const fitTimeoutRef = useRef<NodeJS.Timeout | null>(null)
23
- const { updateSession, registerSendInput, registerFocusTerminal, dockHeight } = useGlobalTerminal()
24
-
25
- const handleConnect = useCallback(() => {
26
- updateSession(session.id, { isConnected: true, isLoading: false })
27
- }, [session.id, updateSession])
28
-
29
- const handleDisconnect = useCallback(() => {
30
- updateSession(session.id, { isConnected: false, isLoading: false })
31
- }, [session.id, updateSession])
32
-
33
- const handleError = useCallback((error: string) => {
34
- console.error(`[TerminalDock ${session.id}] Error:`, error)
35
- updateSession(session.id, { isLoading: false })
36
- }, [session.id, updateSession])
37
-
38
- const {
39
- initTerminal,
40
- connect,
41
- disconnect,
42
- sendInput,
43
- focusTerminal,
44
- fit,
45
- } = useClaudeTerminal({
46
- sessionId: session.id,
47
- projectDir: session.projectPath,
48
- onConnect: handleConnect,
49
- onDisconnect: handleDisconnect,
50
- onError: handleError,
51
- })
52
-
53
- // Debounced fit function
54
- const debouncedFit = useCallback(() => {
55
- if (fitTimeoutRef.current) {
56
- clearTimeout(fitTimeoutRef.current)
57
- }
58
- fitTimeoutRef.current = setTimeout(() => {
59
- if (isActive && hasInitializedRef.current) {
60
- fit()
61
- }
62
- }, 50)
63
- }, [fit, isActive])
64
-
65
- // Initialize terminal AND connect - only once
66
- useEffect(() => {
67
- if (containerRef.current && !hasInitializedRef.current) {
68
- hasInitializedRef.current = true
69
-
70
- initTerminal(containerRef.current).then(() => {
71
- if (!hasConnectedRef.current) {
72
- hasConnectedRef.current = true
73
- connect()
74
- }
75
- })
76
- }
77
- }, []) // Empty deps - run only on mount
78
-
79
- // Re-fit terminal when tab becomes active or dock height changes
80
- useEffect(() => {
81
- if (isActive && hasInitializedRef.current) {
82
- requestAnimationFrame(() => {
83
- fit()
84
- })
85
- }
86
- }, [isActive, fit, dockHeight])
87
-
88
- // ResizeObserver for container size changes
89
- useEffect(() => {
90
- if (!wrapperRef.current) return
91
-
92
- const resizeObserver = new ResizeObserver(() => {
93
- debouncedFit()
94
- })
95
-
96
- resizeObserver.observe(wrapperRef.current)
97
-
98
- return () => {
99
- resizeObserver.disconnect()
100
- if (fitTimeoutRef.current) {
101
- clearTimeout(fitTimeoutRef.current)
102
- }
103
- }
104
- }, [debouncedFit])
105
-
106
- // Register sendInput and focusTerminal for this session
107
- useEffect(() => {
108
- registerSendInput(session.id, sendInput)
109
- registerFocusTerminal(session.id, focusTerminal)
110
- }, [session.id, sendInput, focusTerminal, registerSendInput, registerFocusTerminal])
111
-
112
- // Expose disconnect for external use
113
- useEffect(() => {
114
- const key = `terminal_disconnect_${session.id}`
115
- ;(window as unknown as Record<string, () => void>)[key] = disconnect
116
- return () => {
117
- delete (window as unknown as Record<string, () => void>)[key]
118
- }
119
- }, [session.id, disconnect])
120
-
121
- return (
122
- <div
123
- ref={wrapperRef}
124
- className="absolute inset-0 bg-[#0a0a0f] px-2 py-2"
125
- style={{ display: isActive ? 'block' : 'none' }}
126
- >
127
- <div ref={containerRef} className="h-full w-full" />
128
- </div>
129
- )
130
- }