@dyyz1993/create-agent 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/package.json +25 -0
  2. package/src/__tests__/cli.test.ts +85 -0
  3. package/src/__tests__/copy.test.ts +168 -0
  4. package/src/__tests__/templates.test.ts +87 -0
  5. package/src/cli.ts +65 -0
  6. package/src/commands/create.ts +49 -0
  7. package/src/commands/list.ts +12 -0
  8. package/src/commands/status.ts +85 -0
  9. package/src/commands/workspace.ts +129 -0
  10. package/src/lib/copy.ts +285 -0
  11. package/src/lib/templates.ts +57 -0
  12. package/src/lib/types.ts +6 -0
  13. package/templates/agent/.env.example +7 -0
  14. package/templates/agent/LICENSE +21 -0
  15. package/templates/agent/README.md +87 -0
  16. package/templates/agent/bun.lock +1279 -0
  17. package/templates/agent/electrobun.config.ts +27 -0
  18. package/templates/agent/eslint.config.mjs +63 -0
  19. package/templates/agent/llms.txt +24 -0
  20. package/templates/agent/package-lock.json +3670 -0
  21. package/templates/agent/package.json +59 -0
  22. package/templates/agent/postcss.config.js +6 -0
  23. package/templates/agent/scripts/dev.ts +138 -0
  24. package/templates/agent/src/__tests__/server-config.test.ts +59 -0
  25. package/templates/agent/src/bun/index.ts +73 -0
  26. package/templates/agent/src/gateway/__tests__/ws-handler.test.ts +48 -0
  27. package/templates/agent/src/gateway/http-routes.ts +1 -0
  28. package/templates/agent/src/gateway/ipc-transport.ts +68 -0
  29. package/templates/agent/src/gateway/ws-handler.ts +86 -0
  30. package/templates/agent/src/mainview/App.tsx +41 -0
  31. package/templates/agent/src/mainview/__tests__/components/ChatPanel.test.tsx +114 -0
  32. package/templates/agent/src/mainview/__tests__/components/ExplorerSidebar.test.tsx +141 -0
  33. package/templates/agent/src/mainview/__tests__/components/MessageBubble.test.tsx +72 -0
  34. package/templates/agent/src/mainview/__tests__/hooks/use-input-history.test.ts +125 -0
  35. package/templates/agent/src/mainview/__tests__/hooks/use-rpc-init.test.ts +217 -0
  36. package/templates/agent/src/mainview/__tests__/hooks/use-sidebar-resize.test.ts +127 -0
  37. package/templates/agent/src/mainview/__tests__/i18n/i18n.test.ts +48 -0
  38. package/templates/agent/src/mainview/__tests__/setup.ts +37 -0
  39. package/templates/agent/src/mainview/__tests__/stores/use-chat-store.test.ts +83 -0
  40. package/templates/agent/src/mainview/__tests__/stores/use-sidebar-store.test.ts +85 -0
  41. package/templates/agent/src/mainview/components/activity-bar/ActivityBar.tsx +36 -0
  42. package/templates/agent/src/mainview/components/activity-bar/MobileTabBar.tsx +36 -0
  43. package/templates/agent/src/mainview/components/activity-bar/__tests__/ActivityBar.test.tsx +44 -0
  44. package/templates/agent/src/mainview/components/activity-bar/__tests__/MobileTabBar.test.tsx +44 -0
  45. package/templates/agent/src/mainview/components/bash/BashPanel.tsx +206 -0
  46. package/templates/agent/src/mainview/components/bash/__tests__/BashPanel.test.tsx +103 -0
  47. package/templates/agent/src/mainview/components/chat/ChatPanel.tsx +102 -0
  48. package/templates/agent/src/mainview/components/chat/MessageBubble.tsx +86 -0
  49. package/templates/agent/src/mainview/components/common/ErrorBoundary.tsx +58 -0
  50. package/templates/agent/src/mainview/components/common/LanguageSwitcher.tsx +14 -0
  51. package/templates/agent/src/mainview/components/common/ThemeToggle.tsx +24 -0
  52. package/templates/agent/src/mainview/components/common/__tests__/ErrorBoundary.test.tsx +74 -0
  53. package/templates/agent/src/mainview/components/common/__tests__/LanguageSwitcher.test.tsx +44 -0
  54. package/templates/agent/src/mainview/components/common/__tests__/ThemeToggle.test.tsx +41 -0
  55. package/templates/agent/src/mainview/components/debug/DebugPanel.tsx +120 -0
  56. package/templates/agent/src/mainview/components/debug/__tests__/DebugPanel.test.tsx +102 -0
  57. package/templates/agent/src/mainview/components/diff/DiffViewerPanel.tsx +106 -0
  58. package/templates/agent/src/mainview/components/diff/__tests__/DiffViewerPanel.test.tsx +73 -0
  59. package/templates/agent/src/mainview/components/explorer/ConfirmDialog.tsx +53 -0
  60. package/templates/agent/src/mainview/components/explorer/ContextMenu.tsx +82 -0
  61. package/templates/agent/src/mainview/components/explorer/ExplorerSidebar.tsx +221 -0
  62. package/templates/agent/src/mainview/components/explorer/InlineInput.tsx +55 -0
  63. package/templates/agent/src/mainview/components/explorer/TreeNodeItem.tsx +121 -0
  64. package/templates/agent/src/mainview/components/explorer/__tests__/ConfirmDialog.test.tsx +37 -0
  65. package/templates/agent/src/mainview/components/explorer/__tests__/ContextMenu.test.tsx +49 -0
  66. package/templates/agent/src/mainview/components/explorer/__tests__/InlineInput.test.tsx +48 -0
  67. package/templates/agent/src/mainview/components/explorer/__tests__/TreeNodeItem.test.tsx +146 -0
  68. package/templates/agent/src/mainview/components/feed/FeedPanel.tsx +239 -0
  69. package/templates/agent/src/mainview/components/feed/__tests__/FeedPanel.test.tsx +108 -0
  70. package/templates/agent/src/mainview/components/file-preview/FilePreviewOverlay.tsx +56 -0
  71. package/templates/agent/src/mainview/components/file-preview/VirtualizedCodeView.tsx +99 -0
  72. package/templates/agent/src/mainview/components/file-preview/__tests__/FilePreviewOverlay.test.tsx +96 -0
  73. package/templates/agent/src/mainview/components/file-preview/__tests__/VirtualizedCodeView.test.tsx +58 -0
  74. package/templates/agent/src/mainview/components/git/GitBranchSelector.tsx +98 -0
  75. package/templates/agent/src/mainview/components/git/GitCommitInput.tsx +60 -0
  76. package/templates/agent/src/mainview/components/git/GitPanel.tsx +528 -0
  77. package/templates/agent/src/mainview/components/git/__tests__/GitBranchSelector.test.tsx +76 -0
  78. package/templates/agent/src/mainview/components/git/__tests__/GitCommitInput.test.tsx +91 -0
  79. package/templates/agent/src/mainview/components/git/__tests__/GitPanel.test.tsx +174 -0
  80. package/templates/agent/src/mainview/components/layout/AppLayout.tsx +170 -0
  81. package/templates/agent/src/mainview/components/layout/__tests__/AppLayout.test.tsx +163 -0
  82. package/templates/agent/src/mainview/components/rules/RulesPanel.tsx +117 -0
  83. package/templates/agent/src/mainview/components/rules/__tests__/RulesPanel.test.tsx +91 -0
  84. package/templates/agent/src/mainview/components/search/SearchPanel.tsx +404 -0
  85. package/templates/agent/src/mainview/components/search/__tests__/SearchPanel.test.tsx +50 -0
  86. package/templates/agent/src/mainview/components/sidebar/PinButton.tsx +20 -0
  87. package/templates/agent/src/mainview/components/sidebar/__tests__/PinButton.test.tsx +48 -0
  88. package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +141 -0
  89. package/templates/agent/src/mainview/components/todo/__tests__/TodoPanel.test.tsx +91 -0
  90. package/templates/agent/src/mainview/global.d.ts +13 -0
  91. package/templates/agent/src/mainview/hooks/__tests__/use-breakpoint.test.ts +147 -0
  92. package/templates/agent/src/mainview/hooks/use-breakpoint.ts +34 -0
  93. package/templates/agent/src/mainview/hooks/use-input-history.ts +84 -0
  94. package/templates/agent/src/mainview/hooks/use-rpc-init.ts +61 -0
  95. package/templates/agent/src/mainview/hooks/use-sidebar-resize.ts +40 -0
  96. package/templates/agent/src/mainview/index.css +61 -0
  97. package/templates/agent/src/mainview/index.html +12 -0
  98. package/templates/agent/src/mainview/lib/api-client.ts +192 -0
  99. package/templates/agent/src/mainview/lib/i18n/index.ts +30 -0
  100. package/templates/agent/src/mainview/lib/i18n/locales/en.json +157 -0
  101. package/templates/agent/src/mainview/lib/i18n/locales/zh.json +157 -0
  102. package/templates/agent/src/mainview/lib/rpc-cache.ts +94 -0
  103. package/templates/agent/src/mainview/main.tsx +24 -0
  104. package/templates/agent/src/mainview/stores/__tests__/use-app-store.test.ts +86 -0
  105. package/templates/agent/src/mainview/stores/__tests__/use-bash-store.test.ts +99 -0
  106. package/templates/agent/src/mainview/stores/__tests__/use-connection-store.test.ts +26 -0
  107. package/templates/agent/src/mainview/stores/__tests__/use-explorer-store.test.ts +130 -0
  108. package/templates/agent/src/mainview/stores/__tests__/use-feed-store.test.ts +132 -0
  109. package/templates/agent/src/mainview/stores/__tests__/use-git-store.test.ts +161 -0
  110. package/templates/agent/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
  111. package/templates/agent/src/mainview/stores/__tests__/use-log-store.test.ts +28 -0
  112. package/templates/agent/src/mainview/stores/__tests__/use-notification-store.test.ts +102 -0
  113. package/templates/agent/src/mainview/stores/__tests__/use-rules-store.test.ts +75 -0
  114. package/templates/agent/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
  115. package/templates/agent/src/mainview/stores/__tests__/use-todo-store.test.ts +75 -0
  116. package/templates/agent/src/mainview/stores/message-batcher.ts +25 -0
  117. package/templates/agent/src/mainview/stores/use-app-store.ts +99 -0
  118. package/templates/agent/src/mainview/stores/use-bash-store.ts +93 -0
  119. package/templates/agent/src/mainview/stores/use-chat-store.ts +37 -0
  120. package/templates/agent/src/mainview/stores/use-connection-store.ts +42 -0
  121. package/templates/agent/src/mainview/stores/use-explorer-store.ts +320 -0
  122. package/templates/agent/src/mainview/stores/use-feed-store.ts +129 -0
  123. package/templates/agent/src/mainview/stores/use-git-store.ts +291 -0
  124. package/templates/agent/src/mainview/stores/use-locale-store.ts +27 -0
  125. package/templates/agent/src/mainview/stores/use-log-store.ts +16 -0
  126. package/templates/agent/src/mainview/stores/use-notification-store.ts +82 -0
  127. package/templates/agent/src/mainview/stores/use-rules-store.ts +54 -0
  128. package/templates/agent/src/mainview/stores/use-sidebar-store.ts +99 -0
  129. package/templates/agent/src/mainview/stores/use-theme-store.ts +46 -0
  130. package/templates/agent/src/mainview/stores/use-todo-store.ts +54 -0
  131. package/templates/agent/src/mainview/types/index.ts +33 -0
  132. package/templates/agent/src/mainview/utils/constants.ts +1 -0
  133. package/templates/agent/src/mainview/utils/drop-handler.ts +150 -0
  134. package/templates/agent/src/mainview/utils/file-icon.tsx +24 -0
  135. package/templates/agent/src/mainview/utils/file-utils.ts +35 -0
  136. package/templates/agent/src/server-config.ts +43 -0
  137. package/templates/agent/src/server.ts +89 -0
  138. package/templates/agent/src/shared/handlers/__tests__/bash-handler.test.ts +135 -0
  139. package/templates/agent/src/shared/handlers/__tests__/chat-handler.test.ts +77 -0
  140. package/templates/agent/src/shared/handlers/__tests__/feed-handler.test.ts +108 -0
  141. package/templates/agent/src/shared/handlers/__tests__/file-handler.test.ts +100 -0
  142. package/templates/agent/src/shared/handlers/__tests__/system-handler.test.ts +55 -0
  143. package/templates/agent/src/shared/handlers/__tests__/timer-handler.test.ts +77 -0
  144. package/templates/agent/src/shared/handlers/bash.ts +89 -0
  145. package/templates/agent/src/shared/handlers/chat.ts +179 -0
  146. package/templates/agent/src/shared/handlers/feed.ts +53 -0
  147. package/templates/agent/src/shared/handlers/file.ts +99 -0
  148. package/templates/agent/src/shared/handlers/git.ts +263 -0
  149. package/templates/agent/src/shared/handlers/index.ts +10 -0
  150. package/templates/agent/src/shared/handlers/rules.ts +45 -0
  151. package/templates/agent/src/shared/handlers/system.ts +27 -0
  152. package/templates/agent/src/shared/handlers/timer.ts +34 -0
  153. package/templates/agent/src/shared/handlers/todo.ts +45 -0
  154. package/templates/agent/src/shared/lib/__tests__/bash-security.test.ts +125 -0
  155. package/templates/agent/src/shared/lib/__tests__/logger.test.ts +92 -0
  156. package/templates/agent/src/shared/lib/__tests__/path-security.test.ts +77 -0
  157. package/templates/agent/src/shared/lib/bash-security.ts +64 -0
  158. package/templates/agent/src/shared/lib/logger.ts +130 -0
  159. package/templates/agent/src/shared/lib/path-security.ts +38 -0
  160. package/templates/agent/src/shared/lib/port-registry.ts +103 -0
  161. package/templates/agent/src/shared/modules/bash.ts +19 -0
  162. package/templates/agent/src/shared/modules/chat.ts +20 -0
  163. package/templates/agent/src/shared/modules/feed.ts +30 -0
  164. package/templates/agent/src/shared/modules/file.ts +40 -0
  165. package/templates/agent/src/shared/modules/git.ts +81 -0
  166. package/templates/agent/src/shared/modules/rules.ts +25 -0
  167. package/templates/agent/src/shared/modules/system.ts +8 -0
  168. package/templates/agent/src/shared/modules/timer.ts +11 -0
  169. package/templates/agent/src/shared/modules/todo.ts +27 -0
  170. package/templates/agent/src/shared/register-all-handlers.ts +36 -0
  171. package/templates/agent/src/shared/rpc-schema.ts +35 -0
  172. package/templates/agent/tailwind.config.js +15 -0
  173. package/templates/agent/tsconfig.json +26 -0
  174. package/templates/agent/vite.config.ts +3 -0
  175. package/templates/agent/vitest.config.ts +3 -0
  176. package/templates/chat/.env.example +6 -0
  177. package/templates/chat/LICENSE +21 -0
  178. package/templates/chat/README.md +56 -0
  179. package/templates/chat/bun.lock +1203 -0
  180. package/templates/chat/electrobun.config.ts +27 -0
  181. package/templates/chat/eslint.config.mjs +56 -0
  182. package/templates/chat/llms.txt +24 -0
  183. package/templates/chat/package-lock.json +3670 -0
  184. package/templates/chat/package.json +58 -0
  185. package/templates/chat/postcss.config.js +6 -0
  186. package/templates/chat/scripts/dev.ts +138 -0
  187. package/templates/chat/src/__tests__/server-config.test.ts +59 -0
  188. package/templates/chat/src/bun/index.ts +72 -0
  189. package/templates/chat/src/gateway/__tests__/ws-handler.test.ts +48 -0
  190. package/templates/chat/src/gateway/http-routes.ts +1 -0
  191. package/templates/chat/src/gateway/ipc-transport.ts +68 -0
  192. package/templates/chat/src/gateway/ws-handler.ts +86 -0
  193. package/templates/chat/src/mainview/App.tsx +108 -0
  194. package/templates/chat/src/mainview/__tests__/components/ChatPanel.test.tsx +76 -0
  195. package/templates/chat/src/mainview/__tests__/components/MessageBubble.test.tsx +48 -0
  196. package/templates/chat/src/mainview/__tests__/i18n/i18n.test.ts +48 -0
  197. package/templates/chat/src/mainview/__tests__/setup-verify.test.ts +8 -0
  198. package/templates/chat/src/mainview/__tests__/setup.ts +37 -0
  199. package/templates/chat/src/mainview/__tests__/stores/use-app-store.test.ts +46 -0
  200. package/templates/chat/src/mainview/__tests__/stores/use-chat-store.test.ts +104 -0
  201. package/templates/chat/src/mainview/__tests__/stores/use-connection-store.test.ts +35 -0
  202. package/templates/chat/src/mainview/__tests__/stores/use-log-store.test.ts +45 -0
  203. package/templates/chat/src/mainview/__tests__/stores/use-notification-store.test.ts +102 -0
  204. package/templates/chat/src/mainview/__tests__/stores/use-sidebar-store.test.ts +89 -0
  205. package/templates/chat/src/mainview/components/activity-bar/ActivityBar.tsx +26 -0
  206. package/templates/chat/src/mainview/components/activity-bar/MobileTabBar.tsx +20 -0
  207. package/templates/chat/src/mainview/components/chat/ChatPanel.tsx +66 -0
  208. package/templates/chat/src/mainview/components/chat/MessageBubble.tsx +85 -0
  209. package/templates/chat/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
  210. package/templates/chat/src/mainview/components/common/ThemeToggle.tsx +24 -0
  211. package/templates/chat/src/mainview/components/sidebar/PinButton.tsx +20 -0
  212. package/templates/chat/src/mainview/global.d.ts +13 -0
  213. package/templates/chat/src/mainview/hooks/use-breakpoint.ts +34 -0
  214. package/templates/chat/src/mainview/hooks/use-input-history.ts +84 -0
  215. package/templates/chat/src/mainview/index.css +61 -0
  216. package/templates/chat/src/mainview/index.html +12 -0
  217. package/templates/chat/src/mainview/lib/api-client.ts +175 -0
  218. package/templates/chat/src/mainview/lib/i18n/index.ts +30 -0
  219. package/templates/chat/src/mainview/lib/i18n/locales/en.json +157 -0
  220. package/templates/chat/src/mainview/lib/i18n/locales/zh.json +157 -0
  221. package/templates/chat/src/mainview/main.tsx +20 -0
  222. package/templates/chat/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
  223. package/templates/chat/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
  224. package/templates/chat/src/mainview/stores/message-batcher.ts +25 -0
  225. package/templates/chat/src/mainview/stores/use-app-store.ts +50 -0
  226. package/templates/chat/src/mainview/stores/use-chat-store.ts +37 -0
  227. package/templates/chat/src/mainview/stores/use-connection-store.ts +42 -0
  228. package/templates/chat/src/mainview/stores/use-locale-store.ts +27 -0
  229. package/templates/chat/src/mainview/stores/use-log-store.ts +16 -0
  230. package/templates/chat/src/mainview/stores/use-notification-store.ts +82 -0
  231. package/templates/chat/src/mainview/stores/use-sidebar-store.ts +98 -0
  232. package/templates/chat/src/mainview/stores/use-theme-store.ts +46 -0
  233. package/templates/chat/src/mainview/types/index.ts +8 -0
  234. package/templates/chat/src/mainview/utils/constants.ts +1 -0
  235. package/templates/chat/src/server-config.ts +42 -0
  236. package/templates/chat/src/server.ts +89 -0
  237. package/templates/chat/src/shared/__tests__/chat-handler.test.ts +71 -0
  238. package/templates/chat/src/shared/__tests__/system-handler.test.ts +70 -0
  239. package/templates/chat/src/shared/handlers/chat.ts +186 -0
  240. package/templates/chat/src/shared/handlers/index.ts +2 -0
  241. package/templates/chat/src/shared/handlers/system.ts +27 -0
  242. package/templates/chat/src/shared/lib/logger.ts +130 -0
  243. package/templates/chat/src/shared/lib/path-security.ts +38 -0
  244. package/templates/chat/src/shared/lib/port-registry.ts +103 -0
  245. package/templates/chat/src/shared/modules/chat.ts +20 -0
  246. package/templates/chat/src/shared/modules/system.ts +8 -0
  247. package/templates/chat/src/shared/register-all-handlers.ts +36 -0
  248. package/templates/chat/src/shared/rpc-schema.ts +11 -0
  249. package/templates/chat/tailwind.config.js +15 -0
  250. package/templates/chat/tsconfig.json +26 -0
  251. package/templates/chat/vite.config.ts +3 -0
  252. package/templates/chat/vitest.config.ts +3 -0
  253. package/templates/general/.env.example +7 -0
  254. package/templates/general/LICENSE +21 -0
  255. package/templates/general/README.md +113 -0
  256. package/templates/general/bun.lock +1266 -0
  257. package/templates/general/electrobun.config.ts +27 -0
  258. package/templates/general/eslint.config.mjs +56 -0
  259. package/templates/general/llms.txt +24 -0
  260. package/templates/general/package-lock.json +3670 -0
  261. package/templates/general/package.json +59 -0
  262. package/templates/general/postcss.config.js +6 -0
  263. package/templates/general/scripts/dev.ts +138 -0
  264. package/templates/general/src/__tests__/server-config.test.ts +59 -0
  265. package/templates/general/src/bun/index.ts +72 -0
  266. package/templates/general/src/gateway/http-routes.ts +1 -0
  267. package/templates/general/src/gateway/ipc-transport.ts +68 -0
  268. package/templates/general/src/gateway/ws-handler.ts +86 -0
  269. package/templates/general/src/mainview/App.tsx +40 -0
  270. package/templates/general/src/mainview/__tests__/components/ChatPanel.test.tsx +82 -0
  271. package/templates/general/src/mainview/__tests__/components/FeedPanel.test.tsx +109 -0
  272. package/templates/general/src/mainview/__tests__/components/GitPanel.test.tsx +151 -0
  273. package/templates/general/src/mainview/__tests__/i18n/i18n.test.ts +48 -0
  274. package/templates/general/src/mainview/__tests__/setup-verify.test.ts +8 -0
  275. package/templates/general/src/mainview/__tests__/setup.ts +37 -0
  276. package/templates/general/src/mainview/components/activity-bar/ActivityBar.tsx +40 -0
  277. package/templates/general/src/mainview/components/activity-bar/MobileTabBar.tsx +29 -0
  278. package/templates/general/src/mainview/components/chat/ChatPanel.tsx +66 -0
  279. package/templates/general/src/mainview/components/chat/MessageBubble.tsx +85 -0
  280. package/templates/general/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
  281. package/templates/general/src/mainview/components/common/ThemeToggle.tsx +24 -0
  282. package/templates/general/src/mainview/components/debug/DebugPanel.tsx +176 -0
  283. package/templates/general/src/mainview/components/diff/DiffViewerPanel.tsx +108 -0
  284. package/templates/general/src/mainview/components/explorer/ConfirmDialog.tsx +52 -0
  285. package/templates/general/src/mainview/components/explorer/ContextMenu.tsx +82 -0
  286. package/templates/general/src/mainview/components/explorer/ExplorerSidebar.tsx +225 -0
  287. package/templates/general/src/mainview/components/explorer/InlineInput.tsx +55 -0
  288. package/templates/general/src/mainview/components/explorer/TreeNodeItem.tsx +121 -0
  289. package/templates/general/src/mainview/components/feed/FeedPanel.tsx +248 -0
  290. package/templates/general/src/mainview/components/file-preview/FilePreviewOverlay.tsx +55 -0
  291. package/templates/general/src/mainview/components/file-preview/VirtualizedCodeView.tsx +99 -0
  292. package/templates/general/src/mainview/components/git/GitBranchSelector.tsx +93 -0
  293. package/templates/general/src/mainview/components/git/GitCommitInput.tsx +60 -0
  294. package/templates/general/src/mainview/components/git/GitPanel.tsx +533 -0
  295. package/templates/general/src/mainview/components/layout/AppLayout.tsx +146 -0
  296. package/templates/general/src/mainview/components/search/SearchPanel.tsx +405 -0
  297. package/templates/general/src/mainview/components/sidebar/PinButton.tsx +20 -0
  298. package/templates/general/src/mainview/global.d.ts +13 -0
  299. package/templates/general/src/mainview/hooks/use-breakpoint.ts +34 -0
  300. package/templates/general/src/mainview/hooks/use-input-history.ts +84 -0
  301. package/templates/general/src/mainview/hooks/use-rpc-init.ts +61 -0
  302. package/templates/general/src/mainview/hooks/use-sidebar-resize.ts +40 -0
  303. package/templates/general/src/mainview/index.css +61 -0
  304. package/templates/general/src/mainview/index.html +12 -0
  305. package/templates/general/src/mainview/lib/api-client.ts +175 -0
  306. package/templates/general/src/mainview/lib/i18n/index.ts +30 -0
  307. package/templates/general/src/mainview/lib/i18n/locales/en.json +157 -0
  308. package/templates/general/src/mainview/lib/i18n/locales/zh.json +157 -0
  309. package/templates/general/src/mainview/main.tsx +20 -0
  310. package/templates/general/src/mainview/stores/__tests__/use-chat-store.test.ts +104 -0
  311. package/templates/general/src/mainview/stores/__tests__/use-connection-store.test.ts +35 -0
  312. package/templates/general/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
  313. package/templates/general/src/mainview/stores/__tests__/use-log-store.test.ts +45 -0
  314. package/templates/general/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
  315. package/templates/general/src/mainview/stores/message-batcher.ts +25 -0
  316. package/templates/general/src/mainview/stores/use-app-store.ts +104 -0
  317. package/templates/general/src/mainview/stores/use-chat-store.ts +37 -0
  318. package/templates/general/src/mainview/stores/use-connection-store.ts +42 -0
  319. package/templates/general/src/mainview/stores/use-explorer-store.ts +315 -0
  320. package/templates/general/src/mainview/stores/use-feed-store.ts +129 -0
  321. package/templates/general/src/mainview/stores/use-git-store.ts +284 -0
  322. package/templates/general/src/mainview/stores/use-locale-store.ts +27 -0
  323. package/templates/general/src/mainview/stores/use-log-store.ts +16 -0
  324. package/templates/general/src/mainview/stores/use-notification-store.ts +82 -0
  325. package/templates/general/src/mainview/stores/use-sidebar-store.ts +99 -0
  326. package/templates/general/src/mainview/stores/use-theme-store.ts +46 -0
  327. package/templates/general/src/mainview/types/index.ts +33 -0
  328. package/templates/general/src/mainview/utils/constants.ts +1 -0
  329. package/templates/general/src/mainview/utils/drop-handler.ts +150 -0
  330. package/templates/general/src/mainview/utils/file-icon.tsx +24 -0
  331. package/templates/general/src/mainview/utils/file-utils.ts +35 -0
  332. package/templates/general/src/server-config.ts +42 -0
  333. package/templates/general/src/server.ts +89 -0
  334. package/templates/general/src/shared/handlers/__tests__/chat.test.ts +183 -0
  335. package/templates/general/src/shared/handlers/__tests__/system.test.ts +94 -0
  336. package/templates/general/src/shared/handlers/__tests__/timer.test.ts +113 -0
  337. package/templates/general/src/shared/handlers/chat.ts +179 -0
  338. package/templates/general/src/shared/handlers/feed.ts +53 -0
  339. package/templates/general/src/shared/handlers/file.ts +99 -0
  340. package/templates/general/src/shared/handlers/git.ts +263 -0
  341. package/templates/general/src/shared/handlers/index.ts +7 -0
  342. package/templates/general/src/shared/handlers/system.ts +27 -0
  343. package/templates/general/src/shared/handlers/timer.ts +34 -0
  344. package/templates/general/src/shared/lib/logger.ts +130 -0
  345. package/templates/general/src/shared/lib/path-security.ts +38 -0
  346. package/templates/general/src/shared/lib/port-registry.ts +103 -0
  347. package/templates/general/src/shared/modules/chat.ts +20 -0
  348. package/templates/general/src/shared/modules/feed.ts +30 -0
  349. package/templates/general/src/shared/modules/file.ts +40 -0
  350. package/templates/general/src/shared/modules/git.ts +81 -0
  351. package/templates/general/src/shared/modules/system.ts +8 -0
  352. package/templates/general/src/shared/modules/timer.ts +11 -0
  353. package/templates/general/src/shared/register-all-handlers.ts +36 -0
  354. package/templates/general/src/shared/rpc-schema.ts +31 -0
  355. package/templates/general/tailwind.config.js +15 -0
  356. package/templates/general/tsconfig.json +26 -0
  357. package/templates/general/vite.config.ts +3 -0
  358. package/templates/general/vitest.config.ts +3 -0
@@ -0,0 +1,44 @@
1
+ import { describe, it, expect, vi, afterEach } from "vitest";
2
+ import { render, screen, cleanup } from "@testing-library/react";
3
+ import React from "react";
4
+
5
+ const mockSetLocale = vi.fn();
6
+
7
+ vi.mock("../../stores/use-locale-store", () => ({
8
+ useLocaleStore: (selector: (s: any) => any) =>
9
+ selector({ locale: "en", setLocale: mockSetLocale }),
10
+ }));
11
+
12
+ vi.mock("react-i18next", () => ({
13
+ useTranslation: () => ({ t: (key: string) => key }),
14
+ }));
15
+
16
+ describe("LanguageSwitcher", () => {
17
+ afterEach(cleanup);
18
+
19
+ it("should render a button", async () => {
20
+ const { LanguageSwitcher } = await import("../LanguageSwitcher");
21
+ render(<LanguageSwitcher />);
22
+ expect(screen.getByRole("button")).toBeInTheDocument();
23
+ });
24
+
25
+ it("should show '中' when locale is en", async () => {
26
+ const { LanguageSwitcher } = await import("../LanguageSwitcher");
27
+ render(<LanguageSwitcher />);
28
+ expect(screen.getByText("中")).toBeInTheDocument();
29
+ });
30
+
31
+ it("should have title 切换到中文 for en locale", async () => {
32
+ const { LanguageSwitcher } = await import("../LanguageSwitcher");
33
+ render(<LanguageSwitcher />);
34
+ expect(screen.getByRole("button")).toHaveAttribute("title", "切换到中文");
35
+ });
36
+
37
+ it("should display locale-specific text content", async () => {
38
+ const { LanguageSwitcher } = await import("../LanguageSwitcher");
39
+ render(<LanguageSwitcher />);
40
+ const button = screen.getByRole("button");
41
+ expect(button.textContent).toBe("中");
42
+ expect(button.className).toContain("rounded");
43
+ });
44
+ });
@@ -0,0 +1,41 @@
1
+ import { describe, it, expect, vi, afterEach } from "vitest";
2
+ import { render, screen, cleanup } from "@testing-library/react";
3
+ import React from "react";
4
+
5
+ const mockToggle = vi.fn();
6
+
7
+ vi.mock("../../stores/use-theme-store", () => ({
8
+ useThemeStore: (selector: (s: any) => any) =>
9
+ selector({ theme: "dark", toggleTheme: mockToggle }),
10
+ }));
11
+
12
+ describe("ThemeToggle", () => {
13
+ afterEach(cleanup);
14
+
15
+ it("should render a button", async () => {
16
+ const { ThemeToggle } = await import("../ThemeToggle");
17
+ render(<ThemeToggle />);
18
+ expect(screen.getByRole("button")).toBeInTheDocument();
19
+ });
20
+
21
+ it("should show sun icon for dark theme", async () => {
22
+ const { ThemeToggle } = await import("../ThemeToggle");
23
+ const { container } = render(<ThemeToggle />);
24
+ const svg = container.querySelector("svg");
25
+ expect(svg).toBeInTheDocument();
26
+ expect(svg!.getAttribute("class")).toContain("text-yellow-400");
27
+ });
28
+
29
+ it("should have title Switch to light mode for dark theme", async () => {
30
+ const { ThemeToggle } = await import("../ThemeToggle");
31
+ render(<ThemeToggle />);
32
+ expect(screen.getByRole("button")).toHaveAttribute("title", "Switch to light mode");
33
+ });
34
+
35
+ it("should render SVG element inside button", async () => {
36
+ const { ThemeToggle } = await import("../ThemeToggle");
37
+ const { container } = render(<ThemeToggle />);
38
+ const button = container.querySelector("button");
39
+ expect(button!.querySelector("svg")).toBeInTheDocument();
40
+ });
41
+ });
@@ -0,0 +1,120 @@
1
+ import { useTranslation } from "react-i18next";
2
+ import { Send, Play, Square, File } from "lucide-react";
3
+ import { useAppStore } from "../../stores/use-app-store";
4
+ import { useLogStore } from "../../stores/use-log-store";
5
+ import type { DemoMethod } from "../../types";
6
+
7
+ export function DebugPanel() {
8
+ const { t } = useTranslation();
9
+ const method = useAppStore((s) => s.method);
10
+ const result = useAppStore((s) => s.result);
11
+ const logs = useLogStore((s) => s.logs);
12
+ const tickEvents = useAppStore((s) => s.tickEvents);
13
+ const tickCount = useAppStore((s) => s.tickCount);
14
+ const subscriptionId = useAppStore((s) => s.subscriptionId);
15
+ const timerRunning = useAppStore((s) => s.timerRunning);
16
+ const setMethod = useAppStore((s) => s.setMethod);
17
+ const callRPC = useAppStore((s) => s.callRPC);
18
+ const handleSubscribe = useAppStore((s) => s.handleSubscribe);
19
+ const handleUnsubscribe = useAppStore((s) => s.handleUnsubscribe);
20
+
21
+ return (
22
+ <div className="w-72 bg-[var(--color-bg-primary)] border-l border-[var(--color-border-primary)] flex flex-col flex-shrink-0 overflow-y-auto">
23
+ <div className="p-3 border-b border-[var(--color-border-primary)]">
24
+ <div className="flex items-center justify-between mb-2">
25
+ <h2 className="text-xs font-semibold flex items-center gap-1.5">
26
+ <Send className="w-3.5 h-3.5 text-[var(--color-text-accent)]" />
27
+ {t("debug.rpcCalls")}
28
+ </h2>
29
+ </div>
30
+ <div className="flex gap-2 mb-2">
31
+ <select
32
+ value={method}
33
+ onChange={(e) => setMethod(e.target.value as DemoMethod)}
34
+ className="flex-1 px-2 py-1 text-xs bg-[var(--color-bg-tertiary)] rounded text-[var(--color-text-primary)] border border-[var(--color-border-secondary)]"
35
+ >
36
+ <option value="system.ping">system.ping</option>
37
+ <option value="system.hello">system.hello</option>
38
+ <option value="system.echo">system.echo</option>
39
+ <option value="chat.send">chat.send</option>
40
+ </select>
41
+ <button
42
+ onClick={() => callRPC("")}
43
+ className="px-3 py-1 text-xs bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] rounded transition-colors flex items-center gap-1"
44
+ >
45
+ <Play className="w-3 h-3" />
46
+ {t("debug.call")}
47
+ </button>
48
+ </div>
49
+ {!!result && (
50
+ <div className="bg-[var(--color-bg-tertiary)] rounded p-2">
51
+ <pre className="text-[var(--color-text-success)] text-[11px] overflow-x-auto whitespace-pre-wrap">
52
+ {JSON.stringify(result, null, 2)}
53
+ </pre>
54
+ </div>
55
+ )}
56
+ </div>
57
+
58
+ <div className="p-3 border-b border-[var(--color-border-primary)]">
59
+ <div className="flex items-center justify-between mb-2">
60
+ <h2 className="text-xs font-semibold flex items-center gap-1.5">
61
+ {timerRunning ? (
62
+ <Square className="w-3.5 h-3.5 text-[var(--color-text-success)] fill-green-400" />
63
+ ) : (
64
+ <Play className="w-3.5 h-3.5 text-[var(--color-text-tertiary)]" />
65
+ )}
66
+ {t("debug.subscriptions")}
67
+ {timerRunning && (
68
+ <span className="ml-1 px-1.5 py-0.5 bg-green-600/30 text-green-400 rounded text-[10px]">
69
+ LIVE
70
+ </span>
71
+ )}
72
+ </h2>
73
+ <div className="flex gap-2 items-center">
74
+ <span className="text-[11px] text-[var(--color-text-tertiary)]">
75
+ {t("debug.events", { count: tickCount })}
76
+ </span>
77
+ {!subscriptionId ? (
78
+ <button
79
+ onClick={handleSubscribe}
80
+ className="px-2 py-0.5 text-xs bg-green-600 hover:bg-green-700 rounded transition-colors"
81
+ >
82
+ {t("debug.subscribe")}
83
+ </button>
84
+ ) : (
85
+ <button
86
+ onClick={handleUnsubscribe}
87
+ className="px-2 py-0.5 text-xs bg-red-600 hover:bg-red-700 rounded transition-colors"
88
+ >
89
+ {t("debug.unsubscribe")}
90
+ </button>
91
+ )}
92
+ </div>
93
+ </div>
94
+ <div className="bg-[var(--color-bg-tertiary)] rounded p-2 max-h-32 overflow-y-auto font-mono text-[11px]">
95
+ {tickEvents.length === 0 ? (
96
+ <div className="text-[var(--color-text-placeholder)] text-center py-1">{t("debug.noEvents")}</div>
97
+ ) : (
98
+ tickEvents.map((ev, i) => (
99
+ <div key={i} className="text-[var(--color-text-info)]">{ev}</div>
100
+ ))
101
+ )}
102
+ </div>
103
+ </div>
104
+
105
+ <div className="p-3 flex flex-col flex-1 min-h-0">
106
+ <h2 className="text-xs font-semibold mb-2 flex-shrink-0 flex items-center gap-1.5">
107
+ <File className="w-3.5 h-3.5 text-[var(--color-text-tertiary)]" />
108
+ {t("debug.logs")}
109
+ </h2>
110
+ <div className="flex-1 bg-black rounded p-2 overflow-y-auto font-mono text-[11px]">
111
+ {logs.map((log, i) => (
112
+ <div key={i} className={log.includes("Error") ? "text-[var(--color-text-error)]" : "text-[var(--color-text-success)]"}>
113
+ {log}
114
+ </div>
115
+ ))}
116
+ </div>
117
+ </div>
118
+ </div>
119
+ );
120
+ }
@@ -0,0 +1,102 @@
1
+ import { describe, it, expect, vi, afterEach } from "vitest";
2
+ import { render, screen, cleanup, fireEvent } from "@testing-library/react";
3
+ import React from "react";
4
+
5
+ const mockCallRPC = vi.fn();
6
+ const mockHandleSubscribe = vi.fn();
7
+ const mockHandleUnsubscribe = vi.fn();
8
+
9
+ vi.mock("../../../stores/use-app-store", () => ({
10
+ useAppStore: (selector: any) =>
11
+ selector({
12
+ method: "system.ping",
13
+ result: { pong: true },
14
+ tickEvents: ["tick-1", "tick-2"],
15
+ tickCount: 2,
16
+ subscriptionId: null,
17
+ timerRunning: false,
18
+ setMethod: vi.fn(),
19
+ callRPC: mockCallRPC,
20
+ handleSubscribe: mockHandleSubscribe,
21
+ handleUnsubscribe: mockHandleUnsubscribe,
22
+ }),
23
+ }));
24
+
25
+ vi.mock("../../../stores/use-log-store", () => ({
26
+ useLogStore: (selector: any) =>
27
+ selector({ logs: ["Connected", "Error: timeout"] }),
28
+ }));
29
+
30
+ vi.mock("react-i18next", () => ({
31
+ useTranslation: () => ({ t: (k: string) => k }),
32
+ }));
33
+
34
+ describe("DebugPanel", () => {
35
+ afterEach(() => {
36
+ cleanup();
37
+ vi.clearAllMocks();
38
+ });
39
+
40
+ it("should render RPC calls section", async () => {
41
+ const { DebugPanel } = await import("../DebugPanel");
42
+ render(<DebugPanel />);
43
+ expect(screen.getByText("debug.rpcCalls")).toBeDefined();
44
+ });
45
+
46
+ it("should render subscriptions section", async () => {
47
+ const { DebugPanel } = await import("../DebugPanel");
48
+ render(<DebugPanel />);
49
+ expect(screen.getByText("debug.subscriptions")).toBeDefined();
50
+ });
51
+
52
+ it("should render logs section", async () => {
53
+ const { DebugPanel } = await import("../DebugPanel");
54
+ render(<DebugPanel />);
55
+ expect(screen.getByText("debug.logs")).toBeDefined();
56
+ });
57
+
58
+ it("should display method select", async () => {
59
+ const { DebugPanel } = await import("../DebugPanel");
60
+ render(<DebugPanel />);
61
+ expect(screen.getByText("system.ping")).toBeDefined();
62
+ });
63
+
64
+ it("should render call button", async () => {
65
+ const { DebugPanel } = await import("../DebugPanel");
66
+ render(<DebugPanel />);
67
+ expect(screen.getByText("debug.call")).toBeDefined();
68
+ });
69
+
70
+ it("should call callRPC on call button click", async () => {
71
+ const { DebugPanel } = await import("../DebugPanel");
72
+ render(<DebugPanel />);
73
+ fireEvent.click(screen.getByText("debug.call"));
74
+ expect(mockCallRPC).toHaveBeenCalledWith("");
75
+ });
76
+
77
+ it("should display result JSON", async () => {
78
+ const { DebugPanel } = await import("../DebugPanel");
79
+ render(<DebugPanel />);
80
+ expect(screen.getByText(/pong/)).toBeDefined();
81
+ });
82
+
83
+ it("should display log entries", async () => {
84
+ const { DebugPanel } = await import("../DebugPanel");
85
+ render(<DebugPanel />);
86
+ expect(screen.getByText("Connected")).toBeDefined();
87
+ expect(screen.getByText("Error: timeout")).toBeDefined();
88
+ });
89
+
90
+ it("should show subscribe button when not subscribed", async () => {
91
+ const { DebugPanel } = await import("../DebugPanel");
92
+ render(<DebugPanel />);
93
+ expect(screen.getByText("debug.subscribe")).toBeDefined();
94
+ });
95
+
96
+ it("should display tick events", async () => {
97
+ const { DebugPanel } = await import("../DebugPanel");
98
+ render(<DebugPanel />);
99
+ expect(screen.getByText("tick-1")).toBeDefined();
100
+ expect(screen.getByText("tick-2")).toBeDefined();
101
+ });
102
+ });
@@ -0,0 +1,106 @@
1
+ import { useState } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import { X, Columns2, Rows3 } from "lucide-react";
4
+ import ReactDiffViewer, { DiffMethod } from "react-diff-viewer-continued";
5
+ import { useGitStore } from "../../stores/use-git-store";
6
+
7
+ const DIFF_STYLES = {
8
+ variables: {
9
+ light: {
10
+ diffViewerBackground: "#111827",
11
+ },
12
+ dark: {
13
+ diffViewerBackground: "#111827",
14
+ diffViewerColor: "#e5e7eb",
15
+ addedBackground: "#052e16",
16
+ addedColor: "#4ade80",
17
+ removedBackground: "#450a0a",
18
+ removedColor: "#fca5a5",
19
+ wordAddedBackground: "#065f46",
20
+ wordRemovedBackground: "#7f1d1d",
21
+ addedGutterBackground: "#052e16",
22
+ removedGutterBackground: "#450a0a",
23
+ gutterBackground: "#1f2937",
24
+ gutterColor: "#6b7280",
25
+ codeFoldGutterBackground: "#1f2937",
26
+ codeFoldBackground: "#1f2937",
27
+ emptyLineBackground: "#111827",
28
+ gutterBackgroundDark: "#1f2937",
29
+ highlightGutterBackground: "#374151",
30
+ highlightBackground: "#374151",
31
+ },
32
+ },
33
+ line: {
34
+ fontSize: "12px",
35
+ fontFamily: "ui-monospace, SFMono-Regular, monospace",
36
+ lineHeight: "1.6",
37
+ },
38
+ gutter: {
39
+ fontSize: "12px",
40
+ fontFamily: "ui-monospace, SFMono-Regular, monospace",
41
+ minWidth: "40px",
42
+ padding: "0 8px",
43
+ },
44
+ } as const;
45
+
46
+ export function DiffViewerPanel() {
47
+ const { t } = useTranslation();
48
+ const currentDiff = useGitStore((s) => s.currentDiff);
49
+ const loadingDiff = useGitStore((s) => s.loadingDiff);
50
+ const clearDiff = useGitStore((s) => s.clearDiff);
51
+ const [splitView, setSplitView] = useState(false);
52
+
53
+ if (!currentDiff && !loadingDiff) return null;
54
+
55
+ const fileName = currentDiff?.filePath.split("/").pop() || "";
56
+
57
+ return (
58
+ <div className="absolute inset-0 bg-[var(--color-bg-primary)] flex flex-col" style={{ zIndex: 40 }}>
59
+ <div className="h-9 bg-[var(--color-bg-secondary)] border-b border-[var(--color-border-primary)] flex items-center px-3 text-xs flex-shrink-0 gap-2">
60
+ <span className="text-[var(--color-text-secondary)] font-medium">{fileName}</span>
61
+ <span className="text-[var(--color-text-placeholder)] truncate text-[10px]">{currentDiff?.filePath}</span>
62
+ <div className="ml-auto flex items-center gap-1">
63
+ <button
64
+ onClick={() => setSplitView(false)}
65
+ className={`p-1 rounded transition-colors ${!splitView ? "bg-[var(--color-bg-input)] text-[var(--color-text-primary)]" : "text-[var(--color-text-placeholder)] hover:text-[var(--color-text-primary)]"}`}
66
+ title={t("diff.lineByLine")}
67
+ >
68
+ <Rows3 className="w-3.5 h-3.5" />
69
+ </button>
70
+ <button
71
+ onClick={() => setSplitView(true)}
72
+ className={`p-1 rounded transition-colors ${splitView ? "bg-[var(--color-bg-input)] text-[var(--color-text-primary)]" : "text-[var(--color-text-placeholder)] hover:text-[var(--color-text-primary)]"}`}
73
+ title={t("diff.sideBySide")}
74
+ >
75
+ <Columns2 className="w-3.5 h-3.5" />
76
+ </button>
77
+ <button
78
+ onClick={clearDiff}
79
+ className="ml-1 text-[var(--color-text-placeholder)] hover:text-[var(--color-text-primary)] transition-colors"
80
+ >
81
+ <X className="w-4 h-4" />
82
+ </button>
83
+ </div>
84
+ </div>
85
+
86
+ <div className="flex-1 overflow-auto">
87
+ {loadingDiff ? (
88
+ <div className="flex items-center justify-center h-full text-[var(--color-text-placeholder)]">
89
+ {t("diff.loading")}
90
+ </div>
91
+ ) : currentDiff ? (
92
+ <ReactDiffViewer
93
+ oldValue={currentDiff.oldContent}
94
+ newValue={currentDiff.newContent}
95
+ splitView={splitView}
96
+ compareMethod={DiffMethod.LINES}
97
+ useDarkTheme={true}
98
+ leftTitle={t("diff.before")}
99
+ rightTitle={t("diff.after")}
100
+ styles={DIFF_STYLES}
101
+ />
102
+ ) : null}
103
+ </div>
104
+ </div>
105
+ );
106
+ }
@@ -0,0 +1,73 @@
1
+ import { describe, it, expect, vi, afterEach } from "vitest";
2
+ import { render, screen, cleanup } from "@testing-library/react";
3
+ import React from "react";
4
+
5
+ const mockClearDiff = vi.fn();
6
+
7
+ vi.mock("../../../stores/use-git-store", () => ({
8
+ useGitStore: (selector: any) =>
9
+ selector({
10
+ currentDiff: {
11
+ filePath: "src/components/App.tsx",
12
+ oldContent: "old code",
13
+ newContent: "new code",
14
+ },
15
+ loadingDiff: false,
16
+ clearDiff: mockClearDiff,
17
+ }),
18
+ }));
19
+
20
+ vi.mock("react-i18next", () => ({
21
+ useTranslation: () => ({ t: (k: string) => k }),
22
+ }));
23
+
24
+ vi.mock("react-diff-viewer-continued", () => ({
25
+ __esModule: true,
26
+ default: ({ oldValue, newValue }: any) => (
27
+ <div data-testid="diff-viewer">{oldValue} → {newValue}</div>
28
+ ),
29
+ DiffMethod: { LINES: "LINES" },
30
+ }));
31
+
32
+ describe("DiffViewerPanel", () => {
33
+ afterEach(() => {
34
+ cleanup();
35
+ vi.clearAllMocks();
36
+ });
37
+
38
+ it("should render file name in header", async () => {
39
+ const { DiffViewerPanel } = await import("../DiffViewerPanel");
40
+ render(<DiffViewerPanel />);
41
+ expect(screen.getByText("App.tsx")).toBeDefined();
42
+ });
43
+
44
+ it("should render full file path", async () => {
45
+ const { DiffViewerPanel } = await import("../DiffViewerPanel");
46
+ render(<DiffViewerPanel />);
47
+ expect(screen.getByText("src/components/App.tsx")).toBeDefined();
48
+ });
49
+
50
+ it("should render diff viewer content", async () => {
51
+ const { DiffViewerPanel } = await import("../DiffViewerPanel");
52
+ render(<DiffViewerPanel />);
53
+ expect(screen.getByTestId("diff-viewer")).toBeDefined();
54
+ });
55
+
56
+ it("should render view mode toggle buttons", async () => {
57
+ const { DiffViewerPanel } = await import("../DiffViewerPanel");
58
+ render(<DiffViewerPanel />);
59
+ expect(screen.getByTitle("diff.lineByLine")).toBeDefined();
60
+ expect(screen.getByTitle("diff.sideBySide")).toBeDefined();
61
+ });
62
+
63
+ it("should return null when no diff and not loading", async () => {
64
+ vi.resetModules();
65
+ vi.doMock("../../../stores/use-git-store", () => ({
66
+ useGitStore: (selector: any) => selector({ currentDiff: null, loadingDiff: false, clearDiff: mockClearDiff }),
67
+ }));
68
+ const mod = await import("../DiffViewerPanel");
69
+ const { container } = render(<mod.DiffViewerPanel />);
70
+ expect(container.innerHTML).toBe("");
71
+ vi.resetModules();
72
+ });
73
+ });
@@ -0,0 +1,53 @@
1
+ import { useEffect, useCallback } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+
4
+ interface ConfirmDialogProps {
5
+ title: string;
6
+ message: string;
7
+ onConfirm: () => void;
8
+ onCancel: () => void;
9
+ }
10
+
11
+ export function ConfirmDialog({ title, message, onConfirm, onCancel }: ConfirmDialogProps) {
12
+ const { t } = useTranslation();
13
+
14
+ const handleKeyDown = useCallback(
15
+ (e: KeyboardEvent) => {
16
+ if (e.key === "Escape") onCancel();
17
+ },
18
+ [onCancel],
19
+ );
20
+
21
+ useEffect(() => {
22
+ document.addEventListener("keydown", handleKeyDown);
23
+ return () => document.removeEventListener("keydown", handleKeyDown);
24
+ }, [handleKeyDown]);
25
+
26
+ return (
27
+ <div
28
+ className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
29
+ onClick={(e) => {
30
+ if (e.target === e.currentTarget) onCancel();
31
+ }}
32
+ >
33
+ <div className="bg-[var(--color-bg-secondary)] border border-[var(--color-border-secondary)] rounded-lg shadow-2xl p-4 min-w-[300px] max-w-[400px]">
34
+ <h3 className="text-sm font-semibold text-[var(--color-text-primary)] mb-2">{title}</h3>
35
+ <p className="text-xs text-[var(--color-text-secondary)] mb-4">{message}</p>
36
+ <div className="flex justify-end gap-2">
37
+ <button
38
+ className="px-3 py-1.5 text-xs bg-[var(--color-bg-tertiary)] hover:bg-[var(--color-bg-active)] rounded transition-colors text-[var(--color-text-secondary)]"
39
+ onClick={onCancel}
40
+ >
41
+ {t("common.cancel")}
42
+ </button>
43
+ <button
44
+ className="px-3 py-1.5 text-xs bg-red-600 hover:bg-red-700 rounded transition-colors text-white"
45
+ onClick={onConfirm}
46
+ >
47
+ {t("common.delete")}
48
+ </button>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ );
53
+ }
@@ -0,0 +1,82 @@
1
+ import { useEffect, useRef, useCallback } from "react";
2
+
3
+ export interface MenuItem {
4
+ label: string;
5
+ icon?: React.ReactNode;
6
+ onClick: () => void;
7
+ danger?: boolean;
8
+ divider?: boolean;
9
+ }
10
+
11
+ interface ContextMenuProps {
12
+ x: number;
13
+ y: number;
14
+ items: MenuItem[];
15
+ onClose: () => void;
16
+ }
17
+
18
+ export function ContextMenu({ x, y, items, onClose }: ContextMenuProps) {
19
+ const ref = useRef<HTMLDivElement>(null);
20
+
21
+ // Adjust position to stay within viewport
22
+ const adjustedX = useRef(x);
23
+ const adjustedY = useRef(y);
24
+
25
+ useEffect(() => {
26
+ const el = ref.current;
27
+ if (!el) return;
28
+ const rect = el.getBoundingClientRect();
29
+ const vw = window.innerWidth;
30
+ const vh = window.innerHeight;
31
+ if (rect.right > vw) adjustedX.current = Math.max(0, x - rect.width);
32
+ if (rect.bottom > vh) adjustedY.current = Math.max(0, y - rect.height);
33
+ el.style.left = `${adjustedX.current}px`;
34
+ el.style.top = `${adjustedY.current}px`;
35
+ }, [x, y]);
36
+
37
+ const handleClickOutside = useCallback((e: MouseEvent) => {
38
+ if (ref.current && !ref.current.contains(e.target as Node)) {
39
+ onClose();
40
+ }
41
+ }, [onClose]);
42
+
43
+ useEffect(() => {
44
+ document.addEventListener("mousedown", handleClickOutside);
45
+ const handleKeyDown = (e: KeyboardEvent) => {
46
+ if (e.key === "Escape") onClose();
47
+ };
48
+ document.addEventListener("keydown", handleKeyDown);
49
+ return () => {
50
+ document.removeEventListener("mousedown", handleClickOutside);
51
+ document.removeEventListener("keydown", handleKeyDown);
52
+ };
53
+ }, [handleClickOutside, onClose]);
54
+
55
+ return (
56
+ <div
57
+ ref={ref}
58
+ className="fixed z-50 min-w-[160px] bg-[var(--color-bg-secondary)] border border-[var(--color-border-secondary)] rounded-md shadow-xl py-1"
59
+ style={{ left: adjustedX.current, top: adjustedY.current }}
60
+ >
61
+ {items.map((item, i) => (
62
+ <div key={i}>
63
+ {item.divider && i > 0 && <div className="border-t border-[var(--color-border-secondary)] my-1" />}
64
+ <button
65
+ className={`w-full text-left px-3 py-1.5 text-xs flex items-center gap-2 transition-colors ${
66
+ item.danger
67
+ ? "text-[var(--color-text-error)] hover:bg-red-900/30"
68
+ : "text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-hover)]"
69
+ }`}
70
+ onClick={() => {
71
+ item.onClick();
72
+ onClose();
73
+ }}
74
+ >
75
+ {item.icon && <span className="w-3.5 shrink-0">{item.icon}</span>}
76
+ {item.label}
77
+ </button>
78
+ </div>
79
+ ))}
80
+ </div>
81
+ );
82
+ }