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,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
- }