@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,94 @@
1
+ interface CacheEntry<T> {
2
+ data: T;
3
+ timestamp: number;
4
+ }
5
+
6
+ class RpcCache {
7
+ private cache = new Map<string, CacheEntry<unknown>>();
8
+ private defaultTTL: number;
9
+ private pruneTimer: ReturnType<typeof setInterval> | null = null;
10
+
11
+ constructor(defaultTTL = 2000) {
12
+ this.defaultTTL = defaultTTL;
13
+ }
14
+
15
+ private key(method: string, params: unknown): string {
16
+ return `${method}:${JSON.stringify(params)}`;
17
+ }
18
+
19
+ get<T>(method: string, params: unknown, ttl?: number): T | null {
20
+ const k = this.key(method, params);
21
+ const entry = this.cache.get(k);
22
+ if (!entry) return null;
23
+
24
+ const effectiveTTL = ttl ?? this.defaultTTL;
25
+ if (Date.now() - entry.timestamp > effectiveTTL) {
26
+ this.cache.delete(k);
27
+ return null;
28
+ }
29
+
30
+ return entry.data as T;
31
+ }
32
+
33
+ set<T>(method: string, params: unknown, data: T): void {
34
+ const k = this.key(method, params);
35
+ this.cache.set(k, { data, timestamp: Date.now() });
36
+ if (!this.pruneTimer) this.startPruneTimer();
37
+ }
38
+
39
+ invalidate(method: string): void {
40
+ const prefix = `${method}:`;
41
+ for (const key of this.cache.keys()) {
42
+ if (key.startsWith(prefix)) {
43
+ this.cache.delete(key);
44
+ }
45
+ }
46
+ }
47
+
48
+ invalidateAll(methods: string[]): void {
49
+ for (const m of methods) {
50
+ this.invalidate(m);
51
+ }
52
+ }
53
+
54
+ clear(): void {
55
+ this.cache.clear();
56
+ if (this.pruneTimer) {
57
+ clearInterval(this.pruneTimer);
58
+ this.pruneTimer = null;
59
+ }
60
+ }
61
+
62
+ prune(): void {
63
+ const threshold = this.defaultTTL * 10;
64
+ const now = Date.now();
65
+ for (const [key, entry] of this.cache) {
66
+ if (now - entry.timestamp > threshold) {
67
+ this.cache.delete(key);
68
+ }
69
+ }
70
+ if (this.cache.size === 0 && this.pruneTimer) {
71
+ clearInterval(this.pruneTimer);
72
+ this.pruneTimer = null;
73
+ }
74
+ }
75
+
76
+ private startPruneTimer(): void {
77
+ if (this.pruneTimer) return;
78
+ this.pruneTimer = setInterval(() => this.prune(), this.defaultTTL * 5);
79
+ }
80
+ }
81
+
82
+ export const CACHEABLE_METHODS: Record<string, number> = {
83
+ "file.listDir": 2000,
84
+ "file.readFile": 3000,
85
+ "git.status": 2000,
86
+ "git.branches": 5000,
87
+ "git.log": 3000,
88
+ "git.diff": 3000,
89
+ "git.commitFiles": 3000,
90
+ "git.commitFileDiff": 3000,
91
+ "git.worktreeList": 5000,
92
+ };
93
+
94
+ export const rpcCache = new RpcCache(2000);
@@ -0,0 +1,24 @@
1
+ import "./lib/i18n";
2
+ import { StrictMode } from "react";
3
+ import { createRoot } from "react-dom/client";
4
+ import { apiClient } from "./lib/api-client";
5
+ import "./index.css";
6
+ import App from "./App";
7
+
8
+ const isElectrobun = typeof window !== "undefined" && !!window.__electrobunBunBridge;
9
+
10
+ if (isElectrobun) {
11
+ // 桌面端:同步初始化 IPC(Electrobun 要求桥接在页面加载时同步设置)
12
+ apiClient.initSyncForDesktop();
13
+ }
14
+ // Web 端 WS 初始化由 App.tsx 统一管理,避免竞争
15
+
16
+ if (import.meta.env.DEV) {
17
+ console.warn("[Main] Application starting, isElectrobun:", isElectrobun);
18
+ }
19
+
20
+ createRoot(document.getElementById("root")!).render(
21
+ <StrictMode>
22
+ <App />
23
+ </StrictMode>
24
+ );
@@ -0,0 +1,86 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+
3
+ vi.mock("../../lib/api-client", () => ({
4
+ apiClient: {
5
+ call: vi.fn().mockResolvedValue({ pong: true, timestamp: Date.now(), platform: "web" }),
6
+ subscribe: vi.fn().mockResolvedValue("sub-id"),
7
+ unsubscribe: vi.fn(),
8
+ },
9
+ }));
10
+
11
+ vi.mock("./use-log-store", () => ({
12
+ useLogStore: {
13
+ getState: () => ({ addLog: vi.fn() }),
14
+ },
15
+ }));
16
+
17
+ describe("useAppStore", () => {
18
+ beforeEach(async () => {
19
+ vi.resetModules();
20
+ });
21
+
22
+ it("should have initial state", async () => {
23
+ const { useAppStore } = await import("../use-app-store");
24
+ const state = useAppStore.getState();
25
+ expect(state.method).toBe("system.ping");
26
+ expect(state.result).toBeNull();
27
+ expect(state.tickEvents).toEqual([]);
28
+ expect(state.tickCount).toBe(0);
29
+ expect(state.subscriptionId).toBeNull();
30
+ expect(state.timerRunning).toBe(false);
31
+ });
32
+
33
+ it("should set method", async () => {
34
+ const { useAppStore } = await import("../use-app-store");
35
+ useAppStore.getState().setMethod("system.hello");
36
+ expect(useAppStore.getState().method).toBe("system.hello");
37
+ });
38
+
39
+ it("should callRPC and store result for system.ping", async () => {
40
+ const { useAppStore } = await import("../use-app-store");
41
+ await useAppStore.getState().callRPC("test");
42
+ expect(useAppStore.getState().result).toEqual(
43
+ expect.objectContaining({ pong: true }),
44
+ );
45
+ });
46
+
47
+ it("should handleSubscribe and start timer", async () => {
48
+ const { apiClient } = await import("../../lib/api-client");
49
+ const { useAppStore } = await import("../use-app-store");
50
+
51
+ await useAppStore.getState().handleSubscribe();
52
+ expect(apiClient.call).toHaveBeenCalledWith("timer.start", {});
53
+ expect(useAppStore.getState().timerRunning).toBe(true);
54
+ expect(apiClient.subscribe).toHaveBeenCalledWith(
55
+ "timer.tick",
56
+ expect.any(Function),
57
+ {},
58
+ );
59
+ expect(useAppStore.getState().subscriptionId).toBe("sub-id");
60
+ });
61
+
62
+ it("should handleUnsubscribe and stop timer", async () => {
63
+ const { apiClient } = await import("../../lib/api-client");
64
+ const { useAppStore } = await import("../use-app-store");
65
+
66
+ await useAppStore.getState().handleSubscribe();
67
+ await useAppStore.getState().handleUnsubscribe();
68
+
69
+ expect(apiClient.unsubscribe).toHaveBeenCalledWith("sub-id");
70
+ expect(useAppStore.getState().timerRunning).toBe(false);
71
+ expect(useAppStore.getState().subscriptionId).toBeNull();
72
+ });
73
+
74
+ it("should stop timer even without subscription", async () => {
75
+ const { apiClient } = await import("../../lib/api-client");
76
+ const { useAppStore } = await import("../use-app-store");
77
+
78
+ (apiClient.unsubscribe as ReturnType<typeof vi.fn>).mockClear();
79
+ (apiClient.call as ReturnType<typeof vi.fn>).mockClear();
80
+
81
+ await useAppStore.getState().handleUnsubscribe();
82
+ expect(apiClient.unsubscribe).not.toHaveBeenCalled();
83
+ expect(apiClient.call).toHaveBeenCalledWith("timer.stop", {});
84
+ expect(useAppStore.getState().timerRunning).toBe(false);
85
+ });
86
+ });
@@ -0,0 +1,99 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+
3
+ vi.mock("../../lib/api-client", () => ({
4
+ apiClient: { call: vi.fn() },
5
+ }));
6
+
7
+ vi.mock("../use-log-store", () => ({
8
+ useLogStore: { getState: () => ({ addLog: vi.fn() }) },
9
+ }));
10
+
11
+ describe("useBashStore", () => {
12
+ beforeEach(async () => {
13
+ vi.resetModules();
14
+ });
15
+
16
+ it("should have correct initial state", async () => {
17
+ const { useBashStore } = await import("../use-bash-store");
18
+ const state = useBashStore.getState();
19
+ expect(state.processes.size).toBe(0);
20
+ expect(state.activePid).toBeNull();
21
+ });
22
+
23
+ it("should add a process", async () => {
24
+ const { useBashStore } = await import("../use-bash-store");
25
+ useBashStore.getState().addProcess(1234, "ls -la");
26
+ const state = useBashStore.getState();
27
+ expect(state.processes.has(1234)).toBe(true);
28
+ expect(state.processes.get(1234)!.command).toBe("ls -la");
29
+ expect(state.processes.get(1234)!.running).toBe(true);
30
+ expect(state.processes.get(1234)!.output).toBe("");
31
+ expect(state.activePid).toBe(1234);
32
+ });
33
+
34
+ it("should update output", async () => {
35
+ const { useBashStore } = await import("../use-bash-store");
36
+ useBashStore.getState().addProcess(1234, "echo hello");
37
+ useBashStore.getState().updateOutput(1234, "hello\n");
38
+ expect(useBashStore.getState().processes.get(1234)!.output).toBe("hello\n");
39
+ });
40
+
41
+ it("should remove (stop) a process", async () => {
42
+ const { useBashStore } = await import("../use-bash-store");
43
+ useBashStore.getState().addProcess(1234, "ls");
44
+ useBashStore.getState().removeProcess(1234);
45
+ expect(useBashStore.getState().processes.get(1234)!.running).toBe(false);
46
+ });
47
+
48
+ it("should set active pid", async () => {
49
+ const { useBashStore } = await import("../use-bash-store");
50
+ useBashStore.getState().setActive(999);
51
+ expect(useBashStore.getState().activePid).toBe(999);
52
+ useBashStore.getState().setActive(null);
53
+ expect(useBashStore.getState().activePid).toBeNull();
54
+ });
55
+
56
+ it("should execute command via apiClient", async () => {
57
+ const { apiClient } = await import("../../lib/api-client");
58
+ const { useBashStore } = await import("../use-bash-store");
59
+ (apiClient.call as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
60
+ pid: 5678,
61
+ output: "hello world\n",
62
+ });
63
+
64
+ await useBashStore.getState().executeCommand("echo hello world", "/home");
65
+ expect(apiClient.call).toHaveBeenCalledWith("bash.execute", { command: "echo hello world", cwd: "/home" });
66
+ const state = useBashStore.getState();
67
+ expect(state.processes.has(5678)).toBe(true);
68
+ expect(state.processes.get(5678)!.output).toBe("hello world\n");
69
+ });
70
+
71
+ it("should kill process via apiClient", async () => {
72
+ const { apiClient } = await import("../../lib/api-client");
73
+ const { useBashStore } = await import("../use-bash-store");
74
+ useBashStore.getState().addProcess(1234, "sleep 100");
75
+ (apiClient.call as ReturnType<typeof vi.fn>).mockResolvedValueOnce(undefined);
76
+
77
+ await useBashStore.getState().killProcess(1234);
78
+ expect(apiClient.call).toHaveBeenCalledWith("bash.kill", { pid: 1234 });
79
+ expect(useBashStore.getState().processes.get(1234)!.running).toBe(false);
80
+ });
81
+
82
+ it("should fetch processes", async () => {
83
+ const { apiClient } = await import("../../lib/api-client");
84
+ const { useBashStore } = await import("../use-bash-store");
85
+ (apiClient.call as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
86
+ processes: [
87
+ { pid: 100, command: "ls", running: true },
88
+ { pid: 200, command: "pwd", running: false },
89
+ ],
90
+ });
91
+
92
+ await useBashStore.getState().fetchProcesses();
93
+ const state = useBashStore.getState();
94
+ expect(state.processes.size).toBe(2);
95
+ expect(state.processes.get(100)!.command).toBe("ls");
96
+ expect(state.processes.get(100)!.running).toBe(true);
97
+ expect(state.processes.get(200)!.running).toBe(false);
98
+ });
99
+ });
@@ -0,0 +1,26 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+
3
+ describe("useConnectionStore", () => {
4
+ beforeEach(async () => {
5
+ vi.resetModules();
6
+ });
7
+
8
+ it("should have initial state", async () => {
9
+ const { useConnectionStore } = await import("../use-connection-store");
10
+ const state = useConnectionStore.getState();
11
+ expect(state.mode).toBe("web");
12
+ expect(state.ready).toBe(false);
13
+ });
14
+
15
+ it("should set ready state", async () => {
16
+ const { useConnectionStore } = await import("../use-connection-store");
17
+ useConnectionStore.getState().setReady(true);
18
+ expect(useConnectionStore.getState().ready).toBe(true);
19
+ });
20
+
21
+ it("should set mode", async () => {
22
+ const { useConnectionStore } = await import("../use-connection-store");
23
+ useConnectionStore.getState().setMode("desktop");
24
+ expect(useConnectionStore.getState().mode).toBe("desktop");
25
+ });
26
+ });
@@ -0,0 +1,130 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+
3
+ vi.mock("../../lib/api-client", () => ({
4
+ apiClient: {
5
+ call: vi.fn(),
6
+ getBaseUrl: vi.fn(() => "http://localhost:3000"),
7
+ getAuthToken: vi.fn(() => "test-token"),
8
+ },
9
+ }));
10
+
11
+ vi.mock("../use-log-store", () => ({
12
+ useLogStore: { getState: () => ({ addLog: vi.fn() }) },
13
+ }));
14
+
15
+ vi.mock("../use-connection-store", () => ({
16
+ useConnectionStore: { getState: () => ({ mode: "desktop" }) },
17
+ }));
18
+
19
+ describe("useExplorerStore", () => {
20
+ beforeEach(async () => {
21
+ vi.resetModules();
22
+ });
23
+
24
+ it("should have correct initial state", async () => {
25
+ const { useExplorerStore } = await import("../use-explorer-store");
26
+ const state = useExplorerStore.getState();
27
+ expect(state.treeNodes).toEqual([]);
28
+ expect(state.currentPath).toBe("");
29
+ expect(state.selectedPath).toBeNull();
30
+ expect(state.filePreview).toBeNull();
31
+ expect(state.loadingFile).toBe(false);
32
+ expect(state.editingNode).toBeNull();
33
+ });
34
+
35
+ it("should set currentPath", async () => {
36
+ const { useExplorerStore } = await import("../use-explorer-store");
37
+ useExplorerStore.getState().setCurrentPath("/home/user");
38
+ expect(useExplorerStore.getState().currentPath).toBe("/home/user");
39
+ });
40
+
41
+ it("should list root directory", async () => {
42
+ const { apiClient } = await import("../../lib/api-client");
43
+ const { useExplorerStore } = await import("../use-explorer-store");
44
+ const mockEntries = [
45
+ { name: "src", path: "/src", type: "directory" as const, size: 0 },
46
+ { name: "readme.md", path: "/readme.md", type: "file" as const, size: 100 },
47
+ ];
48
+ (apiClient.call as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
49
+ entries: mockEntries,
50
+ basePath: "/root",
51
+ });
52
+
53
+ useExplorerStore.getState().setCurrentPath("/");
54
+ await useExplorerStore.getState().listRootDir();
55
+
56
+ const state = useExplorerStore.getState();
57
+ expect(state.treeNodes).toHaveLength(2);
58
+ expect(state.treeNodes[0].name).toBe("src");
59
+ expect(state.treeNodes[0].expanded).toBe(false);
60
+ expect(state.treeNodes[0].loaded).toBe(false);
61
+ expect(state.treeNodes[1].name).toBe("readme.md");
62
+ expect(state.currentPath).toBe("/root");
63
+ });
64
+
65
+ it("should toggle node to expand and load children", async () => {
66
+ const { apiClient } = await import("../../lib/api-client");
67
+ const { useExplorerStore } = await import("../use-explorer-store");
68
+
69
+ const rootNodes = [
70
+ { name: "src", path: "/src", type: "directory" as const, size: 0 },
71
+ ];
72
+ (apiClient.call as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
73
+ entries: rootNodes,
74
+ basePath: "/",
75
+ });
76
+ await useExplorerStore.getState().listRootDir();
77
+
78
+ const childEntries = [
79
+ { name: "index.ts", path: "/src/index.ts", type: "file" as const, size: 50 },
80
+ ];
81
+ (apiClient.call as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
82
+ entries: childEntries,
83
+ });
84
+
85
+ await useExplorerStore.getState().toggleNode("/src");
86
+
87
+ const state = useExplorerStore.getState();
88
+ expect(state.treeNodes[0].expanded).toBe(true);
89
+ expect(state.treeNodes[0].loaded).toBe(true);
90
+ expect(state.treeNodes[0].children).toHaveLength(1);
91
+ expect(state.treeNodes[0].children![0].name).toBe("index.ts");
92
+ });
93
+
94
+ it("should collapse an expanded node", async () => {
95
+ const { apiClient } = await import("../../lib/api-client");
96
+ const { useExplorerStore } = await import("../use-explorer-store");
97
+
98
+ (apiClient.call as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
99
+ entries: [{ name: "src", path: "/src", type: "directory" as const, size: 0 }],
100
+ basePath: "/",
101
+ });
102
+ await useExplorerStore.getState().listRootDir();
103
+
104
+ (apiClient.call as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
105
+ entries: [],
106
+ });
107
+ await useExplorerStore.getState().toggleNode("/src");
108
+ expect(useExplorerStore.getState().treeNodes[0].expanded).toBe(true);
109
+
110
+ await useExplorerStore.getState().toggleNode("/src");
111
+ expect(useExplorerStore.getState().treeNodes[0].expanded).toBe(false);
112
+ });
113
+
114
+ it("should start and cancel editing", async () => {
115
+ const { useExplorerStore } = await import("../use-explorer-store");
116
+ useExplorerStore.getState().startEditing("/src", "file");
117
+ expect(useExplorerStore.getState().editingNode).toEqual({ path: "/src", type: "file" });
118
+
119
+ useExplorerStore.getState().cancelEditing();
120
+ expect(useExplorerStore.getState().editingNode).toBeNull();
121
+ });
122
+
123
+ it("should close preview", async () => {
124
+ const { useExplorerStore } = await import("../use-explorer-store");
125
+ useExplorerStore.setState({ selectedPath: "/file.ts", filePreview: { path: "/file.ts", name: "file.ts" } as any });
126
+ useExplorerStore.getState().closePreview();
127
+ expect(useExplorerStore.getState().selectedPath).toBeNull();
128
+ expect(useExplorerStore.getState().filePreview).toBeNull();
129
+ });
130
+ });
@@ -0,0 +1,132 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+
3
+ vi.mock("../../lib/api-client", () => ({
4
+ apiClient: {
5
+ call: vi.fn(),
6
+ subscribe: vi.fn(() => "sub-id"),
7
+ unsubscribe: vi.fn(),
8
+ },
9
+ }));
10
+
11
+ describe("useFeedStore", () => {
12
+ beforeEach(async () => {
13
+ vi.resetModules();
14
+ });
15
+
16
+ it("should have correct initial state", async () => {
17
+ const { useFeedStore } = await import("../use-feed-store");
18
+ const state = useFeedStore.getState();
19
+ expect(state.posts).toEqual([]);
20
+ expect(state.author).toBe("alice");
21
+ expect(state.category).toBe("tech");
22
+ expect(state.content).toBe("");
23
+ expect(state.loading).toBe(false);
24
+ });
25
+
26
+ it("should set author, category, content", async () => {
27
+ const { useFeedStore } = await import("../use-feed-store");
28
+ const store = useFeedStore.getState();
29
+ store.setAuthor("bob");
30
+ store.setCategory("news");
31
+ store.setContent("hello world");
32
+ const state = useFeedStore.getState();
33
+ expect(state.author).toBe("bob");
34
+ expect(state.category).toBe("news");
35
+ expect(state.content).toBe("hello world");
36
+ });
37
+
38
+ it("should create post and clear content", async () => {
39
+ const { apiClient } = await import("../../lib/api-client");
40
+ const { useFeedStore } = await import("../use-feed-store");
41
+ (apiClient.call as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ id: "post-1" });
42
+ useFeedStore.getState().setContent(" new post ");
43
+
44
+ await useFeedStore.getState().createPost();
45
+ expect(apiClient.call).toHaveBeenCalledWith("feed.post", {
46
+ content: "new post",
47
+ category: "tech",
48
+ author: "alice",
49
+ });
50
+ expect(useFeedStore.getState().content).toBe("");
51
+ expect(useFeedStore.getState().loading).toBe(false);
52
+ });
53
+
54
+ it("should not create post with empty content", async () => {
55
+ const { apiClient } = await import("../../lib/api-client");
56
+ const { useFeedStore } = await import("../use-feed-store");
57
+
58
+ useFeedStore.setState({ content: " " });
59
+ (apiClient.call as ReturnType<typeof vi.fn>).mockClear();
60
+
61
+ await useFeedStore.getState().createPost();
62
+ expect(apiClient.call).not.toHaveBeenCalled();
63
+ });
64
+
65
+ it("should load posts", async () => {
66
+ const { apiClient } = await import("../../lib/api-client");
67
+ const { useFeedStore } = await import("../use-feed-store");
68
+ const posts = [
69
+ { id: "1", content: "hello", category: "tech", author: "alice", timestamp: Date.now() },
70
+ ];
71
+ (apiClient.call as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ posts });
72
+
73
+ await useFeedStore.getState().loadPosts();
74
+ expect(useFeedStore.getState().posts).toHaveLength(1);
75
+ });
76
+
77
+ it("should add a post locally", async () => {
78
+ const { useFeedStore } = await import("../use-feed-store");
79
+ const post = { id: "p1", content: "hi", category: "tech" as const, author: "alice", timestamp: Date.now() };
80
+ useFeedStore.getState().addPost(post);
81
+ expect(useFeedStore.getState().posts).toHaveLength(1);
82
+ expect(useFeedStore.getState().posts[0].id).toBe("p1");
83
+ });
84
+ });
85
+
86
+ describe("useEventStreamStore", () => {
87
+ beforeEach(async () => {
88
+ vi.resetModules();
89
+ });
90
+
91
+ it("should have correct initial state", async () => {
92
+ const { useEventStreamStore } = await import("../use-feed-store");
93
+ const state = useEventStreamStore.getState();
94
+ expect(state.entries).toEqual([]);
95
+ expect(state.activeEventType).toBe("feed.update");
96
+ expect(state.activeFilter).toBe("");
97
+ expect(state.subscriptionId).toBeNull();
98
+ expect(state.subscribed).toBe(false);
99
+ });
100
+
101
+ it("should set active event type and filter", async () => {
102
+ const { useEventStreamStore } = await import("../use-feed-store");
103
+ useEventStreamStore.getState().setActiveEventType("chat.message");
104
+ useEventStreamStore.getState().setActiveFilter('{"key":"val"}');
105
+ const state = useEventStreamStore.getState();
106
+ expect(state.activeEventType).toBe("chat.message");
107
+ expect(state.activeFilter).toBe('{"key":"val"}');
108
+ });
109
+
110
+ it("should subscribe and unsubscribe", async () => {
111
+ const { apiClient } = await import("../../lib/api-client");
112
+ const { useEventStreamStore } = await import("../use-feed-store");
113
+
114
+ await useEventStreamStore.getState().handleSubscribe();
115
+ expect(apiClient.subscribe).toHaveBeenCalled();
116
+ expect(useEventStreamStore.getState().subscribed).toBe(true);
117
+ expect(useEventStreamStore.getState().subscriptionId).toBe("sub-id");
118
+
119
+ useEventStreamStore.getState().handleUnsubscribe();
120
+ expect(apiClient.unsubscribe).toHaveBeenCalledWith("sub-id");
121
+ expect(useEventStreamStore.getState().subscribed).toBe(false);
122
+ expect(useEventStreamStore.getState().subscriptionId).toBeNull();
123
+ });
124
+
125
+ it("should add entries keeping max 50", async () => {
126
+ const { useEventStreamStore } = await import("../use-feed-store");
127
+ for (let i = 0; i < 55; i++) {
128
+ useEventStreamStore.getState().addEntry("feed.update", { i }, {});
129
+ }
130
+ expect(useEventStreamStore.getState().entries.length).toBeLessThanOrEqual(50);
131
+ });
132
+ });