@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,76 @@
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 mockCheckout = vi.fn();
6
+ const mockFetchBranches = vi.fn();
7
+
8
+ vi.mock("../../../stores/use-git-store", () => ({
9
+ useGitStore: (selector: any) =>
10
+ selector({
11
+ branches: [
12
+ { name: "main", isCurrent: true, isRemote: false },
13
+ { name: "dev", isCurrent: false, isRemote: false },
14
+ { name: "origin/feat", isCurrent: false, isRemote: true },
15
+ ],
16
+ loadingBranches: false,
17
+ loadingAction: null,
18
+ fetchBranches: mockFetchBranches,
19
+ checkout: mockCheckout,
20
+ }),
21
+ }));
22
+
23
+ vi.mock("../../../stores/use-explorer-store", () => ({
24
+ useExplorerStore: (selector: any) => selector({ currentPath: "/project" }),
25
+ }));
26
+
27
+ describe("GitBranchSelector", () => {
28
+ afterEach(() => {
29
+ cleanup();
30
+ vi.clearAllMocks();
31
+ });
32
+
33
+ it("should render local branches", async () => {
34
+ const { GitBranchSelector } = await import("../GitBranchSelector");
35
+ render(<GitBranchSelector onClose={vi.fn()} />);
36
+ expect(screen.getByText("Local")).toBeDefined();
37
+ expect(screen.getByText("main")).toBeDefined();
38
+ expect(screen.getByText("dev")).toBeDefined();
39
+ });
40
+
41
+ it("should render remote branches", async () => {
42
+ const { GitBranchSelector } = await import("../GitBranchSelector");
43
+ render(<GitBranchSelector onClose={vi.fn()} />);
44
+ expect(screen.getByText("Remote")).toBeDefined();
45
+ expect(screen.getByText("feat")).toBeDefined();
46
+ });
47
+
48
+ it("should call fetchBranches on mount", async () => {
49
+ const { GitBranchSelector } = await import("../GitBranchSelector");
50
+ render(<GitBranchSelector onClose={vi.fn()} />);
51
+ expect(mockFetchBranches).toHaveBeenCalledWith("/project");
52
+ });
53
+
54
+ it("should show check icon for current branch", async () => {
55
+ const { GitBranchSelector } = await import("../GitBranchSelector");
56
+ const { container } = render(<GitBranchSelector onClose={vi.fn()} />);
57
+ const checks = container.querySelectorAll("svg");
58
+ expect(checks.length).toBeGreaterThan(0);
59
+ });
60
+
61
+ it("should call checkout and onClose when branch clicked", async () => {
62
+ const onClose = vi.fn();
63
+ const { GitBranchSelector } = await import("../GitBranchSelector");
64
+ render(<GitBranchSelector onClose={onClose} />);
65
+ fireEvent.click(screen.getByText("dev"));
66
+ expect(mockCheckout).toHaveBeenCalledWith("/project", "dev");
67
+ expect(onClose).toHaveBeenCalled();
68
+ });
69
+
70
+ it("should not call checkout for current branch", async () => {
71
+ const { GitBranchSelector } = await import("../GitBranchSelector");
72
+ render(<GitBranchSelector onClose={vi.fn()} />);
73
+ fireEvent.click(screen.getByText("main"));
74
+ expect(mockCheckout).not.toHaveBeenCalled();
75
+ });
76
+ });
@@ -0,0 +1,91 @@
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 mockCommit = vi.fn();
6
+
7
+ vi.mock("../../../stores/use-git-store", () => {
8
+ const state = {
9
+ staged: [{ path: "a.ts", status: "modified" }],
10
+ loadingAction: null as string | null,
11
+ commit: mockCommit,
12
+ };
13
+ return { useGitStore: (selector: any) => selector(state) };
14
+ });
15
+
16
+ vi.mock("../../../stores/use-explorer-store", () => ({
17
+ useExplorerStore: (selector: any) => selector({ currentPath: "/project" }),
18
+ }));
19
+
20
+ vi.mock("react-i18next", () => ({
21
+ useTranslation: () => ({ t: (k: string) => k }),
22
+ }));
23
+
24
+ describe("GitCommitInput", () => {
25
+ afterEach(() => {
26
+ cleanup();
27
+ mockCommit.mockClear();
28
+ });
29
+
30
+ it("should render textarea when staged files exist", async () => {
31
+ const { GitCommitInput } = await import("../GitCommitInput");
32
+ render(<GitCommitInput />);
33
+ expect(screen.getByPlaceholderText("git.commitPlaceholder")).toBeDefined();
34
+ });
35
+
36
+ it("should render commit button", async () => {
37
+ const { GitCommitInput } = await import("../GitCommitInput");
38
+ render(<GitCommitInput />);
39
+ expect(screen.getByText("git.commit")).toBeDefined();
40
+ });
41
+
42
+ it("should disable commit button when message is empty", async () => {
43
+ const { GitCommitInput } = await import("../GitCommitInput");
44
+ render(<GitCommitInput />);
45
+ const btn = screen.getByText("git.commit");
46
+ expect(btn).toBeDisabled();
47
+ });
48
+
49
+ it("should enable commit button when message is entered", async () => {
50
+ const { GitCommitInput } = await import("../GitCommitInput");
51
+ render(<GitCommitInput />);
52
+ const textarea = screen.getByPlaceholderText("git.commitPlaceholder");
53
+ fireEvent.change(textarea, { target: { value: "test commit" } });
54
+ const btn = screen.getByText("git.commit");
55
+ expect(btn).not.toBeDisabled();
56
+ });
57
+
58
+ it("should call commit on button click with message", async () => {
59
+ const { GitCommitInput } = await import("../GitCommitInput");
60
+ render(<GitCommitInput />);
61
+ const textarea = screen.getByPlaceholderText("git.commitPlaceholder");
62
+ fireEvent.change(textarea, { target: { value: "test commit" } });
63
+ fireEvent.click(screen.getByText("git.commit"));
64
+ expect(mockCommit).toHaveBeenCalledWith("/project", "test commit");
65
+ });
66
+
67
+ it("should submit on Cmd+Enter", async () => {
68
+ const { GitCommitInput } = await import("../GitCommitInput");
69
+ render(<GitCommitInput />);
70
+ const textarea = screen.getByPlaceholderText("git.commitPlaceholder");
71
+ fireEvent.change(textarea, { target: { value: "test" } });
72
+ fireEvent.keyDown(textarea, { key: "Enter", metaKey: true });
73
+ expect(mockCommit).toHaveBeenCalledWith("/project", "test");
74
+ });
75
+
76
+ it("should not render when no staged files", async () => {
77
+ vi.resetModules();
78
+ vi.doMock("../../../stores/use-git-store", () => ({
79
+ useGitStore: (selector: any) => selector({ staged: [], loadingAction: null, commit: mockCommit }),
80
+ }));
81
+ vi.doMock("../../../stores/use-explorer-store", () => ({
82
+ useExplorerStore: (selector: any) => selector({ currentPath: "/project" }),
83
+ }));
84
+ vi.doMock("react-i18next", () => ({
85
+ useTranslation: () => ({ t: (k: string) => k }),
86
+ }));
87
+ const mod = await import("../GitCommitInput");
88
+ const { container } = render(<mod.GitCommitInput />);
89
+ expect(container.innerHTML).toBe("");
90
+ });
91
+ });
@@ -0,0 +1,174 @@
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 mockFetchStatus = vi.fn();
6
+ const mockFetchWorktrees = vi.fn();
7
+ const mockFetchLog = vi.fn();
8
+ const mockFetchDiff = vi.fn();
9
+ const mockPush = vi.fn();
10
+ const mockPull = vi.fn();
11
+ const mockStageFiles = vi.fn();
12
+ const mockUnstageFiles = vi.fn();
13
+ const mockToggleCommitExpand = vi.fn();
14
+ const mockFetchCommitFileDiff = vi.fn();
15
+
16
+ const gitState = {
17
+ branch: "main",
18
+ ahead: 2,
19
+ behind: 1,
20
+ staged: [
21
+ { path: "src/a.ts", status: "modified" as const },
22
+ ],
23
+ changed: [
24
+ { path: "src/b.ts", status: "added" as const },
25
+ ],
26
+ untracked: ["src/c.ts"],
27
+ commits: [] as any[],
28
+ loadingCommits: false,
29
+ currentDiff: null as any,
30
+ expandedCommits: new Set<string>(),
31
+ commitFiles: {} as Record<string, any[]>,
32
+ loadingCommitFiles: new Set<string>(),
33
+ loadingAction: null as string | null,
34
+ worktrees: [] as any[],
35
+ loadingDiff: false,
36
+ loadingBranches: false,
37
+ branches: [] as any[],
38
+ fetchStatus: mockFetchStatus,
39
+ fetchWorktrees: mockFetchWorktrees,
40
+ fetchLog: mockFetchLog,
41
+ fetchDiff: mockFetchDiff,
42
+ push: mockPush,
43
+ pull: mockPull,
44
+ stageFiles: mockStageFiles,
45
+ unstageFiles: mockUnstageFiles,
46
+ toggleCommitExpand: mockToggleCommitExpand,
47
+ fetchCommitFileDiff: mockFetchCommitFileDiff,
48
+ commit: vi.fn(),
49
+ fetchBranches: vi.fn(),
50
+ checkout: vi.fn(),
51
+ };
52
+
53
+ vi.mock("../../../stores/use-git-store", () => ({
54
+ useGitStore: (selector: any) => selector(gitState),
55
+ }));
56
+
57
+ const explorerState = {
58
+ currentPath: "/project",
59
+ openFile: vi.fn(),
60
+ };
61
+
62
+ vi.mock("../../../stores/use-explorer-store", () => ({
63
+ useExplorerStore: (selector: any) => selector(explorerState),
64
+ }));
65
+
66
+ vi.mock("../../../stores/use-sidebar-store", () => ({
67
+ useSidebarStore: (selector: any) => selector({ isPinned: true, breakpoint: "desktop" }),
68
+ }));
69
+
70
+ vi.mock("react-i18next", () => ({
71
+ useTranslation: () => ({ t: (k: string) => k }),
72
+ }));
73
+
74
+ vi.mock("../GitCommitInput", () => ({
75
+ GitCommitInput: () => <div data-testid="commit-input" />,
76
+ }));
77
+
78
+ vi.mock("../GitBranchSelector", () => ({
79
+ GitBranchSelector: ({ onClose: _onClose }: any) => <div data-testid="branch-selector" />,
80
+ }));
81
+
82
+ vi.mock("../../explorer/ContextMenu", () => ({
83
+ ContextMenu: ({ items: _items }: any) => <div data-testid="context-menu" />,
84
+ }));
85
+
86
+ vi.mock("../../sidebar/PinButton", () => ({
87
+ PinButton: () => <div data-testid="pin-button" />,
88
+ }));
89
+
90
+ describe("GitPanel", () => {
91
+ afterEach(cleanup);
92
+
93
+ it("should render git title", async () => {
94
+ const { GitPanel } = await import("../GitPanel");
95
+ render(<GitPanel />);
96
+ expect(screen.getByText("git.title")).toBeDefined();
97
+ });
98
+
99
+ it("should display branch name", async () => {
100
+ const { GitPanel } = await import("../GitPanel");
101
+ render(<GitPanel />);
102
+ expect(screen.getByText("main")).toBeDefined();
103
+ });
104
+
105
+ it("should show total changes badge", async () => {
106
+ const { GitPanel } = await import("../GitPanel");
107
+ render(<GitPanel />);
108
+ expect(screen.getByText("3")).toBeDefined();
109
+ });
110
+
111
+ it("should show ahead/behind indicators", async () => {
112
+ const { GitPanel } = await import("../GitPanel");
113
+ render(<GitPanel />);
114
+ expect(screen.getByText("↑2")).toBeDefined();
115
+ expect(screen.getByText("↓1")).toBeDefined();
116
+ });
117
+
118
+ it("should render staged files section", async () => {
119
+ const { GitPanel } = await import("../GitPanel");
120
+ render(<GitPanel />);
121
+ expect(screen.getByText(/git.staged/)).toBeDefined();
122
+ expect(screen.getByText("a.ts")).toBeDefined();
123
+ });
124
+
125
+ it("should render changed files section", async () => {
126
+ const { GitPanel } = await import("../GitPanel");
127
+ render(<GitPanel />);
128
+ expect(screen.getByText(/git.changes/)).toBeDefined();
129
+ expect(screen.getByText("b.ts")).toBeDefined();
130
+ });
131
+
132
+ it("should render untracked files section", async () => {
133
+ const { GitPanel } = await import("../GitPanel");
134
+ render(<GitPanel />);
135
+ expect(screen.getByText(/git.untracked/)).toBeDefined();
136
+ expect(screen.getByText("c.ts")).toBeDefined();
137
+ });
138
+
139
+ it("should render push button", async () => {
140
+ const { GitPanel } = await import("../GitPanel");
141
+ render(<GitPanel />);
142
+ expect(screen.getByTitle("git.push")).toBeDefined();
143
+ });
144
+
145
+ it("should render pull button", async () => {
146
+ const { GitPanel } = await import("../GitPanel");
147
+ render(<GitPanel />);
148
+ expect(screen.getByTitle("git.pull")).toBeDefined();
149
+ });
150
+
151
+ it("should render refresh button", async () => {
152
+ const { GitPanel } = await import("../GitPanel");
153
+ render(<GitPanel />);
154
+ expect(screen.getByTitle("git.refresh")).toBeDefined();
155
+ });
156
+
157
+ it("should call fetchStatus on mount", async () => {
158
+ const { GitPanel } = await import("../GitPanel");
159
+ render(<GitPanel />);
160
+ expect(mockFetchStatus).toHaveBeenCalledWith("/project");
161
+ });
162
+
163
+ it("should render commits toggle button", async () => {
164
+ const { GitPanel } = await import("../GitPanel");
165
+ render(<GitPanel />);
166
+ expect(screen.getByText("git.commits")).toBeDefined();
167
+ });
168
+
169
+ it("should use full-width class when hideOuterShell is true", async () => {
170
+ const { GitPanel } = await import("../GitPanel");
171
+ const { container } = render(<GitPanel hideOuterShell />);
172
+ expect(container.firstChild).toBeDefined();
173
+ });
174
+ });
@@ -0,0 +1,170 @@
1
+ import React, { Suspense } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import { Wifi, Monitor, MessageSquare, Rss, Terminal, ListTodo } from "lucide-react";
4
+ import { useConnectionStore } from "../../stores/use-connection-store";
5
+ import { useExplorerStore } from "../../stores/use-explorer-store";
6
+ import { useSidebarStore } from "../../stores/use-sidebar-store";
7
+ import { ActivityBar } from "../activity-bar/ActivityBar";
8
+ import { MobileTabBar } from "../activity-bar/MobileTabBar";
9
+ import { ExplorerSidebar } from "../explorer/ExplorerSidebar";
10
+ import { ChatPanel } from "../chat/ChatPanel";
11
+ import { ThemeToggle } from "../common/ThemeToggle";
12
+
13
+ const GitPanel = React.lazy(() => import("../git/GitPanel").then((m) => ({ default: m.GitPanel })));
14
+ const SearchPanel = React.lazy(() => import("../search/SearchPanel").then((m) => ({ default: m.SearchPanel })));
15
+ const RulesPanel = React.lazy(() => import("../rules/RulesPanel").then((m) => ({ default: m.RulesPanel })));
16
+ const FeedPanel = React.lazy(() => import("../feed/FeedPanel").then((m) => ({ default: m.FeedPanel })));
17
+ const BashPanel = React.lazy(() => import("../bash/BashPanel").then((m) => ({ default: m.BashPanel })));
18
+ const TodoPanel = React.lazy(() => import("../todo/TodoPanel").then((m) => ({ default: m.TodoPanel })));
19
+ const FilePreviewOverlay = React.lazy(() => import("../file-preview/FilePreviewOverlay").then((m) => ({ default: m.FilePreviewOverlay })));
20
+ const DiffViewerPanel = React.lazy(() => import("../diff/DiffViewerPanel").then((m) => ({ default: m.DiffViewerPanel })));
21
+ const DebugPanel = React.lazy(() => import("../debug/DebugPanel").then((m) => ({ default: m.DebugPanel })));
22
+
23
+ function PanelFallback() {
24
+ return (
25
+ <div className="flex items-center justify-center h-full">
26
+ <div className="animate-pulse text-[var(--color-text-tertiary)] text-sm">Loading...</div>
27
+ </div>
28
+ );
29
+ }
30
+
31
+ export type CenterTab = "chat" | "feed" | "bash" | "todo";
32
+
33
+ interface AppLayoutProps {
34
+ centerTab: CenterTab;
35
+ setCenterTab: (tab: CenterTab) => void;
36
+ sidebarWidth: number;
37
+ handleResizeStart: (e: React.MouseEvent) => void;
38
+ }
39
+
40
+ export function AppLayout({
41
+ centerTab,
42
+ setCenterTab,
43
+ sidebarWidth,
44
+ handleResizeStart,
45
+ }: AppLayoutProps) {
46
+ const { t } = useTranslation();
47
+ const mode = useConnectionStore((s) => s.mode);
48
+ const filePreview = useExplorerStore((s) => s.filePreview);
49
+ const loadingFile = useExplorerStore((s) => s.loadingFile);
50
+ const closePreview = useExplorerStore((s) => s.closePreview);
51
+
52
+ const activePanel = useSidebarStore((s) => s.activePanel);
53
+ const isPinned = useSidebarStore((s) => s.isPinned);
54
+ const drawerOpen = useSidebarStore((s) => s.drawerOpen);
55
+ const setDrawerOpen = useSidebarStore((s) => s.setDrawerOpen);
56
+ const breakpoint = useSidebarStore((s) => s.breakpoint);
57
+
58
+ const isMobile = breakpoint === "mobile";
59
+ const isDesktop = breakpoint === "desktop";
60
+ const sidebarIsDrawer = isMobile || !isPinned;
61
+ const showSidebar = activePanel !== null && (isPinned || drawerOpen);
62
+ const showDebug = isDesktop;
63
+
64
+ const sidebarContent = activePanel === "explorer" ? (
65
+ <ExplorerSidebar hideOuterShell />
66
+ ) : activePanel === "git" ? (
67
+ <Suspense fallback={<PanelFallback />}><GitPanel hideOuterShell /></Suspense>
68
+ ) : activePanel === "search" ? (
69
+ <Suspense fallback={<PanelFallback />}><SearchPanel /></Suspense>
70
+ ) : activePanel === "rules" ? (
71
+ <Suspense fallback={<PanelFallback />}><RulesPanel /></Suspense>
72
+ ) : null;
73
+
74
+ const centerTabs: { id: CenterTab; icon: typeof MessageSquare; label: string }[] = [
75
+ { id: "chat", icon: MessageSquare, label: t("tabs.chat") },
76
+ { id: "feed", icon: Rss, label: t("tabs.feed") },
77
+ { id: "bash", icon: Terminal, label: t("tabs.bash") },
78
+ { id: "todo", icon: ListTodo, label: t("tabs.todo") },
79
+ ];
80
+
81
+ return (
82
+ <div className="h-screen bg-[var(--color-bg-primary)] text-[var(--color-text-primary)] flex flex-col overflow-hidden">
83
+ {!isMobile && (
84
+ <div className="h-8 bg-[var(--color-bg-secondary)] flex items-center px-3 text-xs border-b border-[var(--color-border-primary)] flex-shrink-0">
85
+ <span className={`px-2 py-0.5 rounded flex items-center gap-1 ${mode === "desktop" ? "bg-green-600" : "bg-blue-600"}`}>
86
+ {mode === "desktop" ? <Monitor className="w-3 h-3" /> : <Wifi className="w-3 h-3" />}
87
+ {mode === "desktop" ? t("app.mode.desktop") : t("app.mode.web")}
88
+ </span>
89
+ <span className="ml-3 text-[var(--color-text-tertiary)]">{t("app.title")}</span>
90
+ <div className="ml-auto">
91
+ <ThemeToggle />
92
+ </div>
93
+ </div>
94
+ )}
95
+
96
+ <div className="flex-1 flex overflow-hidden relative">
97
+ {!isMobile && <ActivityBar />}
98
+
99
+ {showSidebar && isPinned && !isMobile && (
100
+ <div className="bg-[var(--color-bg-primary)] border-r border-[var(--color-border-primary)] flex flex-col flex-shrink-0 overflow-hidden relative"
101
+ style={{ width: sidebarWidth }}>
102
+ {sidebarContent}
103
+ <div
104
+ className="absolute right-0 top-0 bottom-0 w-1 cursor-col-resize hover:bg-indigo-500/50 active:bg-indigo-500 transition-colors z-10 group"
105
+ onMouseDown={handleResizeStart}
106
+ style={{ width: 4, marginRight: -4 }}
107
+ >
108
+ <div className="absolute inset-y-0 left-1/2 w-0.5 bg-[var(--color-border-primary)] group-hover:bg-[var(--color-text-accent)] transition-colors -translate-x-1/2" />
109
+ </div>
110
+ </div>
111
+ )}
112
+
113
+ <div className="flex-1 flex flex-col overflow-hidden relative">
114
+ <div className="flex items-center bg-[var(--color-bg-secondary)] border-b border-[var(--color-border-primary)] flex-shrink-0">
115
+ {centerTabs.map(({ id, icon: Icon, label }) => (
116
+ <button
117
+ key={id}
118
+ data-testid={`center-tab-${id}`}
119
+ onClick={() => setCenterTab(id)}
120
+ className={`flex items-center gap-1.5 px-4 py-1.5 text-xs border-b-2 transition-colors ${
121
+ centerTab === id
122
+ ? "border-[var(--color-accent)] text-[var(--color-text-primary)]"
123
+ : "border-transparent text-[var(--color-text-placeholder)] hover:text-[var(--color-text-secondary)]"
124
+ }`}
125
+ >
126
+ <Icon className="w-3.5 h-3.5" />
127
+ {label}
128
+ </button>
129
+ ))}
130
+ </div>
131
+
132
+ {centerTab === "chat" && <ChatPanel />}
133
+ {centerTab === "feed" && <Suspense fallback={<PanelFallback />}><FeedPanel /></Suspense>}
134
+ {centerTab === "bash" && <Suspense fallback={<PanelFallback />}><BashPanel /></Suspense>}
135
+ {centerTab === "todo" && <Suspense fallback={<PanelFallback />}><TodoPanel /></Suspense>}
136
+ {filePreview && (
137
+ <Suspense fallback={<PanelFallback />}>
138
+ <FilePreviewOverlay
139
+ preview={filePreview}
140
+ loading={loadingFile}
141
+ onClose={closePreview}
142
+ />
143
+ </Suspense>
144
+ )}
145
+ <Suspense fallback={null}><DiffViewerPanel /></Suspense>
146
+ </div>
147
+
148
+ {showDebug && <Suspense fallback={<PanelFallback />}><DebugPanel /></Suspense>}
149
+ </div>
150
+
151
+ {sidebarIsDrawer && showSidebar && (
152
+ <>
153
+ <div
154
+ className="fixed inset-0 bg-black/50 z-40"
155
+ style={isMobile ? { bottom: 56 } : undefined}
156
+ onClick={() => setDrawerOpen(false)}
157
+ />
158
+ <div
159
+ className="fixed left-0 top-0 w-60 z-50 bg-[var(--color-bg-primary)] border-r border-[var(--color-border-primary)] flex flex-col overflow-hidden"
160
+ style={isMobile ? { top: 0, bottom: 56 } : { top: 0, bottom: 0 }}
161
+ >
162
+ {sidebarContent}
163
+ </div>
164
+ </>
165
+ )}
166
+
167
+ {isMobile && <MobileTabBar />}
168
+ </div>
169
+ );
170
+ }
@@ -0,0 +1,163 @@
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
+ vi.mock("../../../stores/use-connection-store", () => ({
6
+ useConnectionStore: (selector: any) => selector({ mode: "desktop" }),
7
+ }));
8
+
9
+ vi.mock("../../../stores/use-explorer-store", () => ({
10
+ useExplorerStore: (selector: any) => selector({ filePreview: null, loadingFile: false, closePreview: vi.fn() }),
11
+ }));
12
+
13
+ vi.mock("../../../stores/use-sidebar-store", () => ({
14
+ useSidebarStore: (selector: any) =>
15
+ selector({ activePanel: "explorer", isPinned: true, drawerOpen: false, breakpoint: "desktop", setDrawerOpen: vi.fn() }),
16
+ }));
17
+
18
+ vi.mock("../../activity-bar/ActivityBar", () => ({
19
+ ActivityBar: () => <div data-testid="activity-bar" />,
20
+ }));
21
+
22
+ vi.mock("../../activity-bar/MobileTabBar", () => ({
23
+ MobileTabBar: () => <div data-testid="mobile-tab-bar" />,
24
+ }));
25
+
26
+ vi.mock("../../explorer/ExplorerSidebar", () => ({
27
+ ExplorerSidebar: () => <div data-testid="explorer-sidebar" />,
28
+ }));
29
+
30
+ vi.mock("../../git/GitPanel", () => ({
31
+ GitPanel: () => <div data-testid="git-panel" />,
32
+ }));
33
+
34
+ vi.mock("../../search/SearchPanel", () => ({
35
+ SearchPanel: () => <div data-testid="search-panel" />,
36
+ }));
37
+
38
+ vi.mock("../../rules/RulesPanel", () => ({
39
+ RulesPanel: () => <div data-testid="rules-panel" />,
40
+ }));
41
+
42
+ vi.mock("../../chat/ChatPanel", () => ({
43
+ ChatPanel: () => <div data-testid="chat-panel" />,
44
+ }));
45
+
46
+ vi.mock("../../feed/FeedPanel", () => ({
47
+ FeedPanel: () => <div data-testid="feed-panel" />,
48
+ }));
49
+
50
+ vi.mock("../../bash/BashPanel", () => ({
51
+ BashPanel: () => <div data-testid="bash-panel" />,
52
+ }));
53
+
54
+ vi.mock("../../todo/TodoPanel", () => ({
55
+ TodoPanel: () => <div data-testid="todo-panel" />,
56
+ }));
57
+
58
+ vi.mock("../../file-preview/FilePreviewOverlay", () => ({
59
+ FilePreviewOverlay: () => <div data-testid="file-preview" />,
60
+ }));
61
+
62
+ vi.mock("../../diff/DiffViewerPanel", () => ({
63
+ DiffViewerPanel: () => <div data-testid="diff-panel" />,
64
+ }));
65
+
66
+ vi.mock("../../debug/DebugPanel", () => ({
67
+ DebugPanel: () => <div data-testid="debug-panel" />,
68
+ }));
69
+
70
+ vi.mock("../../common/ThemeToggle", () => ({
71
+ ThemeToggle: () => <div data-testid="theme-toggle" />,
72
+ }));
73
+
74
+ vi.mock("react-i18next", () => ({
75
+ useTranslation: () => ({ t: (k: string) => k }),
76
+ }));
77
+
78
+ describe("AppLayout", () => {
79
+ afterEach(cleanup);
80
+
81
+ it("should render center tab buttons", async () => {
82
+ const { AppLayout } = await import("../AppLayout");
83
+ render(
84
+ <AppLayout
85
+ centerTab="chat"
86
+ setCenterTab={vi.fn()}
87
+ sidebarWidth={240}
88
+ handleResizeStart={vi.fn()}
89
+ />,
90
+ );
91
+ expect(screen.getByText("tabs.chat")).toBeDefined();
92
+ expect(screen.getByText("tabs.feed")).toBeDefined();
93
+ expect(screen.getByText("tabs.bash")).toBeDefined();
94
+ expect(screen.getByText("tabs.todo")).toBeDefined();
95
+ });
96
+
97
+ it("should render ActivityBar on desktop", async () => {
98
+ const { AppLayout } = await import("../AppLayout");
99
+ render(
100
+ <AppLayout
101
+ centerTab="chat"
102
+ setCenterTab={vi.fn()}
103
+ sidebarWidth={240}
104
+ handleResizeStart={vi.fn()}
105
+ />,
106
+ );
107
+ expect(screen.getByTestId("activity-bar")).toBeDefined();
108
+ });
109
+
110
+ it("should render sidebar content when panel is active and pinned", async () => {
111
+ const { AppLayout } = await import("../AppLayout");
112
+ render(
113
+ <AppLayout
114
+ centerTab="chat"
115
+ setCenterTab={vi.fn()}
116
+ sidebarWidth={240}
117
+ handleResizeStart={vi.fn()}
118
+ />,
119
+ );
120
+ expect(screen.getByTestId("explorer-sidebar")).toBeDefined();
121
+ });
122
+
123
+ it("should call setCenterTab when tab is clicked", async () => {
124
+ const setCenterTab = vi.fn();
125
+ const { AppLayout } = await import("../AppLayout");
126
+ render(
127
+ <AppLayout
128
+ centerTab="chat"
129
+ setCenterTab={setCenterTab}
130
+ sidebarWidth={240}
131
+ handleResizeStart={vi.fn()}
132
+ />,
133
+ );
134
+ fireEvent.click(screen.getByText("tabs.feed"));
135
+ expect(setCenterTab).toHaveBeenCalledWith("feed");
136
+ });
137
+
138
+ it("should render mode indicator in title bar", async () => {
139
+ const { AppLayout } = await import("../AppLayout");
140
+ render(
141
+ <AppLayout
142
+ centerTab="chat"
143
+ setCenterTab={vi.fn()}
144
+ sidebarWidth={240}
145
+ handleResizeStart={vi.fn()}
146
+ />,
147
+ );
148
+ expect(screen.getByText("app.mode.desktop")).toBeDefined();
149
+ });
150
+
151
+ it("should render DebugPanel on desktop", async () => {
152
+ const { AppLayout } = await import("../AppLayout");
153
+ render(
154
+ <AppLayout
155
+ centerTab="chat"
156
+ setCenterTab={vi.fn()}
157
+ sidebarWidth={240}
158
+ handleResizeStart={vi.fn()}
159
+ />,
160
+ );
161
+ expect(screen.getByTestId("debug-panel")).toBeDefined();
162
+ });
163
+ });