prjct-cli 0.18.2 → 0.20.0

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