@dyyz1993/create-agent 2.1.0 → 2.1.2

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 (349) hide show
  1. package/package.json +5 -4
  2. package/src/__tests__/cli.test.ts +2 -0
  3. package/src/__tests__/copy.test.ts +110 -122
  4. package/src/__tests__/create-args.test.ts +113 -0
  5. package/src/__tests__/templates.test.ts +29 -3
  6. package/src/__tests__/update.test.ts +129 -0
  7. package/src/cli.ts +30 -32
  8. package/src/commands/create.ts +83 -24
  9. package/src/commands/update.ts +174 -0
  10. package/src/commands/workspace.ts +26 -6
  11. package/src/lib/copy.ts +233 -93
  12. package/src/lib/templates.ts +12 -0
  13. package/templates/agent/.husky/commit-msg +4 -0
  14. package/templates/agent/.husky/pre-commit +11 -0
  15. package/templates/agent/.husky/pre-push +6 -0
  16. package/templates/agent/.prettierignore +6 -0
  17. package/templates/agent/.prettierrc +9 -0
  18. package/templates/agent/CHANGELOG.md +15 -0
  19. package/templates/agent/commitlint.config.js +8 -0
  20. package/templates/agent/package.json +19 -4
  21. package/templates/agent/src/gateway/__tests__/ws-handler-token.test.ts +53 -39
  22. package/templates/agent/src/mainview/App.tsx +14 -12
  23. package/templates/agent/src/mainview/__tests__/hooks/use-rpc-init.test.ts +194 -190
  24. package/templates/agent/src/mainview/components/bash/BashPanel.tsx +210 -189
  25. package/templates/agent/src/mainview/components/chat/ChatPanel.tsx +45 -20
  26. package/templates/agent/src/mainview/components/common/ThemeToggle.tsx +41 -21
  27. package/templates/agent/src/mainview/components/debug/DebugPanel.tsx +181 -114
  28. package/templates/agent/src/mainview/components/debug/__tests__/DebugPanel.test.tsx +80 -80
  29. package/templates/agent/src/mainview/components/diff/__tests__/DiffViewerPanel.test.tsx +61 -58
  30. package/templates/agent/src/mainview/components/feed/FeedPanel.tsx +43 -41
  31. package/templates/agent/src/mainview/components/file-preview/FilePreviewOverlay.tsx +69 -50
  32. package/templates/agent/src/mainview/components/file-preview/VirtualizedCodeView.tsx +31 -18
  33. package/templates/agent/src/mainview/components/git/GitPanel.tsx +731 -490
  34. package/templates/agent/src/mainview/components/git/__tests__/GitPanel.test.tsx +157 -139
  35. package/templates/agent/src/mainview/components/layout/AppLayout.tsx +210 -161
  36. package/templates/agent/src/mainview/components/layout/__tests__/AppLayout.test.tsx +130 -122
  37. package/templates/agent/src/mainview/components/rules/RulesPanel.tsx +119 -109
  38. package/templates/agent/src/mainview/components/search/SearchPanel.tsx +123 -117
  39. package/templates/agent/src/mainview/components/search/__tests__/SearchPanel.test.tsx +64 -44
  40. package/templates/agent/src/mainview/components/sidebar/__tests__/PinButton.test.tsx +38 -38
  41. package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +40 -38
  42. package/templates/agent/src/mainview/components/todo/__tests__/TodoPanel.test.tsx +72 -72
  43. package/templates/agent/src/mainview/lib/i18n/locales/en.json +175 -155
  44. package/templates/agent/src/mainview/lib/i18n/locales/zh.json +173 -155
  45. package/templates/agent/src/mainview/stores/use-app-store.ts +96 -86
  46. package/templates/agent/src/mainview/stores/use-explorer-store.ts +292 -275
  47. package/templates/agent/src/mainview/types/index.ts +23 -22
  48. package/templates/agent/src/mainview/utils/file-icon.tsx +17 -21
  49. package/templates/agent/src/shared/handlers/chat.ts +53 -53
  50. package/templates/agent/src/shared/handlers/debug.ts +13 -3
  51. package/templates/browser-agent/.env.example +19 -0
  52. package/templates/browser-agent/.husky/commit-msg +4 -0
  53. package/templates/browser-agent/.husky/pre-commit +11 -0
  54. package/templates/browser-agent/.husky/pre-push +6 -0
  55. package/templates/browser-agent/.prettierignore +6 -0
  56. package/templates/browser-agent/.prettierrc +9 -0
  57. package/templates/browser-agent/AGENTS.md +396 -0
  58. package/templates/browser-agent/CHANGELOG.md +15 -0
  59. package/templates/browser-agent/LICENSE +21 -0
  60. package/templates/browser-agent/README.md +103 -0
  61. package/templates/browser-agent/commitlint.config.js +8 -0
  62. package/templates/browser-agent/electrobun.config.ts +27 -0
  63. package/templates/browser-agent/electron/main.js +46 -0
  64. package/templates/browser-agent/electron/preload.js +5 -0
  65. package/templates/browser-agent/electron-builder.json +40 -0
  66. package/templates/browser-agent/eslint.config.mjs +79 -0
  67. package/templates/browser-agent/llms.txt +24 -0
  68. package/templates/browser-agent/package.json +140 -0
  69. package/templates/browser-agent/postcss.config.js +6 -0
  70. package/templates/browser-agent/scripts/dev.ts +138 -0
  71. package/templates/browser-agent/src/__tests__/hybrid-mode.test.ts +126 -0
  72. package/templates/browser-agent/src/__tests__/server-config-security.test.ts +55 -0
  73. package/templates/browser-agent/src/__tests__/server-config.test.ts +59 -0
  74. package/templates/browser-agent/src/bun/index.ts +130 -0
  75. package/templates/browser-agent/src/bun/three.d.ts +1 -0
  76. package/templates/browser-agent/src/gateway/http-routes.ts +1 -0
  77. package/templates/browser-agent/src/gateway/ipc-transport.ts +68 -0
  78. package/templates/browser-agent/src/gateway/sse-transport.ts +221 -0
  79. package/templates/browser-agent/src/mainview/App.tsx +45 -0
  80. package/templates/browser-agent/src/mainview/__tests__/hooks/use-rpc-init.test.ts +202 -0
  81. package/templates/browser-agent/src/mainview/__tests__/hooks/use-sidebar-resize.test.ts +127 -0
  82. package/templates/browser-agent/src/mainview/__tests__/i18n/i18n.test.ts +49 -0
  83. package/templates/browser-agent/src/mainview/__tests__/setup.ts +40 -0
  84. package/templates/browser-agent/src/mainview/__tests__/stores/use-chat-store.test.ts +73 -0
  85. package/templates/browser-agent/src/mainview/__tests__/stores/use-sidebar-store.test.ts +61 -0
  86. package/templates/browser-agent/src/mainview/__tests__/theme-variables.test.ts +36 -0
  87. package/templates/browser-agent/src/mainview/components/assets/AssetsPanel.tsx +139 -0
  88. package/templates/browser-agent/src/mainview/components/chat/ChatPanel.tsx +219 -0
  89. package/templates/browser-agent/src/mainview/components/chat/CommandBar.tsx +184 -0
  90. package/templates/browser-agent/src/mainview/components/chat/MessageBubble.tsx +249 -0
  91. package/templates/browser-agent/src/mainview/components/chat/ToolPicker.tsx +115 -0
  92. package/templates/browser-agent/src/mainview/components/common/ErrorBoundary.tsx +1 -0
  93. package/templates/browser-agent/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
  94. package/templates/browser-agent/src/mainview/components/common/ThemeToggle.tsx +45 -0
  95. package/templates/browser-agent/src/mainview/components/common/__tests__/ErrorBoundary.test.tsx +74 -0
  96. package/templates/browser-agent/src/mainview/components/common/__tests__/LanguageSwitcher.test.tsx +44 -0
  97. package/templates/browser-agent/src/mainview/components/common/__tests__/ThemeToggle.test.tsx +41 -0
  98. package/templates/browser-agent/src/mainview/components/dev/NetworkPanel.tsx +155 -0
  99. package/templates/browser-agent/src/mainview/components/layout/AppLayout.tsx +311 -0
  100. package/templates/browser-agent/src/mainview/components/onboarding/SetupWizard.tsx +310 -0
  101. package/templates/browser-agent/src/mainview/components/process/ProcessPanel.tsx +329 -0
  102. package/templates/browser-agent/src/mainview/components/sidebar/SessionSidebar.tsx +122 -0
  103. package/templates/browser-agent/src/mainview/components/sidebar/SkillSidebar.tsx +115 -0
  104. package/templates/browser-agent/src/mainview/components/topbar/TopBar.tsx +350 -0
  105. package/templates/browser-agent/src/mainview/global.d.ts +13 -0
  106. package/templates/browser-agent/src/mainview/hooks/__tests__/use-breakpoint.test.ts +151 -0
  107. package/templates/browser-agent/src/mainview/hooks/use-agent-chat.ts +157 -0
  108. package/templates/browser-agent/src/mainview/hooks/use-breakpoint.ts +68 -0
  109. package/templates/browser-agent/src/mainview/hooks/use-rpc-init.ts +71 -0
  110. package/templates/browser-agent/src/mainview/hooks/use-sidebar-resize.ts +40 -0
  111. package/templates/browser-agent/src/mainview/index.css +71 -0
  112. package/templates/browser-agent/src/mainview/index.html +12 -0
  113. package/templates/browser-agent/src/mainview/lib/api-client.ts +397 -0
  114. package/templates/browser-agent/src/mainview/lib/i18n/index.ts +30 -0
  115. package/templates/browser-agent/src/mainview/lib/i18n/locales/en.json +130 -0
  116. package/templates/browser-agent/src/mainview/lib/i18n/locales/zh.json +128 -0
  117. package/templates/browser-agent/src/mainview/lib/network-bus.ts +92 -0
  118. package/templates/browser-agent/src/mainview/lib/rpc-cache.ts +94 -0
  119. package/templates/browser-agent/src/mainview/main.tsx +18 -0
  120. package/templates/browser-agent/src/mainview/stores/__tests__/use-connection-store.test.ts +26 -0
  121. package/templates/browser-agent/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
  122. package/templates/browser-agent/src/mainview/stores/__tests__/use-log-store.test.ts +28 -0
  123. package/templates/browser-agent/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
  124. package/templates/browser-agent/src/mainview/stores/message-batcher.ts +25 -0
  125. package/templates/browser-agent/src/mainview/stores/use-asset-store.ts +65 -0
  126. package/templates/browser-agent/src/mainview/stores/use-chat-store.ts +200 -0
  127. package/templates/browser-agent/src/mainview/stores/use-connection-store.ts +173 -0
  128. package/templates/browser-agent/src/mainview/stores/use-locale-store.ts +27 -0
  129. package/templates/browser-agent/src/mainview/stores/use-log-store.ts +16 -0
  130. package/templates/browser-agent/src/mainview/stores/use-network-panel-store.ts +18 -0
  131. package/templates/browser-agent/src/mainview/stores/use-record-store.ts +147 -0
  132. package/templates/browser-agent/src/mainview/stores/use-session-store.ts +151 -0
  133. package/templates/browser-agent/src/mainview/stores/use-sidebar-store.ts +161 -0
  134. package/templates/browser-agent/src/mainview/stores/use-theme-store.ts +46 -0
  135. package/templates/browser-agent/src/mainview/stores/use-view-store.ts +20 -0
  136. package/templates/browser-agent/src/mainview/types/index.ts +6 -0
  137. package/templates/browser-agent/src/server-config.ts +45 -0
  138. package/templates/browser-agent/src/server.ts +78 -0
  139. package/templates/browser-agent/src/shared/handlers/__tests__/chat-concurrency.test.ts +127 -0
  140. package/templates/browser-agent/src/shared/handlers/__tests__/chat-handler.test.ts +77 -0
  141. package/templates/browser-agent/src/shared/handlers/__tests__/file-handler.test.ts +100 -0
  142. package/templates/browser-agent/src/shared/handlers/__tests__/system-handler.test.ts +55 -0
  143. package/templates/browser-agent/src/shared/handlers/__tests__/timer-handler.test.ts +77 -0
  144. package/templates/browser-agent/src/shared/handlers/browser.ts +596 -0
  145. package/templates/browser-agent/src/shared/handlers/chat.ts +213 -0
  146. package/templates/browser-agent/src/shared/handlers/file.ts +99 -0
  147. package/templates/browser-agent/src/shared/handlers/index.ts +7 -0
  148. package/templates/browser-agent/src/shared/handlers/session.ts +167 -0
  149. package/templates/browser-agent/src/shared/handlers/system.ts +27 -0
  150. package/templates/browser-agent/src/shared/handlers/timer.ts +34 -0
  151. package/templates/browser-agent/src/shared/http-routes.ts +437 -0
  152. package/templates/browser-agent/src/shared/lib/__tests__/logger.test.ts +92 -0
  153. package/templates/browser-agent/src/shared/lib/__tests__/path-security.test.ts +77 -0
  154. package/templates/browser-agent/src/shared/lib/__tests__/web-server.test.ts +203 -0
  155. package/templates/browser-agent/src/shared/lib/agent.ts +384 -0
  156. package/templates/browser-agent/src/shared/lib/cdp.ts +448 -0
  157. package/templates/browser-agent/src/shared/lib/generate.ts +111 -0
  158. package/templates/browser-agent/src/shared/lib/logger.ts +152 -0
  159. package/templates/browser-agent/src/shared/lib/mock-stream.ts +147 -0
  160. package/templates/browser-agent/src/shared/lib/path-security.ts +38 -0
  161. package/templates/browser-agent/src/shared/lib/port-registry.ts +103 -0
  162. package/templates/browser-agent/src/shared/lib/web-server.ts +127 -0
  163. package/templates/browser-agent/src/shared/lib/xhs-extract.ts +71 -0
  164. package/templates/browser-agent/src/shared/modules/browser.ts +86 -0
  165. package/templates/browser-agent/src/shared/modules/chat.ts +20 -0
  166. package/templates/browser-agent/src/shared/modules/file.ts +40 -0
  167. package/templates/browser-agent/src/shared/modules/session.ts +55 -0
  168. package/templates/browser-agent/src/shared/modules/system.ts +8 -0
  169. package/templates/browser-agent/src/shared/modules/timer.ts +11 -0
  170. package/templates/browser-agent/src/shared/register-all-handlers.ts +36 -0
  171. package/templates/browser-agent/src/shared/rpc-schema.ts +34 -0
  172. package/templates/browser-agent/tailwind.config.js +15 -0
  173. package/templates/browser-agent/tsconfig.ipc.json +14 -0
  174. package/templates/browser-agent/tsconfig.json +36 -0
  175. package/templates/browser-agent/vite.config.ts +3 -0
  176. package/templates/browser-agent/vitest.config.ts +3 -0
  177. package/templates/chat/.husky/commit-msg +4 -0
  178. package/templates/chat/.husky/pre-commit +11 -0
  179. package/templates/chat/.husky/pre-push +6 -0
  180. package/templates/chat/CHANGELOG.md +15 -0
  181. package/templates/chat/commitlint.config.js +8 -0
  182. package/templates/chat/package.json +19 -4
  183. package/templates/chat/src/mainview/__tests__/hooks/use-input-history.test.ts +125 -0
  184. package/templates/chat/src/mainview/__tests__/setup.ts +32 -29
  185. package/templates/chat/src/mainview/components/chat/ChatPanel.tsx +80 -55
  186. package/templates/chat/src/mainview/components/common/ThemeToggle.tsx +40 -20
  187. package/templates/chat/src/mainview/lib/i18n/locales/en.json +175 -155
  188. package/templates/chat/src/mainview/lib/i18n/locales/zh.json +173 -155
  189. package/templates/chat/src/shared/handlers/chat.ts +17 -19
  190. package/templates/chat/src/shared/handlers/debug.ts +12 -2
  191. package/templates/cowork/.env.example +8 -0
  192. package/templates/cowork/.prettierignore +6 -0
  193. package/templates/cowork/.prettierrc +9 -0
  194. package/templates/cowork/AGENTS.md +236 -0
  195. package/templates/cowork/CHANGELOG.md +15 -0
  196. package/templates/cowork/LICENSE +21 -0
  197. package/templates/cowork/README.md +103 -0
  198. package/templates/cowork/commitlint.config.js +8 -0
  199. package/templates/cowork/docs/CODE-VIEW-PLAN.md +637 -0
  200. package/templates/cowork/docs/CODE-VIEW-V2.md +196 -0
  201. package/templates/cowork/docs/CODE-VIEW-V4.md +150 -0
  202. package/templates/cowork/electrobun.config.ts +27 -0
  203. package/templates/cowork/eslint.config.mjs +79 -0
  204. package/templates/cowork/llms.txt +24 -0
  205. package/templates/cowork/package.json +87 -0
  206. package/templates/cowork/postcss.config.js +6 -0
  207. package/templates/cowork/scripts/dev.ts +138 -0
  208. package/templates/cowork/src/__tests__/hybrid-mode.test.ts +126 -0
  209. package/templates/cowork/src/__tests__/server-config-security.test.ts +55 -0
  210. package/templates/cowork/src/__tests__/server-config.test.ts +59 -0
  211. package/templates/cowork/src/bun/index.ts +130 -0
  212. package/templates/cowork/src/bun/three.d.ts +1 -0
  213. package/templates/cowork/src/gateway/http-routes.ts +1 -0
  214. package/templates/cowork/src/gateway/ipc-transport.ts +68 -0
  215. package/templates/cowork/src/gateway/sse-transport.ts +231 -0
  216. package/templates/cowork/src/mainview/App.tsx +45 -0
  217. package/templates/cowork/src/mainview/__tests__/hooks/use-rpc-init.test.ts +202 -0
  218. package/templates/cowork/src/mainview/__tests__/hooks/use-sidebar-resize.test.ts +127 -0
  219. package/templates/cowork/src/mainview/__tests__/i18n/i18n.test.ts +49 -0
  220. package/templates/cowork/src/mainview/__tests__/setup.ts +40 -0
  221. package/templates/cowork/src/mainview/__tests__/stores/use-chat-store.test.ts +73 -0
  222. package/templates/cowork/src/mainview/__tests__/stores/use-sidebar-store.test.ts +61 -0
  223. package/templates/cowork/src/mainview/__tests__/theme-variables.test.ts +36 -0
  224. package/templates/cowork/src/mainview/components/chat/TaskChat.tsx +311 -0
  225. package/templates/cowork/src/mainview/components/chat/__tests__/localhost-links.test.ts +76 -0
  226. package/templates/cowork/src/mainview/components/common/ErrorBoundary.tsx +1 -0
  227. package/templates/cowork/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
  228. package/templates/cowork/src/mainview/components/common/ThemeToggle.tsx +45 -0
  229. package/templates/cowork/src/mainview/components/common/__tests__/ErrorBoundary.test.tsx +74 -0
  230. package/templates/cowork/src/mainview/components/common/__tests__/LanguageSwitcher.test.tsx +44 -0
  231. package/templates/cowork/src/mainview/components/common/__tests__/ThemeToggle.test.tsx +41 -0
  232. package/templates/cowork/src/mainview/components/dev/NetworkPanel.tsx +155 -0
  233. package/templates/cowork/src/mainview/components/layout/AppLayout.tsx +216 -0
  234. package/templates/cowork/src/mainview/components/right/ArtifactsPanel.tsx +54 -0
  235. package/templates/cowork/src/mainview/components/right/ContextPanel.tsx +133 -0
  236. package/templates/cowork/src/mainview/components/right/ElementPicker.tsx +206 -0
  237. package/templates/cowork/src/mainview/components/right/PreviewBlock.tsx +155 -0
  238. package/templates/cowork/src/mainview/components/right/ProgressPanel.tsx +85 -0
  239. package/templates/cowork/src/mainview/components/sidebar/TaskSidebar.tsx +211 -0
  240. package/templates/cowork/src/mainview/components/topbar/TopBar.tsx +86 -0
  241. package/templates/cowork/src/mainview/global.d.ts +13 -0
  242. package/templates/cowork/src/mainview/hooks/__tests__/use-breakpoint.test.ts +123 -0
  243. package/templates/cowork/src/mainview/hooks/use-breakpoint.ts +53 -0
  244. package/templates/cowork/src/mainview/hooks/use-rpc-init.ts +26 -0
  245. package/templates/cowork/src/mainview/hooks/use-sidebar-resize.ts +40 -0
  246. package/templates/cowork/src/mainview/index.css +89 -0
  247. package/templates/cowork/src/mainview/index.html +12 -0
  248. package/templates/cowork/src/mainview/lib/api-client.ts +404 -0
  249. package/templates/cowork/src/mainview/lib/i18n/index.ts +30 -0
  250. package/templates/cowork/src/mainview/lib/i18n/locales/en.json +130 -0
  251. package/templates/cowork/src/mainview/lib/i18n/locales/zh.json +128 -0
  252. package/templates/cowork/src/mainview/lib/network-bus.ts +92 -0
  253. package/templates/cowork/src/mainview/lib/rpc-cache.ts +94 -0
  254. package/templates/cowork/src/mainview/main.tsx +18 -0
  255. package/templates/cowork/src/mainview/stores/__tests__/use-connection-store.test.ts +26 -0
  256. package/templates/cowork/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
  257. package/templates/cowork/src/mainview/stores/__tests__/use-log-store.test.ts +28 -0
  258. package/templates/cowork/src/mainview/stores/__tests__/use-preview-store.test.ts +193 -0
  259. package/templates/cowork/src/mainview/stores/__tests__/use-sidebar-store.test.ts +81 -0
  260. package/templates/cowork/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
  261. package/templates/cowork/src/mainview/stores/message-batcher.ts +25 -0
  262. package/templates/cowork/src/mainview/stores/use-chat-store.ts +198 -0
  263. package/templates/cowork/src/mainview/stores/use-connection-store.ts +168 -0
  264. package/templates/cowork/src/mainview/stores/use-context-store.ts +68 -0
  265. package/templates/cowork/src/mainview/stores/use-locale-store.ts +27 -0
  266. package/templates/cowork/src/mainview/stores/use-log-store.ts +16 -0
  267. package/templates/cowork/src/mainview/stores/use-network-panel-store.ts +18 -0
  268. package/templates/cowork/src/mainview/stores/use-output-store.ts +47 -0
  269. package/templates/cowork/src/mainview/stores/use-preview-store.ts +97 -0
  270. package/templates/cowork/src/mainview/stores/use-sidebar-store.ts +272 -0
  271. package/templates/cowork/src/mainview/stores/use-task-store.ts +86 -0
  272. package/templates/cowork/src/mainview/stores/use-theme-store.ts +46 -0
  273. package/templates/cowork/src/mainview/stores/use-view-store.ts +29 -0
  274. package/templates/cowork/src/mainview/types/index.ts +6 -0
  275. package/templates/cowork/src/server-config.ts +44 -0
  276. package/templates/cowork/src/server.ts +78 -0
  277. package/templates/cowork/src/shared/handlers/__tests__/chat-concurrency.test.ts +127 -0
  278. package/templates/cowork/src/shared/handlers/__tests__/chat-handler.test.ts +77 -0
  279. package/templates/cowork/src/shared/handlers/__tests__/file-handler.test.ts +100 -0
  280. package/templates/cowork/src/shared/handlers/__tests__/preview-handler.test.ts +172 -0
  281. package/templates/cowork/src/shared/handlers/__tests__/system-handler.test.ts +55 -0
  282. package/templates/cowork/src/shared/handlers/__tests__/timer-handler.test.ts +77 -0
  283. package/templates/cowork/src/shared/handlers/chat.ts +213 -0
  284. package/templates/cowork/src/shared/handlers/context.ts +72 -0
  285. package/templates/cowork/src/shared/handlers/file.ts +99 -0
  286. package/templates/cowork/src/shared/handlers/index.ts +9 -0
  287. package/templates/cowork/src/shared/handlers/output.ts +59 -0
  288. package/templates/cowork/src/shared/handlers/preview.ts +81 -0
  289. package/templates/cowork/src/shared/handlers/system.ts +27 -0
  290. package/templates/cowork/src/shared/handlers/task.ts +101 -0
  291. package/templates/cowork/src/shared/handlers/timer.ts +34 -0
  292. package/templates/cowork/src/shared/http-routes.ts +444 -0
  293. package/templates/cowork/src/shared/lib/__tests__/logger.test.ts +92 -0
  294. package/templates/cowork/src/shared/lib/__tests__/path-security.test.ts +77 -0
  295. package/templates/cowork/src/shared/lib/__tests__/web-server.test.ts +203 -0
  296. package/templates/cowork/src/shared/lib/logger.ts +152 -0
  297. package/templates/cowork/src/shared/lib/path-security.ts +38 -0
  298. package/templates/cowork/src/shared/lib/port-registry.ts +103 -0
  299. package/templates/cowork/src/shared/lib/web-server.ts +127 -0
  300. package/templates/cowork/src/shared/modules/chat.ts +20 -0
  301. package/templates/cowork/src/shared/modules/context.ts +32 -0
  302. package/templates/cowork/src/shared/modules/file.ts +40 -0
  303. package/templates/cowork/src/shared/modules/output.ts +20 -0
  304. package/templates/cowork/src/shared/modules/preview.ts +44 -0
  305. package/templates/cowork/src/shared/modules/system.ts +8 -0
  306. package/templates/cowork/src/shared/modules/task.ts +31 -0
  307. package/templates/cowork/src/shared/modules/timer.ts +11 -0
  308. package/templates/cowork/src/shared/register-all-handlers.ts +36 -0
  309. package/templates/cowork/src/shared/rpc-schema.ts +38 -0
  310. package/templates/cowork/tailwind.config.js +15 -0
  311. package/templates/cowork/test-upload.txt +0 -0
  312. package/templates/cowork/tsconfig.ipc.json +14 -0
  313. package/templates/cowork/tsconfig.json +36 -0
  314. package/templates/cowork/vite.config.ts +3 -0
  315. package/templates/cowork/vitest.config.ts +3 -0
  316. package/templates/general/.husky/commit-msg +4 -0
  317. package/templates/general/.husky/pre-commit +11 -0
  318. package/templates/general/.husky/pre-push +6 -0
  319. package/templates/general/CHANGELOG.md +15 -0
  320. package/templates/general/commitlint.config.js +8 -0
  321. package/templates/general/package.json +19 -4
  322. package/templates/general/src/mainview/__tests__/hooks/use-input-history.test.ts +125 -0
  323. package/templates/general/src/mainview/__tests__/setup.ts +32 -29
  324. package/templates/general/src/mainview/components/chat/ChatPanel.tsx +80 -55
  325. package/templates/general/src/mainview/components/common/ThemeToggle.tsx +40 -20
  326. package/templates/general/src/mainview/components/debug/DebugPanel.tsx +170 -156
  327. package/templates/general/src/mainview/components/debug/__tests__/DebugPanel.test.tsx +169 -0
  328. package/templates/general/src/mainview/components/explorer/ConfirmDialog.tsx +42 -42
  329. package/templates/general/src/mainview/components/explorer/ContextMenu.tsx +72 -67
  330. package/templates/general/src/mainview/components/feed/FeedPanel.tsx +7 -5
  331. package/templates/general/src/mainview/components/file-preview/FilePreviewOverlay.tsx +64 -45
  332. package/templates/general/src/mainview/components/file-preview/VirtualizedCodeView.tsx +24 -7
  333. package/templates/general/src/mainview/components/git/GitPanel.tsx +700 -473
  334. package/templates/general/src/mainview/components/layout/AppLayout.tsx +123 -114
  335. package/templates/general/src/mainview/components/search/SearchPanel.tsx +15 -19
  336. package/templates/general/src/mainview/lib/i18n/locales/en.json +175 -155
  337. package/templates/general/src/mainview/lib/i18n/locales/zh.json +173 -155
  338. package/templates/general/src/mainview/stores/__tests__/use-explorer-store.test.ts +259 -0
  339. package/templates/general/src/mainview/stores/__tests__/use-feed-store.test.ts +160 -0
  340. package/templates/general/src/mainview/stores/__tests__/use-git-store.test.ts +313 -0
  341. package/templates/general/src/mainview/stores/__tests__/use-notification-store.test.ts +115 -0
  342. package/templates/general/src/mainview/stores/__tests__/use-sidebar-store.test.ts +120 -0
  343. package/templates/general/src/mainview/stores/use-explorer-store.ts +277 -260
  344. package/templates/general/src/mainview/types/index.ts +21 -20
  345. package/templates/general/src/shared/handlers/chat.ts +1 -1
  346. package/templates/general/src/shared/handlers/debug.ts +12 -2
  347. package/templates/shared/components/ErrorBoundary.tsx +0 -1
  348. package/templates/shared/vite-base.config.ts +8 -7
  349. /package/templates/{shared → browser-agent}/test-upload.txt +0 -0
@@ -1,4 +1,4 @@
1
- import { useState, useCallback, useRef, useEffect } from "react";
1
+ import { useState, useCallback, useRef, useEffect } from 'react';
2
2
  import {
3
3
  Search,
4
4
  CaseSensitive,
@@ -9,10 +9,11 @@ import {
9
9
  ChevronDown,
10
10
  Loader2,
11
11
  X,
12
- } from "lucide-react";
13
- import { apiClient } from "../../lib/api-client";
14
- import { useExplorerStore } from "../../stores/use-explorer-store";
15
- import type { TreeNode } from "../../types";
12
+ } from 'lucide-react';
13
+ import { apiClient } from '../../lib/api-client';
14
+ import { useExplorerStore } from '../../stores/use-explorer-store';
15
+ import { useTranslation } from 'react-i18next';
16
+ import type { TreeNode } from '../../types';
16
17
 
17
18
  interface SearchMatch {
18
19
  file: string;
@@ -31,79 +32,79 @@ interface FileGroup {
31
32
  }
32
33
 
33
34
  const SKIP_DIRS = new Set([
34
- "node_modules",
35
- ".git",
36
- "dist",
37
- "build",
38
- ".next",
39
- ".nuxt",
40
- "coverage",
41
- ".cache",
42
- ".turbo",
43
- ".vercel",
44
- "out",
45
- ".output",
35
+ 'node_modules',
36
+ '.git',
37
+ 'dist',
38
+ 'build',
39
+ '.next',
40
+ '.nuxt',
41
+ 'coverage',
42
+ '.cache',
43
+ '.turbo',
44
+ '.vercel',
45
+ 'out',
46
+ '.output',
46
47
  ]);
47
48
 
48
49
  const TEXT_EXTENSIONS = new Set([
49
- "ts",
50
- "tsx",
51
- "js",
52
- "jsx",
53
- "mjs",
54
- "cjs",
55
- "json",
56
- "md",
57
- "mdx",
58
- "yaml",
59
- "yml",
60
- "toml",
61
- "css",
62
- "scss",
63
- "less",
64
- "sass",
65
- "html",
66
- "htm",
67
- "xml",
68
- "svg",
69
- "py",
70
- "rb",
71
- "go",
72
- "rs",
73
- "java",
74
- "kt",
75
- "swift",
76
- "c",
77
- "cpp",
78
- "h",
79
- "hpp",
80
- "sh",
81
- "bash",
82
- "zsh",
83
- "fish",
84
- "sql",
85
- "graphql",
86
- "gql",
87
- "txt",
88
- "csv",
89
- "env",
90
- "gitignore",
91
- "eslintrc",
92
- "prettierrc",
93
- "conf",
94
- "ini",
95
- "cfg",
96
- "log",
97
- "vue",
98
- "svelte",
50
+ 'ts',
51
+ 'tsx',
52
+ 'js',
53
+ 'jsx',
54
+ 'mjs',
55
+ 'cjs',
56
+ 'json',
57
+ 'md',
58
+ 'mdx',
59
+ 'yaml',
60
+ 'yml',
61
+ 'toml',
62
+ 'css',
63
+ 'scss',
64
+ 'less',
65
+ 'sass',
66
+ 'html',
67
+ 'htm',
68
+ 'xml',
69
+ 'svg',
70
+ 'py',
71
+ 'rb',
72
+ 'go',
73
+ 'rs',
74
+ 'java',
75
+ 'kt',
76
+ 'swift',
77
+ 'c',
78
+ 'cpp',
79
+ 'h',
80
+ 'hpp',
81
+ 'sh',
82
+ 'bash',
83
+ 'zsh',
84
+ 'fish',
85
+ 'sql',
86
+ 'graphql',
87
+ 'gql',
88
+ 'txt',
89
+ 'csv',
90
+ 'env',
91
+ 'gitignore',
92
+ 'eslintrc',
93
+ 'prettierrc',
94
+ 'conf',
95
+ 'ini',
96
+ 'cfg',
97
+ 'log',
98
+ 'vue',
99
+ 'svelte',
99
100
  ]);
100
101
 
101
102
  function isTextFile(name: string): boolean {
102
- const dotIdx = name.lastIndexOf(".");
103
+ const dotIdx = name.lastIndexOf('.');
103
104
  if (dotIdx === -1) return false;
104
105
  const ext = name.slice(dotIdx + 1).toLowerCase();
105
106
  if (TEXT_EXTENSIONS.has(ext)) return true;
106
- if (name.startsWith(".") && TEXT_EXTENSIONS.has(name.slice(1))) return true;
107
+ if (name.startsWith('.') && TEXT_EXTENSIONS.has(name.slice(1))) return true;
107
108
  return false;
108
109
  }
109
110
 
@@ -111,15 +112,16 @@ function buildSearchRegex(
111
112
  query: string,
112
113
  caseSensitive: boolean,
113
114
  wholeWord: boolean,
114
- useRegex: boolean
115
+ useRegex: boolean,
115
116
  ): RegExp {
116
- let pattern = useRegex ? query : query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
117
+ let pattern = useRegex ? query : query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
117
118
  if (wholeWord) pattern = `\\b${pattern}\\b`;
118
- return new RegExp(pattern, caseSensitive ? "g" : "gi");
119
+ return new RegExp(pattern, caseSensitive ? 'g' : 'gi');
119
120
  }
120
121
 
121
122
  export function SearchPanel() {
122
- const [query, setQuery] = useState("");
123
+ const { t } = useTranslation();
124
+ const [query, setQuery] = useState('');
123
125
  const [caseSensitive, setCaseSensitive] = useState(false);
124
126
  const [wholeWord, setWholeWord] = useState(false);
125
127
  const [useRegex, setUseRegex] = useState(false);
@@ -144,12 +146,12 @@ export function SearchPanel() {
144
146
  while (queue.length > 0 && !abortRef.current) {
145
147
  const current = queue.shift()!;
146
148
  try {
147
- const res = await apiClient.call("file.listDir", { path: current });
149
+ const res = await apiClient.call('file.listDir', { path: current });
148
150
  for (const entry of res.entries) {
149
151
  if (abortRef.current) break;
150
- if (entry.name.startsWith(".") && entry.name !== ".env" && entry.name !== ".env.local")
152
+ if (entry.name.startsWith('.') && entry.name !== '.env' && entry.name !== '.env.local')
151
153
  continue;
152
- if (entry.type === "directory") {
154
+ if (entry.type === 'directory') {
153
155
  if (!SKIP_DIRS.has(entry.name)) queue.push(entry.path);
154
156
  } else if (isTextFile(entry.name)) {
155
157
  files.push(entry.path);
@@ -180,8 +182,8 @@ export function SearchPanel() {
180
182
  for (const fp of filePaths) {
181
183
  if (abortRef.current) break;
182
184
  try {
183
- const res = await apiClient.call("file.readFile", { path: fp });
184
- const lines = res.content.split("\n");
185
+ const res = await apiClient.call('file.readFile', { path: fp });
186
+ const lines = res.content.split('\n');
185
187
  for (let i = 0; i < lines.length; i++) {
186
188
  const line = lines[i]!;
187
189
  regex.lastIndex = 0;
@@ -206,7 +208,7 @@ export function SearchPanel() {
206
208
 
207
209
  setFilesSearched(filePaths.length);
208
210
 
209
- const rootPath = currentPath.endsWith("/") ? currentPath : currentPath + "/";
211
+ const rootPath = currentPath.endsWith('/') ? currentPath : currentPath + '/';
210
212
  const groups: FileGroup[] = [];
211
213
  for (const [filePath, matches] of matchMap) {
212
214
  groups.push({
@@ -232,32 +234,32 @@ export function SearchPanel() {
232
234
 
233
235
  const handleKeyDown = useCallback(
234
236
  (e: React.KeyboardEvent) => {
235
- if (e.key === "Enter" && !isSearching) {
237
+ if (e.key === 'Enter' && !isSearching) {
236
238
  performSearch();
237
239
  }
238
- if (e.key === "Escape") {
239
- setQuery("");
240
+ if (e.key === 'Escape') {
241
+ setQuery('');
240
242
  inputRef.current?.focus();
241
243
  }
242
244
  },
243
- [performSearch, isSearching]
245
+ [performSearch, isSearching],
244
246
  );
245
247
 
246
248
  const toggleGroup = useCallback((index: number) => {
247
249
  setResults((prev) => prev.map((g, i) => (i === index ? { ...g, expanded: !g.expanded } : g)));
248
250
  }, []);
249
251
 
250
- const handleOpenFile = useCallback((filePath: string, _line: number) => {
252
+ const handleOpenFile = useCallback((filePath: string, line: number) => {
251
253
  const node: TreeNode = {
252
- name: filePath.split("/").pop() || filePath,
254
+ name: filePath.split('/').pop() || filePath,
253
255
  path: filePath,
254
- type: "file",
256
+ type: 'file',
255
257
  };
256
- useExplorerStore.getState().openFile(node);
258
+ useExplorerStore.getState().openFileAtLine(node, line);
257
259
  }, []);
258
260
 
259
261
  const modKey =
260
- typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent) ? "" : "Ctrl";
262
+ typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.userAgent) ? '' : 'Ctrl';
261
263
 
262
264
  return (
263
265
  <div className="flex flex-col h-full bg-[var(--color-bg-primary)]">
@@ -265,7 +267,7 @@ export function SearchPanel() {
265
267
  <div className="px-3 py-2 text-xs font-semibold text-[var(--color-text-tertiary)] uppercase tracking-wide border-b border-[var(--color-border-primary)] flex items-center justify-between flex-shrink-0">
266
268
  <div className="flex items-center gap-1.5">
267
269
  <Search className="w-3.5 h-3.5" />
268
- Search
270
+ {t('sidebar.search')}
269
271
  </div>
270
272
  <kbd className="px-1.5 py-0.5 text-[10px] text-[var(--color-text-placeholder)] bg-[var(--color-bg-secondary)] border border-[var(--color-border-primary)] rounded font-mono">
271
273
  {modKey}⇧F
@@ -282,13 +284,13 @@ export function SearchPanel() {
282
284
  value={query}
283
285
  onChange={(e) => setQuery(e.target.value)}
284
286
  onKeyDown={handleKeyDown}
285
- placeholder="Search in files..."
287
+ placeholder={t('sidebar.search') + '...'}
286
288
  className="w-full pl-7 pr-7 py-1.5 text-xs bg-[var(--color-bg-secondary)] rounded text-[var(--color-text-primary)] border border-[var(--color-border-secondary)] focus:border-[var(--color-accent)] focus:outline-none"
287
289
  />
288
290
  {query && (
289
291
  <button
290
292
  onClick={() => {
291
- setQuery("");
293
+ setQuery('');
292
294
  inputRef.current?.focus();
293
295
  }}
294
296
  className="absolute right-2 top-1/2 -translate-y-1/2 text-[var(--color-text-placeholder)] hover:text-[var(--color-text-secondary)]"
@@ -304,8 +306,8 @@ export function SearchPanel() {
304
306
  onClick={() => setCaseSensitive((v) => !v)}
305
307
  className={`p-1 rounded text-xs transition-colors ${
306
308
  caseSensitive
307
- ? "bg-[var(--color-accent)]/30 text-[var(--color-text-accent)] border border-[var(--color-accent)]/50"
308
- : "text-[var(--color-text-placeholder)] hover:text-[var(--color-text-secondary)] border border-transparent"
309
+ ? 'bg-[var(--color-accent)]/30 text-[var(--color-text-accent)] border border-[var(--color-accent)]/50'
310
+ : 'text-[var(--color-text-placeholder)] hover:text-[var(--color-text-secondary)] border border-transparent'
309
311
  }`}
310
312
  title="Match Case"
311
313
  >
@@ -315,8 +317,8 @@ export function SearchPanel() {
315
317
  onClick={() => setWholeWord((v) => !v)}
316
318
  className={`p-1 rounded text-xs transition-colors ${
317
319
  wholeWord
318
- ? "bg-[var(--color-accent)]/30 text-[var(--color-text-accent)] border border-[var(--color-accent)]/50"
319
- : "text-[var(--color-text-placeholder)] hover:text-[var(--color-text-secondary)] border border-transparent"
320
+ ? 'bg-[var(--color-accent)]/30 text-[var(--color-text-accent)] border border-[var(--color-accent)]/50'
321
+ : 'text-[var(--color-text-placeholder)] hover:text-[var(--color-text-secondary)] border border-transparent'
320
322
  }`}
321
323
  title="Match Whole Word"
322
324
  >
@@ -326,8 +328,8 @@ export function SearchPanel() {
326
328
  onClick={() => setUseRegex((v) => !v)}
327
329
  className={`p-1 rounded text-xs transition-colors ${
328
330
  useRegex
329
- ? "bg-[var(--color-accent)]/30 text-[var(--color-text-accent)] border border-[var(--color-accent)]/50"
330
- : "text-[var(--color-text-placeholder)] hover:text-[var(--color-text-secondary)] border border-transparent"
331
+ ? 'bg-[var(--color-accent)]/30 text-[var(--color-text-accent)] border border-[var(--color-accent)]/50'
332
+ : 'text-[var(--color-text-placeholder)] hover:text-[var(--color-text-secondary)] border border-transparent'
331
333
  }`}
332
334
  title="Use Regular Expression"
333
335
  >
@@ -339,10 +341,10 @@ export function SearchPanel() {
339
341
  {isSearching ? (
340
342
  <button
341
343
  onClick={cancelSearch}
342
- className="px-2 py-1 text-xs text-[var(--color-text-error)] hover:text-red-300 transition-colors flex items-center gap-1"
344
+ className="px-2 py-1 text-xs text-[var(--color-text-error)] hover:text-[var(--color-text-error)] transition-colors flex items-center gap-1"
343
345
  >
344
346
  <Loader2 className="w-3 h-3 animate-spin" />
345
- Cancel
347
+ {t('common.cancel')}
346
348
  </button>
347
349
  ) : (
348
350
  query.trim() && (
@@ -350,7 +352,7 @@ export function SearchPanel() {
350
352
  onClick={performSearch}
351
353
  className="px-2 py-1 text-xs bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] rounded transition-colors"
352
354
  >
353
- Search
355
+ {t('sidebar.search')}
354
356
  </button>
355
357
  )
356
358
  )}
@@ -362,15 +364,11 @@ export function SearchPanel() {
362
364
  <div className="px-3 py-1.5 text-[11px] text-[var(--color-text-placeholder)] border-b border-[var(--color-bg-secondary)] flex-shrink-0">
363
365
  {totalMatches > 0 ? (
364
366
  <>
365
- <span className="text-[var(--color-text-accent)] font-medium">{totalMatches}</span>{" "}
366
- result{totalMatches !== 1 && "s"} in{" "}
367
- <span className="text-[var(--color-text-accent)] font-medium">{results.length}</span>{" "}
368
- file
369
- {results.length !== 1 && "s"}
370
- <span className="ml-2 text-gray-600">({filesSearched} files searched)</span>
367
+ {t('search.resultsIn', { count: totalMatches, fileCount: results.length })}
368
+ <span className="ml-2">{t('search.filesSearched', { count: filesSearched })}</span>
371
369
  </>
372
370
  ) : (
373
- "No results found"
371
+ t('search.noResults')
374
372
  )}
375
373
  </div>
376
374
  )}
@@ -379,23 +377,31 @@ export function SearchPanel() {
379
377
  {isSearching && (
380
378
  <div className="flex items-center justify-center py-8 text-[var(--color-text-placeholder)] text-sm flex-shrink-0">
381
379
  <Loader2 className="w-4 h-4 animate-spin mr-2" />
382
- Searching...
380
+ {t('common.loading')}
383
381
  </div>
384
382
  )}
385
383
 
386
384
  {/* Results list */}
387
385
  <div className="flex-1 overflow-y-auto">
388
386
  {!hasSearched && !isSearching ? (
389
- <div className="flex flex-col items-center justify-center h-full text-gray-600 text-xs px-4">
390
- <Search className="w-8 h-8 mb-2 text-gray-700" />
391
- <p>Type a search term and press Enter</p>
392
- <p className="mt-1 text-gray-700">{modKey}+Shift+F to focus search</p>
387
+ <div className="flex flex-col items-center justify-center h-full text-[var(--color-text-tertiary)] text-xs px-4">
388
+ <Search className="w-8 h-8 mb-2 text-[var(--color-text-placeholder)]" />
389
+ <p>{t('search.typeToSearch')}</p>
390
+ <p className="mt-1 text-[var(--color-text-placeholder)]">
391
+ {t('search.focusShortcut', { key: modKey })}
392
+ </p>
393
393
  </div>
394
394
  ) : !isSearching && totalMatches === 0 ? (
395
- <div className="flex flex-col items-center justify-center h-full text-gray-600 text-xs px-4">
396
- <FileText className="w-8 h-8 mb-2 text-gray-700" />
395
+ <div className="flex flex-col items-center justify-center h-full text-[var(--color-text-tertiary)] text-xs px-4">
396
+ <FileText className="w-8 h-8 mb-2 text-[var(--color-text-placeholder)]" />
397
+ <p>{t('search.noResultsFor', { query })}</p>
398
+ <p className="mt-1 text-[var(--color-text-placeholder)]">{t('search.tryDifferent')}</p>
399
+ </div>
400
+ ) : !isSearching && totalMatches === 0 ? (
401
+ <div className="flex flex-col items-center justify-center h-full text-[var(--color-text-tertiary)] text-xs px-4">
402
+ <FileText className="w-8 h-8 mb-2 text-[var(--color-text-placeholder)]" />
397
403
  <p>No results for "{query}"</p>
398
- <p className="mt-1 text-gray-700">Try different search terms</p>
404
+ <p className="mt-1 text-[var(--color-text-placeholder)]">Try different search terms</p>
399
405
  </div>
400
406
  ) : (
401
407
  <div className="py-1">
@@ -415,7 +421,7 @@ export function SearchPanel() {
415
421
  <span className="text-[var(--color-text-secondary)] truncate flex-1">
416
422
  {group.relativePath}
417
423
  </span>
418
- <span className="text-[10px] text-gray-600 flex-shrink-0 ml-1">
424
+ <span className="text-[10px] text-[var(--color-text-tertiary)] flex-shrink-0 ml-1">
419
425
  {group.matches.length}
420
426
  </span>
421
427
  </button>
@@ -429,7 +435,7 @@ export function SearchPanel() {
429
435
  onClick={() => handleOpenFile(match.file, match.line)}
430
436
  className="w-full text-left px-2 py-0.5 text-[11px] hover:bg-[var(--color-bg-secondary)] transition-colors group flex items-start gap-2"
431
437
  >
432
- <span className="text-gray-600 w-6 text-right flex-shrink-0 select-none pt-px">
438
+ <span className="text-[var(--color-text-tertiary)] w-6 text-right flex-shrink-0 select-none pt-px">
433
439
  {match.line}
434
440
  </span>
435
441
  <span className="text-[var(--color-text-tertiary)] truncate font-mono leading-relaxed">
@@ -1,50 +1,70 @@
1
- import { describe, it, expect, vi, afterEach } from "vitest";
2
- import { render, screen, cleanup } from "@testing-library/react";
3
- import React from "react";
1
+ import { describe, it, expect, vi, afterEach } from 'vitest';
2
+ import { render, screen, cleanup } from '@testing-library/react';
3
+ import React from 'react';
4
4
 
5
- vi.mock("../../../stores/use-explorer-store", () => ({
6
- useExplorerStore: (selector: any) =>
7
- selector({ currentPath: "/project" }),
5
+ vi.mock('../../../stores/use-explorer-store', () => ({
6
+ useExplorerStore: (selector: any) => selector({ currentPath: '/project' }),
8
7
  }));
9
8
 
10
- vi.mock("../../../lib/api-client", () => ({
11
- apiClient: { call: vi.fn() },
9
+ vi.mock('../../../lib/api-client', () => ({
10
+ apiClient: { call: vi.fn() },
12
11
  }));
13
12
 
14
- describe("SearchPanel", () => {
15
- afterEach(cleanup);
16
-
17
- it("should render search header", async () => {
18
- const { SearchPanel } = await import("../SearchPanel");
19
- render(<SearchPanel />);
20
- expect(screen.getByText("Search")).toBeDefined();
21
- });
22
-
23
- it("should render search input", async () => {
24
- const { SearchPanel } = await import("../SearchPanel");
25
- render(<SearchPanel />);
26
- expect(screen.getByPlaceholderText("Search in files...")).toBeDefined();
27
- });
28
-
29
- it("should render keyboard shortcut hint", async () => {
30
- const { SearchPanel } = await import("../SearchPanel");
31
- render(<SearchPanel />);
32
- const kbd = document.querySelector("kbd");
33
- expect(kbd).not.toBeNull();
34
- expect(kbd?.textContent).toMatch(/[⌘Ctrl]/);
35
- });
36
-
37
- it("should render toggle buttons for case/word/regex", async () => {
38
- const { SearchPanel } = await import("../SearchPanel");
39
- render(<SearchPanel />);
40
- expect(screen.getByTitle("Match Case")).toBeDefined();
41
- expect(screen.getByTitle("Match Whole Word")).toBeDefined();
42
- expect(screen.getByTitle("Use Regular Expression")).toBeDefined();
43
- });
44
-
45
- it("should show initial empty state before search", async () => {
46
- const { SearchPanel } = await import("../SearchPanel");
47
- render(<SearchPanel />);
48
- expect(screen.getByText("Type a search term and press Enter")).toBeDefined();
49
- });
13
+ vi.mock('react-i18next', () => ({
14
+ useTranslation: () => ({
15
+ t: (key: string, params?: Record<string, unknown>) => {
16
+ const map: Record<string, string> = {
17
+ 'sidebar.search': 'Search',
18
+ 'search.placeholder': 'Search in files...',
19
+ 'search.typeToSearch': 'Type a search term and press Enter',
20
+ 'search.focusShortcut': `...${params?.key || 'Ctrl'}+F to focus search`,
21
+ 'search.noResults': 'No results found',
22
+ 'search.filesSearched': `(${params?.count || 0} files searched)`,
23
+ 'search.resultsIn': `${params?.count || 0} result in ${params?.fileCount || 0} files`,
24
+ 'search.matchCase': 'Match Case',
25
+ 'search.matchWholeWord': 'Match Whole Word',
26
+ 'search.useRegex': 'Use Regular Expression',
27
+ };
28
+ return map[key] || key;
29
+ },
30
+ i18n: { language: 'en' },
31
+ }),
32
+ }));
33
+
34
+ describe('SearchPanel', () => {
35
+ afterEach(cleanup);
36
+
37
+ it('should render search header', async () => {
38
+ const { SearchPanel } = await import('../SearchPanel');
39
+ render(<SearchPanel />);
40
+ expect(screen.getByText('Search')).toBeDefined();
41
+ });
42
+
43
+ it('should render search input', async () => {
44
+ const { SearchPanel } = await import('../SearchPanel');
45
+ render(<SearchPanel />);
46
+ expect(screen.getByPlaceholderText('Search...')).toBeDefined();
47
+ });
48
+
49
+ it('should render keyboard shortcut hint', async () => {
50
+ const { SearchPanel } = await import('../SearchPanel');
51
+ render(<SearchPanel />);
52
+ const kbd = document.querySelector('kbd');
53
+ expect(kbd).not.toBeNull();
54
+ expect(kbd?.textContent).toMatch(/[⌘Ctrl]/);
55
+ });
56
+
57
+ it('should render toggle buttons for case/word/regex', async () => {
58
+ const { SearchPanel } = await import('../SearchPanel');
59
+ render(<SearchPanel />);
60
+ expect(screen.getByTitle('Match Case')).toBeDefined();
61
+ expect(screen.getByTitle('Match Whole Word')).toBeDefined();
62
+ expect(screen.getByTitle('Use Regular Expression')).toBeDefined();
63
+ });
64
+
65
+ it('should show initial empty state before search', async () => {
66
+ const { SearchPanel } = await import('../SearchPanel');
67
+ render(<SearchPanel />);
68
+ expect(screen.getByText('Type a search term and press Enter')).toBeDefined();
69
+ });
50
70
  });
@@ -1,48 +1,48 @@
1
- import { describe, it, expect, vi, afterEach } from "vitest";
2
- import { render, screen, cleanup, fireEvent } from "@testing-library/react";
3
- import React from "react";
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
4
 
5
5
  const mockSetPinned = vi.fn();
6
6
 
7
- vi.mock("../../../stores/use-sidebar-store", () => ({
8
- useSidebarStore: (selector: any) =>
9
- selector({ isPinned: true, setPinned: mockSetPinned, breakpoint: "desktop" }),
7
+ vi.mock('../../../stores/use-sidebar-store', () => ({
8
+ useSidebarStore: <T,>(selector: (s: Record<string, unknown>) => T) =>
9
+ selector({ isPinned: true, setPinned: mockSetPinned, breakpoint: 'desktop' }),
10
10
  }));
11
11
 
12
- describe("PinButton", () => {
13
- afterEach(() => {
14
- cleanup();
15
- vi.clearAllMocks();
16
- });
12
+ describe('PinButton', () => {
13
+ afterEach(() => {
14
+ cleanup();
15
+ vi.clearAllMocks();
16
+ });
17
17
 
18
- it("should render a button", async () => {
19
- const { PinButton } = await import("../PinButton");
20
- render(<PinButton />);
21
- expect(screen.getByRole("button")).toBeDefined();
22
- });
18
+ it('should render a button', async () => {
19
+ const { PinButton } = await import('../PinButton');
20
+ render(<PinButton />);
21
+ expect(screen.getByRole('button')).toBeDefined();
22
+ });
23
23
 
24
- it("should show unpin title when pinned", async () => {
25
- const { PinButton } = await import("../PinButton");
26
- render(<PinButton />);
27
- expect(screen.getByTitle("Unpin sidebar")).toBeDefined();
28
- });
24
+ it('should show unpin title when pinned', async () => {
25
+ const { PinButton } = await import('../PinButton');
26
+ render(<PinButton />);
27
+ expect(screen.getByTitle('Unpin sidebar')).toBeDefined();
28
+ });
29
29
 
30
- it("should call setPinned on click", async () => {
31
- const { PinButton } = await import("../PinButton");
32
- render(<PinButton />);
33
- fireEvent.click(screen.getByRole("button"));
34
- expect(mockSetPinned).toHaveBeenCalledWith(false);
35
- });
30
+ it('should call setPinned on click', async () => {
31
+ const { PinButton } = await import('../PinButton');
32
+ render(<PinButton />);
33
+ fireEvent.click(screen.getByRole('button'));
34
+ expect(mockSetPinned).toHaveBeenCalledWith(false);
35
+ });
36
36
 
37
- it("should not render on mobile", async () => {
38
- vi.resetModules();
39
- vi.doMock("../../../stores/use-sidebar-store", () => ({
40
- useSidebarStore: (selector: any) =>
41
- selector({ isPinned: false, setPinned: vi.fn(), breakpoint: "mobile" }),
42
- }));
43
- const mod = await import("../PinButton");
44
- const { container } = render(<mod.PinButton />);
45
- expect(container.innerHTML).toBe("");
46
- vi.resetModules();
47
- });
37
+ it('should not render on mobile', async () => {
38
+ vi.resetModules();
39
+ vi.doMock('../../../stores/use-sidebar-store', () => ({
40
+ useSidebarStore: <T,>(selector: (s: Record<string, unknown>) => T) =>
41
+ selector({ isPinned: false, setPinned: vi.fn(), breakpoint: 'mobile' }),
42
+ }));
43
+ const mod = await import('../PinButton');
44
+ const { container } = render(<mod.PinButton />);
45
+ expect(container.innerHTML).toBe('');
46
+ vi.resetModules();
47
+ });
48
48
  });