@atercates/claude-deck 0.2.1

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 (293) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/app/api/claude/hidden/route.ts +66 -0
  4. package/app/api/claude/projects/[name]/sessions/route.ts +71 -0
  5. package/app/api/claude/projects/route.ts +44 -0
  6. package/app/api/code-search/available/route.ts +12 -0
  7. package/app/api/code-search/route.ts +47 -0
  8. package/app/api/dev-servers/[id]/logs/route.ts +23 -0
  9. package/app/api/dev-servers/[id]/restart/route.ts +20 -0
  10. package/app/api/dev-servers/[id]/route.ts +51 -0
  11. package/app/api/dev-servers/[id]/stop/route.ts +20 -0
  12. package/app/api/dev-servers/detect/route.ts +39 -0
  13. package/app/api/dev-servers/route.ts +48 -0
  14. package/app/api/exec/route.ts +60 -0
  15. package/app/api/files/content/route.ts +76 -0
  16. package/app/api/files/route.ts +37 -0
  17. package/app/api/files/upload-temp/route.ts +41 -0
  18. package/app/api/git/check/route.ts +54 -0
  19. package/app/api/git/clone/route.ts +99 -0
  20. package/app/api/git/commit/route.ts +75 -0
  21. package/app/api/git/discard/route.ts +38 -0
  22. package/app/api/git/file-content/route.ts +64 -0
  23. package/app/api/git/history/[hash]/diff/route.ts +38 -0
  24. package/app/api/git/history/[hash]/route.ts +34 -0
  25. package/app/api/git/history/route.ts +27 -0
  26. package/app/api/git/multi-status/route.ts +46 -0
  27. package/app/api/git/pr/route.ts +164 -0
  28. package/app/api/git/push/route.ts +64 -0
  29. package/app/api/git/stage/route.ts +40 -0
  30. package/app/api/git/status/route.ts +51 -0
  31. package/app/api/git/unstage/route.ts +46 -0
  32. package/app/api/groups/[...path]/route.ts +136 -0
  33. package/app/api/groups/route.ts +93 -0
  34. package/app/api/orchestrate/spawn/route.ts +45 -0
  35. package/app/api/orchestrate/workers/[id]/route.ts +89 -0
  36. package/app/api/orchestrate/workers/route.ts +31 -0
  37. package/app/api/projects/[id]/detect/route.ts +27 -0
  38. package/app/api/projects/[id]/dev-servers/[dsId]/route.ts +66 -0
  39. package/app/api/projects/[id]/dev-servers/route.ts +51 -0
  40. package/app/api/projects/[id]/repositories/[repoId]/route.ts +67 -0
  41. package/app/api/projects/[id]/repositories/route.ts +74 -0
  42. package/app/api/projects/[id]/route.ts +108 -0
  43. package/app/api/projects/detect/route.ts +33 -0
  44. package/app/api/projects/route.ts +59 -0
  45. package/app/api/sessions/[id]/claude-session/route.ts +42 -0
  46. package/app/api/sessions/[id]/fork/route.ts +74 -0
  47. package/app/api/sessions/[id]/mcp-config/route.ts +34 -0
  48. package/app/api/sessions/[id]/messages/route.ts +60 -0
  49. package/app/api/sessions/[id]/pr/route.ts +188 -0
  50. package/app/api/sessions/[id]/preview/route.ts +42 -0
  51. package/app/api/sessions/[id]/route.ts +229 -0
  52. package/app/api/sessions/[id]/send-keys/route.ts +119 -0
  53. package/app/api/sessions/[id]/summarize/route.ts +331 -0
  54. package/app/api/sessions/init-script/route.ts +84 -0
  55. package/app/api/sessions/route.ts +209 -0
  56. package/app/api/sessions/status/route.ts +237 -0
  57. package/app/api/system/route.ts +9 -0
  58. package/app/api/tmux/kill-all/route.ts +57 -0
  59. package/app/api/tmux/rename/route.ts +30 -0
  60. package/app/globals.css +174 -0
  61. package/app/icon.svg +11 -0
  62. package/app/layout.tsx +122 -0
  63. package/app/page.tsx +629 -0
  64. package/components/ChatMessage.tsx +65 -0
  65. package/components/ChatView.tsx +276 -0
  66. package/components/ClaudeProjects/ClaudeProjectCard.tsx +195 -0
  67. package/components/ClaudeProjects/ClaudeProjectsSection.tsx +89 -0
  68. package/components/ClaudeProjects/ClaudeSessionCard.tsx +100 -0
  69. package/components/ClaudeProjects/index.ts +1 -0
  70. package/components/CodeSearch/CodeSearchResults.tsx +177 -0
  71. package/components/ConductorPanel.tsx +256 -0
  72. package/components/DevServers/DevServerCard.tsx +311 -0
  73. package/components/DevServers/DevServersSection.tsx +91 -0
  74. package/components/DevServers/ServerLogsModal.tsx +151 -0
  75. package/components/DevServers/StartServerDialog.tsx +359 -0
  76. package/components/DevServers/index.ts +4 -0
  77. package/components/DiffViewer/DiffModal.tsx +151 -0
  78. package/components/DiffViewer/UnifiedDiff.tsx +185 -0
  79. package/components/DiffViewer/index.tsx +2 -0
  80. package/components/DirectoryPicker.tsx +355 -0
  81. package/components/FileExplorer/FileEditor.tsx +276 -0
  82. package/components/FileExplorer/FileTabs.tsx +118 -0
  83. package/components/FileExplorer/FileTree.tsx +214 -0
  84. package/components/FileExplorer/HtmlRenderer.tsx +16 -0
  85. package/components/FileExplorer/MarkdownRenderer.tsx +18 -0
  86. package/components/FileExplorer/index.tsx +520 -0
  87. package/components/FilePicker.tsx +339 -0
  88. package/components/FolderPicker.tsx +201 -0
  89. package/components/GitDrawer/FileEditDialog.tsx +400 -0
  90. package/components/GitDrawer/index.tsx +464 -0
  91. package/components/GitPanel/CommitForm.tsx +205 -0
  92. package/components/GitPanel/CommitHistory.tsx +174 -0
  93. package/components/GitPanel/CommitItem.tsx +196 -0
  94. package/components/GitPanel/FileChanges.tsx +414 -0
  95. package/components/GitPanel/GitPanelTabs.tsx +39 -0
  96. package/components/GitPanel/index.tsx +817 -0
  97. package/components/MessageInput.tsx +82 -0
  98. package/components/NewClaudeSessionDialog.tsx +166 -0
  99. package/components/NewSessionDialog/AdvancedSettings.tsx +78 -0
  100. package/components/NewSessionDialog/AgentSelector.tsx +37 -0
  101. package/components/NewSessionDialog/CreatingOverlay.tsx +94 -0
  102. package/components/NewSessionDialog/NewSessionDialog.types.ts +136 -0
  103. package/components/NewSessionDialog/ProjectSelector.tsx +146 -0
  104. package/components/NewSessionDialog/WorkingDirectoryInput.tsx +55 -0
  105. package/components/NewSessionDialog/WorktreeSection.tsx +92 -0
  106. package/components/NewSessionDialog/hooks/useNewSessionForm.ts +370 -0
  107. package/components/NewSessionDialog/index.tsx +106 -0
  108. package/components/NotificationSettings.tsx +127 -0
  109. package/components/PRCreationModal.tsx +272 -0
  110. package/components/Pane/DesktopTabBar.tsx +353 -0
  111. package/components/Pane/MobileTabBar.tsx +210 -0
  112. package/components/Pane/OpenInVSCode.tsx +69 -0
  113. package/components/Pane/PaneSkeletons.tsx +57 -0
  114. package/components/Pane/index.tsx +558 -0
  115. package/components/PaneLayout.tsx +60 -0
  116. package/components/Projects/DevServersSection.tsx +140 -0
  117. package/components/Projects/DirectoryField.tsx +92 -0
  118. package/components/Projects/NewProjectDialog.tsx +188 -0
  119. package/components/Projects/NewProjectDialog.types.ts +46 -0
  120. package/components/Projects/ProjectCard.tsx +276 -0
  121. package/components/Projects/ProjectSettingsDialog.tsx +811 -0
  122. package/components/Projects/hooks/useNewProjectForm.ts +249 -0
  123. package/components/Projects/index.ts +3 -0
  124. package/components/Providers.tsx +49 -0
  125. package/components/QuickSwitcher.tsx +306 -0
  126. package/components/SessionList/KillAllConfirm.tsx +46 -0
  127. package/components/SessionList/SelectionToolbar.tsx +164 -0
  128. package/components/SessionList/SessionList.types.ts +37 -0
  129. package/components/SessionList/SessionListHeader.tsx +71 -0
  130. package/components/SessionList/hooks/useSessionListMutations.ts +269 -0
  131. package/components/SessionList/index.tsx +189 -0
  132. package/components/ShellDrawer/index.tsx +106 -0
  133. package/components/SidebarFooter.tsx +55 -0
  134. package/components/Terminal/KeybarToggleButton.tsx +45 -0
  135. package/components/Terminal/ScrollToBottomButton.tsx +32 -0
  136. package/components/Terminal/SearchBar.tsx +71 -0
  137. package/components/Terminal/TerminalToolbar.tsx +551 -0
  138. package/components/Terminal/VirtualKeyboard.tsx +711 -0
  139. package/components/Terminal/constants.ts +20 -0
  140. package/components/Terminal/hooks/index.ts +5 -0
  141. package/components/Terminal/hooks/resize-handlers.ts +140 -0
  142. package/components/Terminal/hooks/terminal-init.ts +151 -0
  143. package/components/Terminal/hooks/touch-scroll.ts +155 -0
  144. package/components/Terminal/hooks/useTerminalConnection.ts +282 -0
  145. package/components/Terminal/hooks/useTerminalConnection.types.ts +39 -0
  146. package/components/Terminal/hooks/useTerminalSearch.ts +103 -0
  147. package/components/Terminal/hooks/websocket-connection.ts +274 -0
  148. package/components/Terminal/index.tsx +320 -0
  149. package/components/ThemeToggle.tsx +168 -0
  150. package/components/TmuxSessions.tsx +132 -0
  151. package/components/ToolCallDisplay.tsx +71 -0
  152. package/components/WorkerCard.tsx +245 -0
  153. package/components/a/ABadge.tsx +115 -0
  154. package/components/a/AButton.tsx +163 -0
  155. package/components/a/ADialog.tsx +93 -0
  156. package/components/a/ADropdownMenu.tsx +279 -0
  157. package/components/a/AIconButton.tsx +190 -0
  158. package/components/a/ASheet.tsx +150 -0
  159. package/components/a/ATooltip.tsx +77 -0
  160. package/components/a/index.ts +64 -0
  161. package/components/mobile/SwipeSidebar.tsx +122 -0
  162. package/components/ui/badge.tsx +41 -0
  163. package/components/ui/button.tsx +60 -0
  164. package/components/ui/context-menu.tsx +197 -0
  165. package/components/ui/dialog.tsx +143 -0
  166. package/components/ui/dropdown-menu.tsx +257 -0
  167. package/components/ui/input.tsx +21 -0
  168. package/components/ui/scroll-area.tsx +52 -0
  169. package/components/ui/select.tsx +159 -0
  170. package/components/ui/skeleton.tsx +111 -0
  171. package/components/ui/switch.tsx +31 -0
  172. package/components/ui/textarea.tsx +21 -0
  173. package/components/ui/tooltip.tsx +32 -0
  174. package/components/views/DesktopView.tsx +244 -0
  175. package/components/views/MobileView.tsx +110 -0
  176. package/components/views/types.ts +75 -0
  177. package/contexts/PaneContext.tsx +336 -0
  178. package/data/claude/index.ts +9 -0
  179. package/data/claude/keys.ts +6 -0
  180. package/data/claude/queries.ts +120 -0
  181. package/data/claude/useClaudeUpdates.ts +37 -0
  182. package/data/code-search/index.ts +2 -0
  183. package/data/code-search/keys.ts +7 -0
  184. package/data/code-search/queries.ts +61 -0
  185. package/data/dev-servers/index.ts +8 -0
  186. package/data/dev-servers/keys.ts +4 -0
  187. package/data/dev-servers/queries.ts +104 -0
  188. package/data/files/index.ts +3 -0
  189. package/data/files/keys.ts +4 -0
  190. package/data/files/queries.ts +25 -0
  191. package/data/git/keys.ts +15 -0
  192. package/data/git/queries.ts +395 -0
  193. package/data/groups/index.ts +1 -0
  194. package/data/groups/mutations.ts +95 -0
  195. package/data/projects/index.ts +10 -0
  196. package/data/projects/keys.ts +4 -0
  197. package/data/projects/queries.ts +193 -0
  198. package/data/repositories/index.ts +7 -0
  199. package/data/repositories/keys.ts +5 -0
  200. package/data/repositories/queries.ts +122 -0
  201. package/data/sessions/index.ts +12 -0
  202. package/data/sessions/keys.ts +8 -0
  203. package/data/sessions/queries.ts +218 -0
  204. package/data/statuses/index.ts +1 -0
  205. package/data/statuses/queries.ts +69 -0
  206. package/hooks/useCopyToClipboard.ts +48 -0
  207. package/hooks/useDevServersManager.ts +73 -0
  208. package/hooks/useDirectoryBrowser.ts +90 -0
  209. package/hooks/useDrawerAnimation.ts +27 -0
  210. package/hooks/useFileDrop.ts +87 -0
  211. package/hooks/useFileEditor.ts +184 -0
  212. package/hooks/useGroups.ts +37 -0
  213. package/hooks/useHomePath.ts +34 -0
  214. package/hooks/useKeyRepeat.ts +55 -0
  215. package/hooks/useKeybarVisibility.ts +42 -0
  216. package/hooks/useNotifications.ts +257 -0
  217. package/hooks/useProjects.ts +53 -0
  218. package/hooks/useSessionStatuses.ts +30 -0
  219. package/hooks/useSessions.ts +86 -0
  220. package/hooks/useSpeechRecognition.ts +124 -0
  221. package/hooks/useViewport.ts +32 -0
  222. package/hooks/useViewportHeight.ts +50 -0
  223. package/lib/async-operations.ts +35 -0
  224. package/lib/banner.ts +81 -0
  225. package/lib/claude/jsonl-cache.ts +86 -0
  226. package/lib/claude/jsonl-reader.ts +271 -0
  227. package/lib/claude/process-manager.ts +278 -0
  228. package/lib/claude/stream-parser.ts +173 -0
  229. package/lib/claude/types.ts +154 -0
  230. package/lib/claude/watcher.ts +71 -0
  231. package/lib/client/session-registry.ts +111 -0
  232. package/lib/code-search.ts +121 -0
  233. package/lib/db/index.ts +48 -0
  234. package/lib/db/migrations.ts +45 -0
  235. package/lib/db/queries.ts +460 -0
  236. package/lib/db/schema.ts +114 -0
  237. package/lib/db/types.ts +92 -0
  238. package/lib/db.ts +2 -0
  239. package/lib/dev-servers.ts +509 -0
  240. package/lib/diff-parser.ts +221 -0
  241. package/lib/env-setup.ts +285 -0
  242. package/lib/file-upload.ts +34 -0
  243. package/lib/file-utils.ts +50 -0
  244. package/lib/files.ts +207 -0
  245. package/lib/git-history.ts +294 -0
  246. package/lib/git-status.ts +391 -0
  247. package/lib/git.ts +257 -0
  248. package/lib/mcp-config.ts +81 -0
  249. package/lib/multi-repo-git.ts +179 -0
  250. package/lib/notifications.ts +219 -0
  251. package/lib/orchestration.ts +448 -0
  252. package/lib/panes.ts +232 -0
  253. package/lib/ports.ts +97 -0
  254. package/lib/pr-generation.ts +307 -0
  255. package/lib/pr.ts +234 -0
  256. package/lib/projects.ts +578 -0
  257. package/lib/providers/registry.ts +70 -0
  258. package/lib/providers.ts +121 -0
  259. package/lib/query-client.ts +14 -0
  260. package/lib/rangeSelectionUtils.ts +65 -0
  261. package/lib/status-detector.ts +375 -0
  262. package/lib/terminal-themes.ts +265 -0
  263. package/lib/theme-config.ts +327 -0
  264. package/lib/utils.ts +6 -0
  265. package/lib/worktrees.ts +262 -0
  266. package/mcp/orchestration-server.ts +438 -0
  267. package/package.json +139 -0
  268. package/postcss.config.mjs +7 -0
  269. package/public/icon.svg +10 -0
  270. package/public/icons/icon-128x128.png +0 -0
  271. package/public/icons/icon-144x144.png +0 -0
  272. package/public/icons/icon-152x152.png +0 -0
  273. package/public/icons/icon-192x192.png +0 -0
  274. package/public/icons/icon-384x384.png +0 -0
  275. package/public/icons/icon-512x512.png +0 -0
  276. package/public/icons/icon-72x72.png +0 -0
  277. package/public/icons/icon-96x96.png +0 -0
  278. package/public/manifest.json +61 -0
  279. package/public/sw.js +64 -0
  280. package/scripts/agent-os +91 -0
  281. package/scripts/install.sh +48 -0
  282. package/scripts/lib/ai-clis.sh +132 -0
  283. package/scripts/lib/commands.sh +487 -0
  284. package/scripts/lib/common.sh +89 -0
  285. package/scripts/lib/prerequisites.sh +462 -0
  286. package/scripts/setup.sh +134 -0
  287. package/server.ts +155 -0
  288. package/stores/fileOpen.ts +26 -0
  289. package/stores/index.ts +1 -0
  290. package/stores/initialPrompt.ts +24 -0
  291. package/stores/sessionSelection.ts +48 -0
  292. package/styles/themes.css +603 -0
  293. package/tsconfig.json +33 -0
@@ -0,0 +1,353 @@
1
+ "use client";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+ import {
5
+ SplitSquareHorizontal,
6
+ SplitSquareVertical,
7
+ X,
8
+ Unplug,
9
+ Plug,
10
+ Plus,
11
+ FolderOpen,
12
+ GitBranch,
13
+ Users,
14
+ Home,
15
+ } from "lucide-react";
16
+ import { OpenInVSCode } from "./OpenInVSCode";
17
+ import {
18
+ Tooltip,
19
+ TooltipContent,
20
+ TooltipTrigger,
21
+ } from "@/components/ui/tooltip";
22
+ import { cn } from "@/lib/utils";
23
+ import type { Session } from "@/lib/db";
24
+
25
+ type ViewMode = "terminal" | "files" | "git" | "workers";
26
+
27
+ interface Tab {
28
+ id: string;
29
+ sessionId: string | null;
30
+ attachedTmux: string | null;
31
+ }
32
+
33
+ interface DesktopTabBarProps {
34
+ tabs: Tab[];
35
+ activeTabId: string;
36
+ session: Session | null | undefined;
37
+ sessions: Session[];
38
+ viewMode: ViewMode;
39
+ isFocused: boolean;
40
+ isConductor: boolean;
41
+ workerCount: number;
42
+ canSplit: boolean;
43
+ canClose: boolean;
44
+ hasAttachedTmux: boolean;
45
+ gitDrawerOpen: boolean;
46
+ shellDrawerOpen: boolean;
47
+ onTabSwitch: (tabId: string) => void;
48
+ onTabClose: (tabId: string) => void;
49
+ onTabAdd: () => void;
50
+ onViewModeChange: (mode: ViewMode) => void;
51
+ onGitDrawerToggle: () => void;
52
+ onShellDrawerToggle: () => void;
53
+ onSplitHorizontal: () => void;
54
+ onSplitVertical: () => void;
55
+ onClose: () => void;
56
+ onDetach: () => void;
57
+ onReattach: () => void;
58
+ hasDetachedTmux: boolean;
59
+ }
60
+
61
+ export function DesktopTabBar({
62
+ tabs,
63
+ activeTabId,
64
+ session,
65
+ sessions,
66
+ viewMode,
67
+ isFocused,
68
+ isConductor,
69
+ workerCount,
70
+ canSplit,
71
+ canClose,
72
+ hasAttachedTmux,
73
+ gitDrawerOpen,
74
+ shellDrawerOpen,
75
+ onTabSwitch,
76
+ onTabClose,
77
+ onTabAdd,
78
+ onViewModeChange,
79
+ onGitDrawerToggle,
80
+ onShellDrawerToggle,
81
+ onSplitHorizontal,
82
+ onSplitVertical,
83
+ onClose,
84
+ onDetach,
85
+ onReattach,
86
+ hasDetachedTmux,
87
+ }: DesktopTabBarProps) {
88
+ const getTabName = (tab: Tab) => {
89
+ if (tab.sessionId) {
90
+ const s = sessions.find((sess) => sess.id === tab.sessionId);
91
+ return s?.name || tab.attachedTmux || "Session";
92
+ }
93
+ if (tab.attachedTmux) return tab.attachedTmux;
94
+ return "New Tab";
95
+ };
96
+
97
+ return (
98
+ <div
99
+ className={cn(
100
+ "flex items-center gap-1 overflow-x-auto px-1 pt-1 transition-colors",
101
+ isFocused ? "bg-muted" : "bg-muted/50"
102
+ )}
103
+ >
104
+ {/* Tabs */}
105
+ <div className="flex min-w-0 flex-1 items-center gap-0.5">
106
+ {tabs.map((tab) => (
107
+ <div
108
+ key={tab.id}
109
+ onClick={(e) => {
110
+ e.stopPropagation();
111
+ onTabSwitch(tab.id);
112
+ }}
113
+ className={cn(
114
+ "group flex cursor-pointer items-center gap-1.5 rounded-t-md px-3 py-1.5 text-xs transition-colors",
115
+ tab.id === activeTabId
116
+ ? "bg-background text-foreground"
117
+ : "text-muted-foreground hover:text-foreground/80 hover:bg-accent/50"
118
+ )}
119
+ >
120
+ <span className="max-w-[120px] truncate">{getTabName(tab)}</span>
121
+ {tabs.length > 1 && (
122
+ <button
123
+ onClick={(e) => {
124
+ e.stopPropagation();
125
+ onTabClose(tab.id);
126
+ }}
127
+ className="hover:text-foreground ml-1 opacity-0 group-hover:opacity-100"
128
+ >
129
+ <X className="h-3 w-3" />
130
+ </button>
131
+ )}
132
+ </div>
133
+ ))}
134
+ <Tooltip>
135
+ <TooltipTrigger asChild>
136
+ <Button
137
+ variant="ghost"
138
+ size="icon-sm"
139
+ onClick={(e) => {
140
+ e.stopPropagation();
141
+ onTabAdd();
142
+ }}
143
+ className="mx-1 h-6 w-6"
144
+ >
145
+ <Plus className="h-3 w-3" />
146
+ </Button>
147
+ </TooltipTrigger>
148
+ <TooltipContent>New tab</TooltipContent>
149
+ </Tooltip>
150
+ </div>
151
+
152
+ {/* View Toggle */}
153
+ {session?.working_directory && (
154
+ <div className="bg-accent/50 mx-2 flex items-center rounded-md p-0.5">
155
+ <Tooltip>
156
+ <TooltipTrigger asChild>
157
+ <button
158
+ onClick={(e) => {
159
+ e.stopPropagation();
160
+ onViewModeChange("terminal");
161
+ }}
162
+ className={cn(
163
+ "rounded px-2 py-1 transition-colors",
164
+ viewMode === "terminal"
165
+ ? "bg-secondary text-foreground"
166
+ : "text-muted-foreground hover:text-foreground"
167
+ )}
168
+ >
169
+ <Home className="h-3.5 w-3.5" />
170
+ </button>
171
+ </TooltipTrigger>
172
+ <TooltipContent>Terminal</TooltipContent>
173
+ </Tooltip>
174
+ <Tooltip>
175
+ <TooltipTrigger asChild>
176
+ <button
177
+ onClick={(e) => {
178
+ e.stopPropagation();
179
+ onViewModeChange("files");
180
+ }}
181
+ className={cn(
182
+ "rounded px-2 py-1 transition-colors",
183
+ viewMode === "files"
184
+ ? "bg-secondary text-foreground"
185
+ : "text-muted-foreground hover:text-foreground"
186
+ )}
187
+ >
188
+ <FolderOpen className="h-3.5 w-3.5" />
189
+ </button>
190
+ </TooltipTrigger>
191
+ <TooltipContent>Files</TooltipContent>
192
+ </Tooltip>
193
+ <Tooltip>
194
+ <TooltipTrigger asChild>
195
+ <button
196
+ onClick={(e) => {
197
+ e.stopPropagation();
198
+ onGitDrawerToggle();
199
+ }}
200
+ className={cn(
201
+ "rounded px-2 py-1 transition-colors",
202
+ gitDrawerOpen
203
+ ? "bg-secondary text-foreground"
204
+ : "text-muted-foreground hover:text-foreground"
205
+ )}
206
+ >
207
+ <GitBranch className="h-3.5 w-3.5" />
208
+ </button>
209
+ </TooltipTrigger>
210
+ <TooltipContent>Git</TooltipContent>
211
+ </Tooltip>
212
+ <Tooltip>
213
+ <TooltipTrigger asChild>
214
+ <button
215
+ onClick={(e) => {
216
+ e.stopPropagation();
217
+ onShellDrawerToggle();
218
+ }}
219
+ className={cn(
220
+ "rounded px-2 py-1 font-mono text-xs transition-colors",
221
+ shellDrawerOpen
222
+ ? "bg-secondary text-foreground"
223
+ : "text-muted-foreground hover:text-foreground"
224
+ )}
225
+ >
226
+ {">_"}
227
+ </button>
228
+ </TooltipTrigger>
229
+ <TooltipContent>Shell</TooltipContent>
230
+ </Tooltip>
231
+ {isConductor && (
232
+ <Tooltip>
233
+ <TooltipTrigger asChild>
234
+ <button
235
+ onClick={(e) => {
236
+ e.stopPropagation();
237
+ onViewModeChange("workers");
238
+ }}
239
+ className={cn(
240
+ "relative rounded px-2 py-1 transition-colors",
241
+ viewMode === "workers"
242
+ ? "bg-secondary text-foreground"
243
+ : "text-muted-foreground hover:text-foreground"
244
+ )}
245
+ >
246
+ <Users className="h-3.5 w-3.5" />
247
+ <span className="bg-primary text-primary-foreground absolute -top-1 -right-1 flex h-3.5 min-w-3.5 items-center justify-center rounded-full text-[9px] font-medium">
248
+ {workerCount}
249
+ </span>
250
+ </button>
251
+ </TooltipTrigger>
252
+ <TooltipContent>Workers</TooltipContent>
253
+ </Tooltip>
254
+ )}
255
+ </div>
256
+ )}
257
+
258
+ {/* Pane Controls */}
259
+ <div className="ml-auto flex items-center gap-0.5 px-2">
260
+ {session?.working_directory && session.working_directory !== "~" && (
261
+ <OpenInVSCode workingDirectory={session.working_directory} />
262
+ )}
263
+ {hasAttachedTmux && (
264
+ <Tooltip>
265
+ <TooltipTrigger asChild>
266
+ <Button
267
+ variant="ghost"
268
+ size="icon-sm"
269
+ onClick={(e) => {
270
+ e.stopPropagation();
271
+ onDetach();
272
+ }}
273
+ className="h-6 w-6"
274
+ >
275
+ <Unplug className="h-3 w-3" />
276
+ </Button>
277
+ </TooltipTrigger>
278
+ <TooltipContent>Detach from tmux</TooltipContent>
279
+ </Tooltip>
280
+ )}
281
+ {hasDetachedTmux && !hasAttachedTmux && (
282
+ <Tooltip>
283
+ <TooltipTrigger asChild>
284
+ <Button
285
+ variant="ghost"
286
+ size="icon-sm"
287
+ onClick={(e) => {
288
+ e.stopPropagation();
289
+ onReattach();
290
+ }}
291
+ className="h-6 w-6 text-yellow-500"
292
+ >
293
+ <Plug className="h-3 w-3" />
294
+ </Button>
295
+ </TooltipTrigger>
296
+ <TooltipContent>Re-attach to tmux</TooltipContent>
297
+ </Tooltip>
298
+ )}
299
+ <Tooltip>
300
+ <TooltipTrigger asChild>
301
+ <Button
302
+ variant="ghost"
303
+ size="icon-sm"
304
+ onClick={(e) => {
305
+ e.stopPropagation();
306
+ onSplitHorizontal();
307
+ }}
308
+ disabled={!canSplit}
309
+ className="h-6 w-6"
310
+ >
311
+ <SplitSquareHorizontal className="h-3 w-3" />
312
+ </Button>
313
+ </TooltipTrigger>
314
+ <TooltipContent>Split horizontal</TooltipContent>
315
+ </Tooltip>
316
+ <Tooltip>
317
+ <TooltipTrigger asChild>
318
+ <Button
319
+ variant="ghost"
320
+ size="icon-sm"
321
+ onClick={(e) => {
322
+ e.stopPropagation();
323
+ onSplitVertical();
324
+ }}
325
+ disabled={!canSplit}
326
+ className="h-6 w-6"
327
+ >
328
+ <SplitSquareVertical className="h-3 w-3" />
329
+ </Button>
330
+ </TooltipTrigger>
331
+ <TooltipContent>Split vertical</TooltipContent>
332
+ </Tooltip>
333
+ <Tooltip>
334
+ <TooltipTrigger asChild>
335
+ <Button
336
+ variant="ghost"
337
+ size="icon-sm"
338
+ onClick={(e) => {
339
+ e.stopPropagation();
340
+ onClose();
341
+ }}
342
+ disabled={!canClose}
343
+ className="h-6 w-6"
344
+ >
345
+ <X className="h-3 w-3" />
346
+ </Button>
347
+ </TooltipTrigger>
348
+ <TooltipContent>Close pane</TooltipContent>
349
+ </Tooltip>
350
+ </div>
351
+ </div>
352
+ );
353
+ }
@@ -0,0 +1,210 @@
1
+ "use client";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+ import {
5
+ DropdownMenu,
6
+ DropdownMenuContent,
7
+ DropdownMenuItem,
8
+ DropdownMenuTrigger,
9
+ } from "@/components/ui/dropdown-menu";
10
+ import {
11
+ Menu,
12
+ Terminal as TerminalIcon,
13
+ FolderOpen,
14
+ GitBranch,
15
+ Users,
16
+ ChevronDown,
17
+ Circle,
18
+ } from "lucide-react";
19
+ import { cn } from "@/lib/utils";
20
+ import type { Session } from "@/lib/db";
21
+ import { useClaudeSessionsQuery } from "@/data/claude";
22
+ import type { LucideIcon } from "lucide-react";
23
+
24
+ type ViewMode = "terminal" | "files" | "git" | "workers";
25
+
26
+ interface ViewModeButtonProps {
27
+ mode: ViewMode;
28
+ currentMode: ViewMode;
29
+ icon: LucideIcon;
30
+ onClick: (mode: ViewMode) => void;
31
+ badge?: React.ReactNode;
32
+ }
33
+
34
+ function ViewModeButton({
35
+ mode,
36
+ currentMode,
37
+ icon: Icon,
38
+ onClick,
39
+ badge,
40
+ }: ViewModeButtonProps) {
41
+ return (
42
+ <button
43
+ onClick={(e) => {
44
+ e.stopPropagation();
45
+ onClick(mode);
46
+ }}
47
+ className={cn(
48
+ "rounded p-1.5 transition-colors",
49
+ badge && "flex items-center gap-0.5",
50
+ currentMode === mode
51
+ ? "bg-secondary text-foreground"
52
+ : "text-muted-foreground"
53
+ )}
54
+ >
55
+ <Icon className="h-4 w-4" />
56
+ {badge}
57
+ </button>
58
+ );
59
+ }
60
+
61
+ interface MobileTabBarProps {
62
+ session: Session | null | undefined;
63
+ claudeProjectName: string | null;
64
+ viewMode: ViewMode;
65
+ isConductor: boolean;
66
+ workerCount: number;
67
+ onMenuClick?: () => void;
68
+ onViewModeChange: (mode: ViewMode) => void;
69
+ onResumeClaudeSession?: (
70
+ sessionId: string,
71
+ cwd: string,
72
+ summary: string,
73
+ projectName: string
74
+ ) => void;
75
+ }
76
+
77
+ export function MobileTabBar({
78
+ session,
79
+ claudeProjectName,
80
+ viewMode,
81
+ isConductor,
82
+ workerCount,
83
+ onMenuClick,
84
+ onViewModeChange,
85
+ onResumeClaudeSession,
86
+ }: MobileTabBarProps) {
87
+ const { data: claudeSessions } = useClaudeSessionsQuery(
88
+ claudeProjectName || ""
89
+ );
90
+ const sessionList = claudeProjectName ? claudeSessions?.sessions || [] : [];
91
+
92
+ return (
93
+ <div
94
+ className="bg-muted flex items-center gap-2 px-2 py-1.5 pt-[max(0.375rem,env(safe-area-inset-top))]"
95
+ onClick={(e) => e.stopPropagation()}
96
+ onTouchStart={(e) => e.stopPropagation()}
97
+ onTouchEnd={(e) => e.stopPropagation()}
98
+ >
99
+ {/* Menu button */}
100
+ {onMenuClick && (
101
+ <Button
102
+ variant="ghost"
103
+ size="icon-sm"
104
+ onClick={(e) => {
105
+ e.stopPropagation();
106
+ onMenuClick();
107
+ }}
108
+ className="h-8 w-8 shrink-0"
109
+ >
110
+ <Menu className="h-4 w-4" />
111
+ </Button>
112
+ )}
113
+
114
+ {/* Session selector */}
115
+ <div className="flex min-w-0 flex-1 items-center">
116
+ <DropdownMenu modal={false}>
117
+ <DropdownMenuTrigger asChild>
118
+ <button
119
+ type="button"
120
+ className="hover:bg-accent active:bg-accent flex min-w-0 flex-1 items-center justify-center gap-1 rounded-md px-2 py-1"
121
+ >
122
+ <span className="truncate text-sm font-medium">
123
+ {session?.name || "No session"}
124
+ </span>
125
+ {sessionList.length > 0 && (
126
+ <ChevronDown className="text-muted-foreground h-3 w-3 shrink-0" />
127
+ )}
128
+ </button>
129
+ </DropdownMenuTrigger>
130
+ {sessionList.length > 0 && (
131
+ <DropdownMenuContent
132
+ align="center"
133
+ className="max-h-[280px] w-[240px] overflow-y-auto p-1"
134
+ >
135
+ {sessionList.map((s) => {
136
+ const isActive = s.sessionId === session?.id;
137
+ return (
138
+ <DropdownMenuItem
139
+ key={s.sessionId}
140
+ onSelect={() => {
141
+ if (s.cwd && claudeProjectName) {
142
+ onResumeClaudeSession?.(
143
+ s.sessionId,
144
+ s.cwd,
145
+ s.summary,
146
+ claudeProjectName
147
+ );
148
+ }
149
+ }}
150
+ className={cn(
151
+ "gap-2 rounded px-2 py-1.5",
152
+ isActive && "bg-accent"
153
+ )}
154
+ >
155
+ <Circle
156
+ className={cn(
157
+ "h-1.5 w-1.5 shrink-0",
158
+ isActive
159
+ ? "fill-primary text-primary"
160
+ : "text-muted-foreground/50"
161
+ )}
162
+ />
163
+ <span className="truncate text-xs">{s.summary}</span>
164
+ </DropdownMenuItem>
165
+ );
166
+ })}
167
+ </DropdownMenuContent>
168
+ )}
169
+ </DropdownMenu>
170
+ </div>
171
+
172
+ {/* View mode toggle */}
173
+ {session?.working_directory && (
174
+ <div className="bg-accent/50 flex shrink-0 items-center rounded-md p-0.5">
175
+ <ViewModeButton
176
+ mode="terminal"
177
+ currentMode={viewMode}
178
+ icon={TerminalIcon}
179
+ onClick={onViewModeChange}
180
+ />
181
+ <ViewModeButton
182
+ mode="files"
183
+ currentMode={viewMode}
184
+ icon={FolderOpen}
185
+ onClick={onViewModeChange}
186
+ />
187
+ <ViewModeButton
188
+ mode="git"
189
+ currentMode={viewMode}
190
+ icon={GitBranch}
191
+ onClick={onViewModeChange}
192
+ />
193
+ {isConductor && (
194
+ <ViewModeButton
195
+ mode="workers"
196
+ currentMode={viewMode}
197
+ icon={Users}
198
+ onClick={onViewModeChange}
199
+ badge={
200
+ <span className="bg-primary/20 text-primary rounded px-1 text-[10px]">
201
+ {workerCount}
202
+ </span>
203
+ }
204
+ />
205
+ )}
206
+ </div>
207
+ )}
208
+ </div>
209
+ );
210
+ }
@@ -0,0 +1,69 @@
1
+ "use client";
2
+
3
+ import { useCallback } from "react";
4
+ import { useQuery } from "@tanstack/react-query";
5
+ import { Button } from "@/components/ui/button";
6
+ import {
7
+ Tooltip,
8
+ TooltipContent,
9
+ TooltipTrigger,
10
+ } from "@/components/ui/tooltip";
11
+ import { cn } from "@/lib/utils";
12
+
13
+ interface OpenInVSCodeProps {
14
+ workingDirectory: string;
15
+ className?: string;
16
+ }
17
+
18
+ function VSCodeIcon({ className }: { className?: string }) {
19
+ return (
20
+ <svg viewBox="0 0 24 24" fill="currentColor" className={className}>
21
+ <path d="M17.583 2.29a1.5 1.5 0 0 1 1.167.17l3.5 2.15A1.5 1.5 0 0 1 23 5.86v12.28a1.5 1.5 0 0 1-.75 1.3l-3.5 2.15a1.5 1.5 0 0 1-1.727-.13L9 14.54l-3.55 2.69a1 1 0 0 1-1.28-.05L2.42 15.53a1 1 0 0 1 0-1.46L5.7 12 2.42 9.93a1 1 0 0 1 0-1.46l1.75-1.65a1 1 0 0 1 1.28-.05L9 9.46l8.023-6.92a1.5 1.5 0 0 1 .56-.25ZM17.5 8.39l-5 4.11 5 4.11V8.39Z" />
22
+ </svg>
23
+ );
24
+ }
25
+
26
+ export function OpenInVSCode({
27
+ workingDirectory,
28
+ className,
29
+ }: OpenInVSCodeProps) {
30
+ const { data: systemInfo } = useQuery({
31
+ queryKey: ["system-info"],
32
+ queryFn: () => fetch("/api/system").then((r) => r.json()),
33
+ staleTime: Infinity,
34
+ });
35
+
36
+ const handleOpen = useCallback(() => {
37
+ const host = window.location.hostname;
38
+ const isLocal = host === "localhost" || host === "127.0.0.1";
39
+
40
+ if (isLocal) {
41
+ window.open(`vscode://file${workingDirectory}`, "_self");
42
+ } else {
43
+ const user = systemInfo?.user || "root";
44
+ window.open(
45
+ `vscode://vscode-remote/ssh-remote+${user}@${host}${workingDirectory}`,
46
+ "_self"
47
+ );
48
+ }
49
+ }, [workingDirectory, systemInfo]);
50
+
51
+ return (
52
+ <Tooltip>
53
+ <TooltipTrigger asChild>
54
+ <Button
55
+ variant="ghost"
56
+ size="icon-sm"
57
+ onClick={(e) => {
58
+ e.stopPropagation();
59
+ handleOpen();
60
+ }}
61
+ className={cn("h-6 w-6", className)}
62
+ >
63
+ <VSCodeIcon className="h-3 w-3" />
64
+ </Button>
65
+ </TooltipTrigger>
66
+ <TooltipContent>Open in VS Code</TooltipContent>
67
+ </Tooltip>
68
+ );
69
+ }
@@ -0,0 +1,57 @@
1
+ "use client";
2
+
3
+ import { ShimmeringLoader } from "@/components/ui/skeleton";
4
+ import { Terminal, FolderOpen, GitBranch } from "lucide-react";
5
+
6
+ export function TerminalSkeleton() {
7
+ return (
8
+ <div className="bg-background flex h-full w-full flex-col items-center justify-center gap-3">
9
+ <Terminal className="text-muted-foreground/50 h-8 w-8 animate-pulse" />
10
+ <div className="flex items-center gap-2">
11
+ <div className="bg-primary/50 h-2 w-2 animate-pulse rounded-full" />
12
+ <span className="text-muted-foreground text-sm">Connecting...</span>
13
+ </div>
14
+ </div>
15
+ );
16
+ }
17
+
18
+ export function FileExplorerSkeleton() {
19
+ return (
20
+ <div className="bg-background h-full w-full p-4">
21
+ <div className="mb-4 flex items-center gap-2">
22
+ <FolderOpen className="text-muted-foreground/50 h-4 w-4" />
23
+ <ShimmeringLoader className="h-4 w-32" />
24
+ </div>
25
+ <div className="space-y-2 pl-4">
26
+ {Array.from({ length: 8 }).map((_, i) => (
27
+ <div key={i} className="flex items-center gap-2">
28
+ <ShimmeringLoader className="h-4 w-4" delayIndex={i} />
29
+ <ShimmeringLoader className="h-4 w-24" delayIndex={i} />
30
+ </div>
31
+ ))}
32
+ </div>
33
+ </div>
34
+ );
35
+ }
36
+
37
+ export function GitPanelSkeleton() {
38
+ return (
39
+ <div className="bg-background h-full w-full p-4">
40
+ <div className="mb-4 flex items-center gap-2">
41
+ <GitBranch className="text-muted-foreground/50 h-4 w-4" />
42
+ <ShimmeringLoader className="h-4 w-24" />
43
+ </div>
44
+ <div className="space-y-3">
45
+ <ShimmeringLoader className="h-8 w-full rounded" />
46
+ <div className="space-y-2">
47
+ {Array.from({ length: 5 }).map((_, i) => (
48
+ <div key={i} className="flex items-center gap-2">
49
+ <ShimmeringLoader className="h-4 w-4" delayIndex={i} />
50
+ <ShimmeringLoader className="h-4 flex-1" delayIndex={i} />
51
+ </div>
52
+ ))}
53
+ </div>
54
+ </div>
55
+ </div>
56
+ );
57
+ }