@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,118 @@
1
+ "use client";
2
+
3
+ import { useRef, useEffect } from "react";
4
+ import { X, File } from "lucide-react";
5
+ import { cn } from "@/lib/utils";
6
+ import type { OpenFile } from "@/hooks/useFileEditor";
7
+
8
+ interface FileTabsProps {
9
+ files: OpenFile[];
10
+ activeFilePath: string | null;
11
+ onSelect: (path: string) => void;
12
+ onClose: (path: string) => void;
13
+ isDirty: (path: string) => boolean;
14
+ }
15
+
16
+ export function FileTabs({
17
+ files,
18
+ activeFilePath,
19
+ onSelect,
20
+ onClose,
21
+ isDirty,
22
+ }: FileTabsProps) {
23
+ const scrollRef = useRef<HTMLDivElement>(null);
24
+ const activeTabRef = useRef<HTMLDivElement>(null);
25
+
26
+ // Scroll active tab into view
27
+ useEffect(() => {
28
+ if (activeTabRef.current && scrollRef.current) {
29
+ activeTabRef.current.scrollIntoView({
30
+ behavior: "smooth",
31
+ block: "nearest",
32
+ inline: "center",
33
+ });
34
+ }
35
+ }, [activeFilePath]);
36
+
37
+ if (files.length === 0) {
38
+ return null;
39
+ }
40
+
41
+ return (
42
+ <div
43
+ ref={scrollRef}
44
+ className="bg-muted/30 scrollbar-none flex items-center gap-0.5 overflow-x-auto px-1"
45
+ >
46
+ {files.map((file) => {
47
+ const isActive = file.path === activeFilePath;
48
+ const dirty = isDirty(file.path);
49
+ const fileName = file.path.split("/").pop() || file.path;
50
+ const ext = fileName.split(".").pop()?.toLowerCase() || "";
51
+
52
+ return (
53
+ <div
54
+ key={file.path}
55
+ ref={isActive ? activeTabRef : null}
56
+ role="button"
57
+ tabIndex={0}
58
+ onClick={() => onSelect(file.path)}
59
+ onKeyDown={(e) => {
60
+ if (e.key === "Enter" || e.key === " ") {
61
+ e.preventDefault();
62
+ onSelect(file.path);
63
+ }
64
+ }}
65
+ className={cn(
66
+ "flex cursor-pointer items-center gap-1.5 px-3 py-2 text-sm whitespace-nowrap transition-colors",
67
+ "min-h-[40px] md:min-h-[36px]",
68
+ "hover:bg-accent/50",
69
+ isActive
70
+ ? "bg-background text-foreground"
71
+ : "text-muted-foreground"
72
+ )}
73
+ >
74
+ <FileIcon extension={ext} />
75
+ <span className="max-w-[120px] truncate">{fileName}</span>
76
+ {dirty && (
77
+ <span className="bg-primary h-2 w-2 flex-shrink-0 rounded-full" />
78
+ )}
79
+ <button
80
+ onClick={(e) => {
81
+ e.stopPropagation();
82
+ onClose(file.path);
83
+ }}
84
+ className={cn(
85
+ "hover:bg-accent ml-1 flex-shrink-0 rounded p-0.5",
86
+ "opacity-0 group-hover:opacity-100",
87
+ isActive && "opacity-100"
88
+ )}
89
+ >
90
+ <X className="h-3 w-3" />
91
+ </button>
92
+ </div>
93
+ );
94
+ })}
95
+ </div>
96
+ );
97
+ }
98
+
99
+ function FileIcon({ extension }: { extension: string }) {
100
+ const colorMap: Record<string, string> = {
101
+ js: "text-yellow-400",
102
+ jsx: "text-yellow-400",
103
+ ts: "text-blue-400",
104
+ tsx: "text-blue-400",
105
+ css: "text-pink-400",
106
+ scss: "text-pink-400",
107
+ html: "text-orange-400",
108
+ xml: "text-orange-400",
109
+ json: "text-green-400",
110
+ yaml: "text-purple-400",
111
+ yml: "text-purple-400",
112
+ md: "text-blue-300",
113
+ py: "text-green-500",
114
+ };
115
+
116
+ const color = colorMap[extension] || "text-muted-foreground";
117
+ return <File className={cn("h-3.5 w-3.5 flex-shrink-0", color)} />;
118
+ }
@@ -0,0 +1,214 @@
1
+ "use client";
2
+
3
+ import { useState, useCallback } from "react";
4
+ import {
5
+ ChevronRight,
6
+ ChevronDown,
7
+ File,
8
+ Folder,
9
+ FolderOpen,
10
+ Loader2,
11
+ } from "lucide-react";
12
+ import { cn } from "@/lib/utils";
13
+ import type { FileNode } from "@/lib/file-utils";
14
+
15
+ interface FileTreeProps {
16
+ nodes: FileNode[];
17
+ basePath: string;
18
+ onFileClick: (path: string) => void;
19
+ depth?: number;
20
+ }
21
+
22
+ /**
23
+ * Recursive file tree component
24
+ * Mobile-optimized with larger touch targets
25
+ * Lazily loads directory contents when expanded
26
+ */
27
+ export function FileTree({
28
+ nodes,
29
+ basePath,
30
+ onFileClick,
31
+ depth = 0,
32
+ }: FileTreeProps) {
33
+ const [expanded, setExpanded] = useState<Set<string>>(new Set());
34
+ const [loadedChildren, setLoadedChildren] = useState<Map<string, FileNode[]>>(
35
+ new Map()
36
+ );
37
+ const [loadingDirs, setLoadingDirs] = useState<Set<string>>(new Set());
38
+
39
+ const fetchChildren = useCallback(
40
+ async (dirPath: string) => {
41
+ if (loadedChildren.has(dirPath)) return;
42
+
43
+ setLoadingDirs((prev) => new Set(prev).add(dirPath));
44
+ try {
45
+ const res = await fetch(
46
+ `/api/files?path=${encodeURIComponent(dirPath)}`
47
+ );
48
+ const data = await res.json();
49
+ if (data.files) {
50
+ setLoadedChildren((prev) => new Map(prev).set(dirPath, data.files));
51
+ }
52
+ } catch (err) {
53
+ console.error("Failed to load directory:", err);
54
+ } finally {
55
+ setLoadingDirs((prev) => {
56
+ const next = new Set(prev);
57
+ next.delete(dirPath);
58
+ return next;
59
+ });
60
+ }
61
+ },
62
+ [loadedChildren]
63
+ );
64
+
65
+ const toggleExpand = useCallback(
66
+ async (path: string) => {
67
+ const isCurrentlyExpanded = expanded.has(path);
68
+
69
+ setExpanded((prev) => {
70
+ const next = new Set(prev);
71
+ if (next.has(path)) {
72
+ next.delete(path);
73
+ } else {
74
+ next.add(path);
75
+ }
76
+ return next;
77
+ });
78
+
79
+ // Fetch children if expanding and not already loaded
80
+ if (!isCurrentlyExpanded && !loadedChildren.has(path)) {
81
+ await fetchChildren(path);
82
+ }
83
+ },
84
+ [expanded, loadedChildren, fetchChildren]
85
+ );
86
+
87
+ return (
88
+ <div className="w-full">
89
+ {nodes.map((node) => {
90
+ const isExpanded = expanded.has(node.path);
91
+ const isDirectory = node.type === "directory";
92
+ const isLoading = loadingDirs.has(node.path);
93
+ const children = loadedChildren.get(node.path) || node.children;
94
+
95
+ return (
96
+ <div key={node.path}>
97
+ {/* File/Directory item */}
98
+ <button
99
+ onClick={() => {
100
+ if (isDirectory) {
101
+ toggleExpand(node.path);
102
+ } else {
103
+ onFileClick(node.path);
104
+ }
105
+ }}
106
+ className={cn(
107
+ "hover:bg-accent flex w-full items-center gap-2 px-2 py-2 text-left transition-colors",
108
+ "min-h-[40px] md:min-h-[32px]", // Touch target
109
+ "text-sm"
110
+ )}
111
+ style={{ paddingLeft: `${depth * 12 + 8}px` }}
112
+ >
113
+ {/* Expand/collapse icon */}
114
+ {isDirectory && (
115
+ <span className="flex h-4 w-4 flex-shrink-0 items-center justify-center">
116
+ {isLoading ? (
117
+ <Loader2 className="text-muted-foreground h-3 w-3 animate-spin" />
118
+ ) : isExpanded ? (
119
+ <ChevronDown className="text-muted-foreground h-4 w-4" />
120
+ ) : (
121
+ <ChevronRight className="text-muted-foreground h-4 w-4" />
122
+ )}
123
+ </span>
124
+ )}
125
+
126
+ {/* Icon */}
127
+ <span className="flex-shrink-0">
128
+ {isDirectory ? (
129
+ isExpanded ? (
130
+ <FolderOpen className="h-4 w-4 text-blue-400" />
131
+ ) : (
132
+ <Folder className="h-4 w-4 text-blue-400" />
133
+ )
134
+ ) : (
135
+ <FileIcon extension={node.extension || ""} />
136
+ )}
137
+ </span>
138
+
139
+ {/* Name */}
140
+ <span
141
+ className={cn(
142
+ "flex-1 truncate",
143
+ isDirectory ? "font-medium" : "text-muted-foreground"
144
+ )}
145
+ >
146
+ {node.name}
147
+ </span>
148
+
149
+ {/* Size (files only, on desktop) */}
150
+ {!isDirectory && node.size !== undefined && (
151
+ <span className="text-muted-foreground hidden flex-shrink-0 text-xs md:block">
152
+ {formatFileSize(node.size)}
153
+ </span>
154
+ )}
155
+ </button>
156
+
157
+ {/* Children (if expanded) */}
158
+ {isDirectory && isExpanded && children && children.length > 0 && (
159
+ <FileTree
160
+ nodes={children}
161
+ basePath={basePath}
162
+ onFileClick={onFileClick}
163
+ depth={depth + 1}
164
+ />
165
+ )}
166
+ </div>
167
+ );
168
+ })}
169
+ </div>
170
+ );
171
+ }
172
+
173
+ /**
174
+ * File icon based on extension
175
+ */
176
+ function FileIcon({ extension }: { extension: string }) {
177
+ const ext = extension.toLowerCase();
178
+
179
+ // Color coding by file type
180
+ const colorMap: Record<string, string> = {
181
+ // JavaScript/TypeScript
182
+ js: "text-yellow-400",
183
+ jsx: "text-yellow-400",
184
+ ts: "text-blue-400",
185
+ tsx: "text-blue-400",
186
+ // Styles
187
+ css: "text-pink-400",
188
+ scss: "text-pink-400",
189
+ // Markup
190
+ html: "text-orange-400",
191
+ xml: "text-orange-400",
192
+ // Data
193
+ json: "text-green-400",
194
+ yaml: "text-purple-400",
195
+ yml: "text-purple-400",
196
+ // Config
197
+ md: "text-blue-300",
198
+ toml: "text-gray-400",
199
+ env: "text-yellow-300",
200
+ };
201
+
202
+ const color = colorMap[ext] || "text-muted-foreground";
203
+
204
+ return <File className={cn("h-4 w-4", color)} />;
205
+ }
206
+
207
+ /**
208
+ * Format file size for display
209
+ */
210
+ function formatFileSize(bytes: number): string {
211
+ if (bytes < 1024) return `${bytes}B`;
212
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
213
+ return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
214
+ }
@@ -0,0 +1,16 @@
1
+ "use client";
2
+
3
+ interface HtmlRendererProps {
4
+ content: string;
5
+ }
6
+
7
+ export function HtmlRenderer({ content }: HtmlRendererProps) {
8
+ return (
9
+ <iframe
10
+ srcDoc={content}
11
+ sandbox=""
12
+ className="h-full w-full border-0 bg-white"
13
+ title="HTML Preview"
14
+ />
15
+ );
16
+ }
@@ -0,0 +1,18 @@
1
+ "use client";
2
+
3
+ import ReactMarkdown from "react-markdown";
4
+ import remarkGfm from "remark-gfm";
5
+
6
+ interface MarkdownRendererProps {
7
+ content: string;
8
+ }
9
+
10
+ export function MarkdownRenderer({ content }: MarkdownRendererProps) {
11
+ return (
12
+ <div className="h-full overflow-y-auto px-6 py-4">
13
+ <article className="prose prose-sm dark:prose-invert max-w-none">
14
+ <ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
15
+ </article>
16
+ </div>
17
+ );
18
+ }