@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,174 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Loader2, History, ArrowLeft, FileCode } from "lucide-react";
5
+ import { Button } from "@/components/ui/button";
6
+ import { CommitItem } from "./CommitItem";
7
+ import { DiffView } from "@/components/DiffViewer/DiffModal";
8
+ import { useCommitHistory, useCommitFileDiff } from "@/data/git/queries";
9
+ import { useViewport } from "@/hooks/useViewport";
10
+ import type { CommitFile } from "@/lib/git-history";
11
+
12
+ interface CommitHistoryProps {
13
+ workingDirectory: string;
14
+ }
15
+
16
+ interface SelectedFileDiff {
17
+ hash: string;
18
+ file: CommitFile;
19
+ }
20
+
21
+ export function CommitHistory({ workingDirectory }: CommitHistoryProps) {
22
+ const { isMobile } = useViewport();
23
+ const {
24
+ data: commits,
25
+ isLoading,
26
+ error,
27
+ } = useCommitHistory(workingDirectory);
28
+ const [selectedFile, setSelectedFile] = useState<SelectedFileDiff | null>(
29
+ null
30
+ );
31
+
32
+ // Fetch diff when file is selected
33
+ const { data: diff, isLoading: loadingDiff } = useCommitFileDiff(
34
+ workingDirectory,
35
+ selectedFile?.hash ?? null,
36
+ selectedFile?.file.path ?? null
37
+ );
38
+
39
+ const handleFileClick = (hash: string, file: CommitFile) => {
40
+ setSelectedFile({ hash, file });
41
+ };
42
+
43
+ if (isLoading) {
44
+ return (
45
+ <div className="flex flex-1 items-center justify-center">
46
+ <Loader2 className="text-muted-foreground h-6 w-6 animate-spin" />
47
+ </div>
48
+ );
49
+ }
50
+
51
+ if (error) {
52
+ return (
53
+ <div className="text-muted-foreground flex flex-1 flex-col items-center justify-center p-4">
54
+ <History className="mb-2 h-8 w-8 opacity-50" />
55
+ <p className="text-center text-sm">Failed to load commit history</p>
56
+ </div>
57
+ );
58
+ }
59
+
60
+ if (!commits?.length) {
61
+ return (
62
+ <div className="text-muted-foreground flex flex-1 flex-col items-center justify-center p-4">
63
+ <History className="mb-2 h-8 w-8 opacity-50" />
64
+ <p className="text-sm">No commits yet</p>
65
+ </div>
66
+ );
67
+ }
68
+
69
+ // Mobile: full-screen diff view when file selected
70
+ if (isMobile && selectedFile) {
71
+ return (
72
+ <div className="flex h-full flex-col">
73
+ <div className="bg-muted/30 flex items-center gap-2 p-2">
74
+ <Button
75
+ variant="ghost"
76
+ size="icon-sm"
77
+ onClick={() => setSelectedFile(null)}
78
+ >
79
+ <ArrowLeft className="h-5 w-5" />
80
+ </Button>
81
+ <div className="min-w-0 flex-1">
82
+ <p className="truncate text-sm font-medium">
83
+ {selectedFile.file.path}
84
+ </p>
85
+ <p className="text-muted-foreground text-xs">
86
+ {selectedFile.hash.slice(0, 7)}
87
+ </p>
88
+ </div>
89
+ </div>
90
+ <div className="flex-1 overflow-auto p-3">
91
+ {loadingDiff ? (
92
+ <div className="flex h-32 items-center justify-center">
93
+ <Loader2 className="text-muted-foreground h-6 w-6 animate-spin" />
94
+ </div>
95
+ ) : (
96
+ <DiffView diff={diff || ""} fileName={selectedFile.file.path} />
97
+ )}
98
+ </div>
99
+ </div>
100
+ );
101
+ }
102
+
103
+ // Mobile: commit list only
104
+ if (isMobile) {
105
+ return (
106
+ <div className="flex-1 overflow-y-auto">
107
+ {commits.map((commit) => (
108
+ <CommitItem
109
+ key={commit.hash}
110
+ commit={commit}
111
+ workingDir={workingDirectory}
112
+ onFileClick={handleFileClick}
113
+ selectedFile={
114
+ selectedFile
115
+ ? { hash: selectedFile.hash, path: selectedFile.file.path }
116
+ : null
117
+ }
118
+ />
119
+ ))}
120
+ </div>
121
+ );
122
+ }
123
+
124
+ // Desktop: side-by-side layout
125
+ return (
126
+ <div className="flex min-h-0 flex-1">
127
+ {/* Commit list */}
128
+ <div className="border-border/50 w-[300px] flex-shrink-0 overflow-y-auto border-r">
129
+ {commits.map((commit) => (
130
+ <CommitItem
131
+ key={commit.hash}
132
+ commit={commit}
133
+ workingDir={workingDirectory}
134
+ onFileClick={handleFileClick}
135
+ selectedFile={
136
+ selectedFile
137
+ ? { hash: selectedFile.hash, path: selectedFile.file.path }
138
+ : null
139
+ }
140
+ />
141
+ ))}
142
+ </div>
143
+
144
+ {/* Diff view */}
145
+ <div className="bg-muted/20 flex min-w-0 flex-1 flex-col">
146
+ {loadingDiff ? (
147
+ <div className="flex flex-1 items-center justify-center">
148
+ <Loader2 className="text-muted-foreground h-6 w-6 animate-spin" />
149
+ </div>
150
+ ) : selectedFile && diff !== undefined ? (
151
+ <>
152
+ <div className="bg-background/50 flex items-center gap-2 p-3">
153
+ <FileCode className="text-muted-foreground h-4 w-4" />
154
+ <span className="flex-1 truncate text-sm font-medium">
155
+ {selectedFile.file.path}
156
+ </span>
157
+ <span className="text-muted-foreground font-mono text-xs">
158
+ {selectedFile.hash.slice(0, 7)}
159
+ </span>
160
+ </div>
161
+ <div className="flex-1 overflow-auto p-3">
162
+ <DiffView diff={diff} fileName={selectedFile.file.path} />
163
+ </div>
164
+ </>
165
+ ) : (
166
+ <div className="text-muted-foreground flex flex-1 flex-col items-center justify-center">
167
+ <FileCode className="mb-4 h-12 w-12 opacity-50" />
168
+ <p className="text-sm">Select a file to view diff</p>
169
+ </div>
170
+ )}
171
+ </div>
172
+ </div>
173
+ );
174
+ }
@@ -0,0 +1,196 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import {
5
+ ChevronRight,
6
+ Plus,
7
+ Minus,
8
+ FileText,
9
+ FilePlus,
10
+ FileX,
11
+ ArrowRight,
12
+ Loader2,
13
+ } from "lucide-react";
14
+ import { cn } from "@/lib/utils";
15
+ import { useCommitDetail } from "@/data/git/queries";
16
+ import type { CommitSummary, CommitFile } from "@/lib/git-history";
17
+
18
+ interface CommitItemProps {
19
+ commit: CommitSummary;
20
+ workingDir: string;
21
+ onFileClick: (hash: string, file: CommitFile) => void;
22
+ selectedFile?: { hash: string; path: string } | null;
23
+ }
24
+
25
+ export function CommitItem({
26
+ commit,
27
+ workingDir,
28
+ onFileClick,
29
+ selectedFile,
30
+ }: CommitItemProps) {
31
+ const [expanded, setExpanded] = useState(false);
32
+
33
+ // Only fetch detail when expanded
34
+ const { data: detail, isLoading } = useCommitDetail(
35
+ workingDir,
36
+ expanded ? commit.hash : null
37
+ );
38
+
39
+ const authorInitial = commit.author.charAt(0).toUpperCase();
40
+
41
+ return (
42
+ <div className="border-border/30 border-b last:border-b-0">
43
+ {/* Commit summary row */}
44
+ <button
45
+ onClick={() => setExpanded(!expanded)}
46
+ className={cn(
47
+ "hover:bg-muted/50 flex w-full items-center gap-3 p-3 text-left transition-colors",
48
+ expanded && "bg-muted/30"
49
+ )}
50
+ >
51
+ <ChevronRight
52
+ className={cn(
53
+ "text-muted-foreground h-4 w-4 flex-shrink-0 transition-transform",
54
+ expanded && "rotate-90"
55
+ )}
56
+ />
57
+
58
+ {/* Author avatar */}
59
+ <div className="bg-primary/20 flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full">
60
+ <span className="text-primary text-xs font-medium">
61
+ {authorInitial}
62
+ </span>
63
+ </div>
64
+
65
+ {/* Commit info */}
66
+ <div className="min-w-0 flex-1">
67
+ <div className="flex items-center gap-2">
68
+ <span className="text-muted-foreground font-mono text-xs">
69
+ {commit.shortHash}
70
+ </span>
71
+ <span className="truncate text-sm">{commit.subject}</span>
72
+ </div>
73
+ <div className="text-muted-foreground mt-0.5 flex items-center gap-2 text-xs">
74
+ <span>{commit.author}</span>
75
+ <span>·</span>
76
+ <span>{commit.relativeTime}</span>
77
+ </div>
78
+ </div>
79
+
80
+ {/* Stats badge */}
81
+ <div className="flex flex-shrink-0 items-center gap-1 text-xs">
82
+ {commit.additions > 0 && (
83
+ <span className="flex items-center text-green-500">
84
+ <Plus className="h-3 w-3" />
85
+ {commit.additions}
86
+ </span>
87
+ )}
88
+ {commit.deletions > 0 && (
89
+ <span className="flex items-center text-red-500">
90
+ <Minus className="h-3 w-3" />
91
+ {commit.deletions}
92
+ </span>
93
+ )}
94
+ </div>
95
+ </button>
96
+
97
+ {/* Expanded file list */}
98
+ {expanded && (
99
+ <div className="pr-3 pb-3 pl-14">
100
+ {isLoading ? (
101
+ <div className="flex items-center justify-center py-4">
102
+ <Loader2 className="text-muted-foreground h-4 w-4 animate-spin" />
103
+ </div>
104
+ ) : detail?.files?.length ? (
105
+ <div className="space-y-1">
106
+ {detail.files.map((file) => (
107
+ <FileRow
108
+ key={file.path}
109
+ file={file}
110
+ isSelected={
111
+ selectedFile?.hash === commit.hash &&
112
+ selectedFile?.path === file.path
113
+ }
114
+ onClick={() => onFileClick(commit.hash, file)}
115
+ />
116
+ ))}
117
+ </div>
118
+ ) : (
119
+ <p className="text-muted-foreground py-2 text-sm">
120
+ No files changed
121
+ </p>
122
+ )}
123
+ </div>
124
+ )}
125
+ </div>
126
+ );
127
+ }
128
+
129
+ interface FileRowProps {
130
+ file: CommitFile;
131
+ isSelected: boolean;
132
+ onClick: () => void;
133
+ }
134
+
135
+ function FileRow({ file, isSelected, onClick }: FileRowProps) {
136
+ const StatusIcon = getStatusIcon(file.status);
137
+
138
+ return (
139
+ <button
140
+ onClick={onClick}
141
+ className={cn(
142
+ "hover:bg-muted/70 flex w-full items-center gap-2 rounded px-2 py-1.5 text-left transition-colors",
143
+ isSelected && "bg-primary/10 hover:bg-primary/20"
144
+ )}
145
+ >
146
+ <StatusIcon
147
+ className={cn("h-4 w-4 flex-shrink-0", getStatusColor(file.status))}
148
+ />
149
+ <span className="flex-1 truncate text-sm">
150
+ {file.oldPath ? (
151
+ <span className="flex items-center gap-1">
152
+ <span className="text-muted-foreground">{file.oldPath}</span>
153
+ <ArrowRight className="h-3 w-3" />
154
+ <span>{file.path}</span>
155
+ </span>
156
+ ) : (
157
+ file.path
158
+ )}
159
+ </span>
160
+ <div className="flex flex-shrink-0 items-center gap-1 text-xs">
161
+ {file.additions > 0 && (
162
+ <span className="text-green-500">+{file.additions}</span>
163
+ )}
164
+ {file.deletions > 0 && (
165
+ <span className="text-red-500">-{file.deletions}</span>
166
+ )}
167
+ </div>
168
+ </button>
169
+ );
170
+ }
171
+
172
+ function getStatusIcon(status: CommitFile["status"]) {
173
+ switch (status) {
174
+ case "added":
175
+ return FilePlus;
176
+ case "deleted":
177
+ return FileX;
178
+ case "renamed":
179
+ return ArrowRight;
180
+ default:
181
+ return FileText;
182
+ }
183
+ }
184
+
185
+ function getStatusColor(status: CommitFile["status"]) {
186
+ switch (status) {
187
+ case "added":
188
+ return "text-green-500";
189
+ case "deleted":
190
+ return "text-red-500";
191
+ case "renamed":
192
+ return "text-yellow-500";
193
+ default:
194
+ return "text-muted-foreground";
195
+ }
196
+ }