@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,164 @@
1
+ "use client";
2
+
3
+ import { useEffect, useCallback, useState } from "react";
4
+ import { useSnapshot } from "valtio";
5
+ import { Button } from "@/components/ui/button";
6
+ import { Trash2, X } from "lucide-react";
7
+ import { selectionStore, selectionActions } from "@/stores/sessionSelection";
8
+ import {
9
+ Dialog,
10
+ DialogContent,
11
+ DialogDescription,
12
+ DialogFooter,
13
+ DialogHeader,
14
+ DialogTitle,
15
+ } from "@/components/ui/dialog";
16
+ import {
17
+ Tooltip,
18
+ TooltipContent,
19
+ TooltipTrigger,
20
+ } from "@/components/ui/tooltip";
21
+
22
+ interface SelectionToolbarProps {
23
+ allSessionIds: string[];
24
+ onDeleteSessions: (sessionIds: string[]) => Promise<void>;
25
+ }
26
+
27
+ export function SelectionToolbar({
28
+ allSessionIds,
29
+ onDeleteSessions,
30
+ }: SelectionToolbarProps) {
31
+ const { selectedIds } = useSnapshot(selectionStore);
32
+ const selectedCount = selectedIds.size;
33
+ const [showDeleteDialog, setShowDeleteDialog] = useState(false);
34
+ const [isDeleting, setIsDeleting] = useState(false);
35
+
36
+ const handleSelectAll = useCallback(() => {
37
+ selectionActions.selectAll(allSessionIds);
38
+ }, [allSessionIds]);
39
+
40
+ const handleDelete = useCallback(async () => {
41
+ const ids = selectionActions.getSelectedIds();
42
+ if (ids.length === 0) return;
43
+
44
+ setIsDeleting(true);
45
+ try {
46
+ await onDeleteSessions(ids);
47
+ selectionActions.clear();
48
+ } finally {
49
+ setIsDeleting(false);
50
+ setShowDeleteDialog(false);
51
+ }
52
+ }, [onDeleteSessions]);
53
+
54
+ // Keyboard shortcuts
55
+ useEffect(() => {
56
+ if (selectedCount === 0) return;
57
+
58
+ const handleKeyDown = (e: KeyboardEvent) => {
59
+ // Delete key - show delete confirmation
60
+ if (e.key === "Delete" || e.key === "Backspace") {
61
+ // Don't trigger if typing in an input
62
+ if (
63
+ e.target instanceof HTMLInputElement ||
64
+ e.target instanceof HTMLTextAreaElement
65
+ ) {
66
+ return;
67
+ }
68
+ e.preventDefault();
69
+ setShowDeleteDialog(true);
70
+ }
71
+
72
+ // Escape - clear selection
73
+ if (e.key === "Escape") {
74
+ e.preventDefault();
75
+ selectionActions.clear();
76
+ }
77
+ };
78
+
79
+ window.addEventListener("keydown", handleKeyDown);
80
+ return () => window.removeEventListener("keydown", handleKeyDown);
81
+ }, [selectedCount]);
82
+
83
+ if (selectedCount === 0) return null;
84
+
85
+ const allSelected = selectedCount === allSessionIds.length;
86
+
87
+ return (
88
+ <>
89
+ <div className="bg-primary/10 border-primary/20 flex items-center gap-2 border-b px-3 py-2">
90
+ <span className="text-sm font-medium whitespace-nowrap">
91
+ {selectedCount} selected
92
+ </span>
93
+ <div className="ml-auto flex items-center gap-1">
94
+ {!allSelected && (
95
+ <Button
96
+ variant="ghost"
97
+ size="sm"
98
+ className="h-7 text-xs"
99
+ onClick={handleSelectAll}
100
+ >
101
+ Select all
102
+ </Button>
103
+ )}
104
+ <Tooltip>
105
+ <TooltipTrigger asChild>
106
+ <Button
107
+ variant="ghost"
108
+ size="icon-sm"
109
+ className="text-destructive hover:text-destructive hover:bg-destructive/10 h-6 w-6"
110
+ onClick={() => setShowDeleteDialog(true)}
111
+ >
112
+ <Trash2 className="h-3 w-3" />
113
+ </Button>
114
+ </TooltipTrigger>
115
+ <TooltipContent>Delete selected</TooltipContent>
116
+ </Tooltip>
117
+ <Tooltip>
118
+ <TooltipTrigger asChild>
119
+ <Button
120
+ variant="ghost"
121
+ size="icon-sm"
122
+ className="h-6 w-6"
123
+ onClick={selectionActions.clear}
124
+ >
125
+ <X className="h-3 w-3" />
126
+ </Button>
127
+ </TooltipTrigger>
128
+ <TooltipContent>Clear selection</TooltipContent>
129
+ </Tooltip>
130
+ </div>
131
+ </div>
132
+
133
+ <Dialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
134
+ <DialogContent showCloseButton={false}>
135
+ <DialogHeader>
136
+ <DialogTitle>
137
+ Delete {selectedCount} session{selectedCount > 1 ? "s" : ""}?
138
+ </DialogTitle>
139
+ <DialogDescription>
140
+ This will permanently delete the selected sessions and their tmux
141
+ sessions. This action cannot be undone.
142
+ </DialogDescription>
143
+ </DialogHeader>
144
+ <DialogFooter>
145
+ <Button
146
+ variant="ghost"
147
+ onClick={() => setShowDeleteDialog(false)}
148
+ disabled={isDeleting}
149
+ >
150
+ Cancel
151
+ </Button>
152
+ <Button
153
+ variant="destructive"
154
+ onClick={handleDelete}
155
+ disabled={isDeleting}
156
+ >
157
+ {isDeleting ? "Deleting..." : "Delete"}
158
+ </Button>
159
+ </DialogFooter>
160
+ </DialogContent>
161
+ </Dialog>
162
+ </>
163
+ );
164
+ }
@@ -0,0 +1,37 @@
1
+ import type { Session, Group } from "@/lib/db";
2
+
3
+ export interface SessionStatus {
4
+ sessionName: string;
5
+ status: "idle" | "running" | "waiting" | "error" | "dead";
6
+ lastLine?: string;
7
+ }
8
+
9
+ export interface SessionListProps {
10
+ activeSessionId?: string;
11
+ sessionStatuses?: Record<string, SessionStatus>;
12
+ onSelect: (sessionId: string) => void;
13
+ onOpenInTab?: (sessionId: string) => void;
14
+ onNewSessionInProject?: (projectId: string) => void;
15
+ onOpenTerminal?: (projectId: string) => void;
16
+ onStartDevServer?: (projectId: string) => void;
17
+ onResumeClaudeSession?: (
18
+ claudeSessionId: string,
19
+ cwd: string,
20
+ summary?: string,
21
+ projectName?: string
22
+ ) => void;
23
+ onNewSession?: (cwd?: string, projectName?: string) => void;
24
+ onCreateDevServer?: (opts: {
25
+ projectId: string;
26
+ type: "node" | "docker";
27
+ name: string;
28
+ command: string;
29
+ workingDirectory: string;
30
+ ports?: number[];
31
+ }) => Promise<void>;
32
+ }
33
+
34
+ export interface SessionHoverHandlers {
35
+ onHoverStart: (session: Session, rect: DOMRect) => void;
36
+ onHoverEnd: () => void;
37
+ }
@@ -0,0 +1,71 @@
1
+ import { ADropdownMenu, menuItem } from "@/components/a/ADropdownMenu";
2
+ import {
3
+ Plus,
4
+ FolderPlus,
5
+ FolderOpen,
6
+ GitBranch,
7
+ MoreHorizontal,
8
+ Trash2,
9
+ } from "lucide-react";
10
+
11
+ interface SessionListHeaderProps {
12
+ onNewProject: () => void;
13
+ onOpenProject: () => void;
14
+ onCloneFromGithub: () => void;
15
+ onKillAll: () => void;
16
+ }
17
+
18
+ export function SessionListHeader({
19
+ onNewProject,
20
+ onOpenProject,
21
+ onCloneFromGithub,
22
+ onKillAll,
23
+ }: SessionListHeaderProps) {
24
+ return (
25
+ <div className="flex items-center justify-between px-3 py-2">
26
+ <div className="flex items-center gap-2">
27
+ <svg
28
+ xmlns="http://www.w3.org/2000/svg"
29
+ viewBox="0 0 24 24"
30
+ className="h-5 w-5"
31
+ stroke="currentColor"
32
+ strokeWidth="2"
33
+ strokeLinecap="round"
34
+ strokeLinejoin="round"
35
+ fill="none"
36
+ >
37
+ <path d="M12 8V4H8" />
38
+ <rect width="16" height="12" x="4" y="8" rx="2" />
39
+ <path d="M2 14h2" />
40
+ <path d="M20 14h2" />
41
+ <path d="M15 13v2" />
42
+ <path d="M9 13v2" />
43
+ </svg>
44
+ <h2 className="font-semibold">ClaudeDeck</h2>
45
+ </div>
46
+ <div className="flex gap-1">
47
+ <ADropdownMenu
48
+ icon={Plus}
49
+ tooltip="New project"
50
+ items={[
51
+ menuItem("New Project", onNewProject, { icon: FolderPlus }),
52
+ menuItem("Open Project", onOpenProject, { icon: FolderOpen }),
53
+ menuItem("Clone from GitHub", onCloneFromGithub, {
54
+ icon: GitBranch,
55
+ }),
56
+ ]}
57
+ />
58
+ <ADropdownMenu
59
+ icon={MoreHorizontal}
60
+ tooltip="More options"
61
+ items={[
62
+ menuItem("Kill all sessions", onKillAll, {
63
+ icon: Trash2,
64
+ variant: "destructive",
65
+ }),
66
+ ]}
67
+ />
68
+ </div>
69
+ </div>
70
+ );
71
+ }
@@ -0,0 +1,269 @@
1
+ import { useCallback } from "react";
2
+ import { useQueryClient } from "@tanstack/react-query";
3
+ import { toast } from "sonner";
4
+ import {
5
+ useDeleteSession,
6
+ useRenameSession,
7
+ useForkSession,
8
+ useSummarizeSession,
9
+ useMoveSessionToProject,
10
+ } from "@/data/sessions";
11
+ import {
12
+ useToggleProject,
13
+ useDeleteProject,
14
+ useRenameProject,
15
+ } from "@/data/projects";
16
+ import { useToggleGroup, useCreateGroup, useDeleteGroup } from "@/data/groups";
17
+ import {
18
+ useStopDevServer,
19
+ useRestartDevServer,
20
+ useRemoveDevServer,
21
+ } from "@/data/dev-servers";
22
+ import { sessionKeys } from "@/data/sessions/keys";
23
+
24
+ interface UseSessionListMutationsOptions {
25
+ onSelectSession: (sessionId: string) => void;
26
+ }
27
+
28
+ export function useSessionListMutations({
29
+ onSelectSession,
30
+ }: UseSessionListMutationsOptions) {
31
+ const queryClient = useQueryClient();
32
+
33
+ // Session mutations
34
+ const deleteSessionMutation = useDeleteSession();
35
+ const renameSessionMutation = useRenameSession();
36
+ const forkSessionMutation = useForkSession();
37
+ const summarizeSessionMutation = useSummarizeSession();
38
+ const moveSessionToProjectMutation = useMoveSessionToProject();
39
+
40
+ // Project mutations
41
+ const toggleProjectMutation = useToggleProject();
42
+ const deleteProjectMutation = useDeleteProject();
43
+ const renameProjectMutation = useRenameProject();
44
+
45
+ // Group mutations
46
+ const toggleGroupMutation = useToggleGroup();
47
+ const createGroupMutation = useCreateGroup();
48
+ const deleteGroupMutation = useDeleteGroup();
49
+
50
+ // Dev server mutations
51
+ const stopDevServerMutation = useStopDevServer();
52
+ const restartDevServerMutation = useRestartDevServer();
53
+ const removeDevServerMutation = useRemoveDevServer();
54
+
55
+ // Derived state
56
+ const summarizingSessionId = summarizeSessionMutation.isPending
57
+ ? (summarizeSessionMutation.variables as string)
58
+ : null;
59
+
60
+ // Session handlers
61
+ const handleDeleteSession = useCallback(
62
+ async (sessionId: string) => {
63
+ if (!confirm("Delete this session? This cannot be undone.")) return;
64
+ await deleteSessionMutation.mutateAsync(sessionId);
65
+ },
66
+ [deleteSessionMutation]
67
+ );
68
+
69
+ const handleRenameSession = useCallback(
70
+ async (sessionId: string, newName: string) => {
71
+ await renameSessionMutation.mutateAsync({ sessionId, newName });
72
+ },
73
+ [renameSessionMutation]
74
+ );
75
+
76
+ const handleForkSession = useCallback(
77
+ async (sessionId: string) => {
78
+ const forkedSession = await forkSessionMutation.mutateAsync(sessionId);
79
+ if (forkedSession) onSelectSession(forkedSession.id);
80
+ },
81
+ [forkSessionMutation, onSelectSession]
82
+ );
83
+
84
+ const handleSummarize = useCallback(
85
+ async (sessionId: string) => {
86
+ const newSession = await summarizeSessionMutation.mutateAsync(sessionId);
87
+ if (newSession) onSelectSession(newSession.id);
88
+ },
89
+ [summarizeSessionMutation, onSelectSession]
90
+ );
91
+
92
+ const handleMoveSessionToProject = useCallback(
93
+ async (sessionId: string, projectId: string) => {
94
+ await moveSessionToProjectMutation.mutateAsync({ sessionId, projectId });
95
+ },
96
+ [moveSessionToProjectMutation]
97
+ );
98
+
99
+ // Project handlers
100
+ const handleToggleProject = useCallback(
101
+ async (projectId: string, expanded: boolean) => {
102
+ await toggleProjectMutation.mutateAsync({ projectId, expanded });
103
+ },
104
+ [toggleProjectMutation]
105
+ );
106
+
107
+ const handleDeleteProject = useCallback(
108
+ async (projectId: string) => {
109
+ if (
110
+ !confirm(
111
+ "Delete this project? Sessions will be moved to Uncategorized."
112
+ )
113
+ )
114
+ return;
115
+ await deleteProjectMutation.mutateAsync(projectId);
116
+ },
117
+ [deleteProjectMutation]
118
+ );
119
+
120
+ const handleRenameProject = useCallback(
121
+ async (projectId: string, newName: string) => {
122
+ await renameProjectMutation.mutateAsync({ projectId, newName });
123
+ },
124
+ [renameProjectMutation]
125
+ );
126
+
127
+ // Group handlers
128
+ const handleToggleGroup = useCallback(
129
+ async (path: string, expanded: boolean) => {
130
+ await toggleGroupMutation.mutateAsync({ path, expanded });
131
+ },
132
+ [toggleGroupMutation]
133
+ );
134
+
135
+ const handleCreateGroup = useCallback(
136
+ async (name: string, parentPath?: string) => {
137
+ await createGroupMutation.mutateAsync({ name, parentPath });
138
+ },
139
+ [createGroupMutation]
140
+ );
141
+
142
+ const handleDeleteGroup = useCallback(
143
+ async (path: string) => {
144
+ if (!confirm("Delete this group? Sessions will be moved to parent."))
145
+ return;
146
+ await deleteGroupMutation.mutateAsync(path);
147
+ },
148
+ [deleteGroupMutation]
149
+ );
150
+
151
+ // Dev server handlers
152
+ const handleStopDevServer = useCallback(
153
+ async (serverId: string) => {
154
+ await stopDevServerMutation.mutateAsync(serverId);
155
+ },
156
+ [stopDevServerMutation]
157
+ );
158
+
159
+ const handleRestartDevServer = useCallback(
160
+ async (serverId: string) => {
161
+ await restartDevServerMutation.mutateAsync(serverId);
162
+ },
163
+ [restartDevServerMutation]
164
+ );
165
+
166
+ const handleRemoveDevServer = useCallback(
167
+ async (serverId: string) => {
168
+ await removeDevServerMutation.mutateAsync(serverId);
169
+ },
170
+ [removeDevServerMutation]
171
+ );
172
+
173
+ // Bulk delete handler
174
+ const handleBulkDelete = useCallback(
175
+ async (sessionIds: string[]) => {
176
+ const count = sessionIds.length;
177
+ const hasWorktrees = sessionIds.length > 0; // Assume some might have worktrees
178
+
179
+ // Show toast with progress
180
+ const toastId = toast.loading(
181
+ hasWorktrees
182
+ ? `Deleting ${count} session${count > 1 ? "s" : ""}... cleaning up worktrees in background`
183
+ : `Deleting ${count} session${count > 1 ? "s" : ""}...`
184
+ );
185
+
186
+ let succeeded = 0;
187
+ let failed = 0;
188
+
189
+ // Delete all sessions in parallel for speed
190
+ await Promise.allSettled(
191
+ sessionIds.map(async (sessionId) => {
192
+ try {
193
+ const response = await fetch(`/api/sessions/${sessionId}`, {
194
+ method: "DELETE",
195
+ });
196
+ if (response.ok) {
197
+ succeeded++;
198
+ } else {
199
+ failed++;
200
+ }
201
+ } catch (error) {
202
+ console.error(`Failed to delete session ${sessionId}:`, error);
203
+ failed++;
204
+ }
205
+ })
206
+ );
207
+
208
+ // Invalidate cache to refresh UI
209
+ queryClient.invalidateQueries({ queryKey: sessionKeys.list() });
210
+
211
+ // Update toast based on results
212
+ if (failed === 0) {
213
+ toast.success(
214
+ `Deleted ${succeeded} session${succeeded > 1 ? "s" : ""}`,
215
+ { id: toastId }
216
+ );
217
+ } else if (succeeded === 0) {
218
+ toast.error(
219
+ `Failed to delete ${failed} session${failed > 1 ? "s" : ""}`,
220
+ {
221
+ id: toastId,
222
+ }
223
+ );
224
+ } else {
225
+ toast.warning(
226
+ `Deleted ${succeeded}, failed ${failed} session${failed > 1 ? "s" : ""}`,
227
+ { id: toastId }
228
+ );
229
+ }
230
+ },
231
+ [queryClient]
232
+ );
233
+
234
+ // Refresh handler
235
+ const handleRefresh = useCallback(async () => {
236
+ await queryClient.invalidateQueries({ queryKey: sessionKeys.list() });
237
+ }, [queryClient]);
238
+
239
+ return {
240
+ // Derived state
241
+ summarizingSessionId,
242
+
243
+ // Session handlers
244
+ handleDeleteSession,
245
+ handleRenameSession,
246
+ handleForkSession,
247
+ handleSummarize,
248
+ handleMoveSessionToProject,
249
+
250
+ // Project handlers
251
+ handleToggleProject,
252
+ handleDeleteProject,
253
+ handleRenameProject,
254
+
255
+ // Group handlers
256
+ handleToggleGroup,
257
+ handleCreateGroup,
258
+ handleDeleteGroup,
259
+
260
+ // Dev server handlers
261
+ handleStopDevServer,
262
+ handleRestartDevServer,
263
+ handleRemoveDevServer,
264
+
265
+ // Bulk operations
266
+ handleBulkDelete,
267
+ handleRefresh,
268
+ };
269
+ }