@akiojin/gwt 4.10.0 → 4.11.6

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 (579) hide show
  1. package/README.ja.md +4 -4
  2. package/README.md +4 -4
  3. package/dist/claude.d.ts +1 -0
  4. package/dist/claude.d.ts.map +1 -1
  5. package/dist/claude.js +52 -49
  6. package/dist/claude.js.map +1 -1
  7. package/dist/cli/ui/App.solid.d.ts +29 -0
  8. package/dist/cli/ui/App.solid.d.ts.map +1 -0
  9. package/dist/cli/ui/App.solid.js +1197 -0
  10. package/dist/cli/ui/App.solid.js.map +1 -0
  11. package/dist/cli/ui/components/solid/Footer.d.ts +10 -0
  12. package/dist/cli/ui/components/solid/Footer.d.ts.map +1 -0
  13. package/dist/cli/ui/components/solid/Footer.js +10 -0
  14. package/dist/cli/ui/components/solid/Footer.js.map +1 -0
  15. package/dist/cli/ui/components/{parts → solid}/Header.d.ts +1 -6
  16. package/dist/cli/ui/components/solid/Header.d.ts.map +1 -0
  17. package/dist/cli/ui/components/solid/Header.js +13 -0
  18. package/dist/cli/ui/components/solid/Header.js.map +1 -0
  19. package/dist/cli/ui/components/solid/HelpOverlay.d.ts +8 -0
  20. package/dist/cli/ui/components/solid/HelpOverlay.d.ts.map +1 -0
  21. package/dist/cli/ui/components/solid/HelpOverlay.js +118 -0
  22. package/dist/cli/ui/components/solid/HelpOverlay.js.map +1 -0
  23. package/dist/cli/ui/components/solid/QuickStartStep.d.ts +17 -0
  24. package/dist/cli/ui/components/solid/QuickStartStep.d.ts.map +1 -0
  25. package/dist/cli/ui/components/solid/QuickStartStep.js +139 -0
  26. package/dist/cli/ui/components/solid/QuickStartStep.js.map +1 -0
  27. package/dist/cli/ui/components/solid/SelectInput.d.ts +22 -0
  28. package/dist/cli/ui/components/solid/SelectInput.d.ts.map +1 -0
  29. package/dist/cli/ui/components/solid/SelectInput.js +44 -0
  30. package/dist/cli/ui/components/solid/SelectInput.js.map +1 -0
  31. package/dist/cli/ui/components/solid/TextInput.d.ts +12 -0
  32. package/dist/cli/ui/components/solid/TextInput.d.ts.map +1 -0
  33. package/dist/cli/ui/components/solid/TextInput.js +9 -0
  34. package/dist/cli/ui/components/solid/TextInput.js.map +1 -0
  35. package/dist/cli/ui/components/solid/WizardController.d.ts +34 -0
  36. package/dist/cli/ui/components/solid/WizardController.d.ts.map +1 -0
  37. package/dist/cli/ui/components/solid/WizardController.js +215 -0
  38. package/dist/cli/ui/components/solid/WizardController.js.map +1 -0
  39. package/dist/cli/ui/components/solid/WizardPopup.d.ts +26 -0
  40. package/dist/cli/ui/components/solid/WizardPopup.d.ts.map +1 -0
  41. package/dist/cli/ui/components/solid/WizardPopup.js +68 -0
  42. package/dist/cli/ui/components/solid/WizardPopup.js.map +1 -0
  43. package/dist/cli/ui/components/solid/WizardSteps.d.ts +52 -0
  44. package/dist/cli/ui/components/solid/WizardSteps.d.ts.map +1 -0
  45. package/dist/cli/ui/components/solid/WizardSteps.js +462 -0
  46. package/dist/cli/ui/components/solid/WizardSteps.js.map +1 -0
  47. package/dist/cli/ui/core/index.d.ts +12 -0
  48. package/dist/cli/ui/core/index.d.ts.map +1 -0
  49. package/dist/cli/ui/core/index.js +15 -0
  50. package/dist/cli/ui/core/index.js.map +1 -0
  51. package/dist/cli/ui/core/keybindings.d.ts +106 -0
  52. package/dist/cli/ui/core/keybindings.d.ts.map +1 -0
  53. package/dist/cli/ui/core/keybindings.js +270 -0
  54. package/dist/cli/ui/core/keybindings.js.map +1 -0
  55. package/dist/cli/ui/core/theme.d.ts +114 -0
  56. package/dist/cli/ui/core/theme.d.ts.map +1 -0
  57. package/dist/cli/ui/core/theme.js +170 -0
  58. package/dist/cli/ui/core/theme.js.map +1 -0
  59. package/dist/cli/ui/core/types.d.ts +156 -0
  60. package/dist/cli/ui/core/types.d.ts.map +1 -0
  61. package/dist/cli/ui/core/types.js +10 -0
  62. package/dist/cli/ui/core/types.js.map +1 -0
  63. package/dist/cli/ui/hooks/solid/useScrollableList.d.ts +26 -0
  64. package/dist/cli/ui/hooks/solid/useScrollableList.d.ts.map +1 -0
  65. package/dist/cli/ui/hooks/solid/useScrollableList.js +89 -0
  66. package/dist/cli/ui/hooks/solid/useScrollableList.js.map +1 -0
  67. package/dist/cli/ui/hooks/solid/useTerminalSize.d.ts +10 -0
  68. package/dist/cli/ui/hooks/solid/useTerminalSize.d.ts.map +1 -0
  69. package/dist/cli/ui/hooks/solid/useTerminalSize.js +16 -0
  70. package/dist/cli/ui/hooks/solid/useTerminalSize.js.map +1 -0
  71. package/dist/cli/ui/index.solid.d.ts +5 -0
  72. package/dist/cli/ui/index.solid.d.ts.map +1 -0
  73. package/dist/cli/ui/index.solid.js +21 -0
  74. package/dist/cli/ui/index.solid.js.map +1 -0
  75. package/dist/cli/ui/{components/screens → screens/solid}/BranchListScreen.d.ts +8 -22
  76. package/dist/cli/ui/screens/solid/BranchListScreen.d.ts.map +1 -0
  77. package/dist/cli/ui/screens/solid/BranchListScreen.js +756 -0
  78. package/dist/cli/ui/screens/solid/BranchListScreen.js.map +1 -0
  79. package/dist/cli/ui/screens/solid/ConfirmScreen.d.ts +10 -0
  80. package/dist/cli/ui/screens/solid/ConfirmScreen.d.ts.map +1 -0
  81. package/dist/cli/ui/screens/solid/ConfirmScreen.js +37 -0
  82. package/dist/cli/ui/screens/solid/ConfirmScreen.js.map +1 -0
  83. package/dist/cli/ui/screens/solid/EnvironmentScreen.d.ts +14 -0
  84. package/dist/cli/ui/screens/solid/EnvironmentScreen.d.ts.map +1 -0
  85. package/dist/cli/ui/screens/solid/EnvironmentScreen.js +91 -0
  86. package/dist/cli/ui/screens/solid/EnvironmentScreen.js.map +1 -0
  87. package/dist/cli/ui/screens/solid/ErrorScreen.d.ts +8 -0
  88. package/dist/cli/ui/screens/solid/ErrorScreen.d.ts.map +1 -0
  89. package/dist/cli/ui/screens/solid/ErrorScreen.js +18 -0
  90. package/dist/cli/ui/screens/solid/ErrorScreen.js.map +1 -0
  91. package/dist/cli/ui/screens/solid/InputScreen.d.ts +13 -0
  92. package/dist/cli/ui/screens/solid/InputScreen.d.ts.map +1 -0
  93. package/dist/cli/ui/screens/solid/InputScreen.js +17 -0
  94. package/dist/cli/ui/screens/solid/InputScreen.js.map +1 -0
  95. package/dist/cli/ui/screens/solid/LoadingIndicator.d.ts +9 -0
  96. package/dist/cli/ui/screens/solid/LoadingIndicator.d.ts.map +1 -0
  97. package/dist/cli/ui/screens/solid/LoadingIndicator.js +43 -0
  98. package/dist/cli/ui/screens/solid/LoadingIndicator.js.map +1 -0
  99. package/dist/cli/ui/{components/screens → screens/solid}/LogDetailScreen.d.ts +2 -2
  100. package/dist/cli/ui/screens/solid/LogDetailScreen.d.ts.map +1 -0
  101. package/dist/cli/ui/screens/solid/LogDetailScreen.js +34 -0
  102. package/dist/cli/ui/screens/solid/LogDetailScreen.js.map +1 -0
  103. package/dist/cli/ui/{components/screens/LogListScreen.d.ts → screens/solid/LogScreen.d.ts} +4 -4
  104. package/dist/cli/ui/screens/solid/LogScreen.d.ts.map +1 -0
  105. package/dist/cli/ui/screens/solid/LogScreen.js +95 -0
  106. package/dist/cli/ui/screens/solid/LogScreen.js.map +1 -0
  107. package/dist/cli/ui/screens/solid/ProfileEnvScreen.d.ts +17 -0
  108. package/dist/cli/ui/screens/solid/ProfileEnvScreen.d.ts.map +1 -0
  109. package/dist/cli/ui/screens/solid/ProfileEnvScreen.js +112 -0
  110. package/dist/cli/ui/screens/solid/ProfileEnvScreen.js.map +1 -0
  111. package/dist/cli/ui/screens/solid/ProfileScreen.d.ts +17 -0
  112. package/dist/cli/ui/screens/solid/ProfileScreen.d.ts.map +1 -0
  113. package/dist/cli/ui/screens/solid/ProfileScreen.js +50 -0
  114. package/dist/cli/ui/screens/solid/ProfileScreen.js.map +1 -0
  115. package/dist/cli/ui/screens/solid/SelectorScreen.d.ts +20 -0
  116. package/dist/cli/ui/screens/solid/SelectorScreen.d.ts.map +1 -0
  117. package/dist/cli/ui/screens/solid/SelectorScreen.js +90 -0
  118. package/dist/cli/ui/screens/solid/SelectorScreen.js.map +1 -0
  119. package/dist/cli/ui/screens/solid/WorktreeCreateScreen.d.ts +13 -0
  120. package/dist/cli/ui/screens/solid/WorktreeCreateScreen.d.ts.map +1 -0
  121. package/dist/cli/ui/screens/solid/WorktreeCreateScreen.js +59 -0
  122. package/dist/cli/ui/screens/solid/WorktreeCreateScreen.js.map +1 -0
  123. package/dist/cli/ui/stores/appStore.d.ts +143 -0
  124. package/dist/cli/ui/stores/appStore.d.ts.map +1 -0
  125. package/dist/cli/ui/stores/appStore.js +158 -0
  126. package/dist/cli/ui/stores/appStore.js.map +1 -0
  127. package/dist/cli/ui/stores/branchStore.d.ts +159 -0
  128. package/dist/cli/ui/stores/branchStore.d.ts.map +1 -0
  129. package/dist/cli/ui/stores/branchStore.js +275 -0
  130. package/dist/cli/ui/stores/branchStore.js.map +1 -0
  131. package/dist/cli/ui/stores/index.d.ts +11 -0
  132. package/dist/cli/ui/stores/index.d.ts.map +1 -0
  133. package/dist/cli/ui/stores/index.js +14 -0
  134. package/dist/cli/ui/stores/index.js.map +1 -0
  135. package/dist/cli/ui/stores/uiStore.d.ts +146 -0
  136. package/dist/cli/ui/stores/uiStore.d.ts.map +1 -0
  137. package/dist/cli/ui/stores/uiStore.js +166 -0
  138. package/dist/cli/ui/stores/uiStore.js.map +1 -0
  139. package/dist/cli/ui/types.d.ts +16 -1
  140. package/dist/cli/ui/types.d.ts.map +1 -1
  141. package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -1
  142. package/dist/cli/ui/utils/branchFormatter.js +7 -210
  143. package/dist/cli/ui/utils/branchFormatter.js.map +1 -1
  144. package/dist/cli/ui/utils/continueSession.d.ts +4 -0
  145. package/dist/cli/ui/utils/continueSession.d.ts.map +1 -1
  146. package/dist/cli/ui/utils/continueSession.js +30 -0
  147. package/dist/cli/ui/utils/continueSession.js.map +1 -1
  148. package/dist/client/assets/{index-ChHC-Puh.css → index-BbfV7Wuj.css} +1 -1
  149. package/dist/client/assets/index-CoAyq5x1.js +78 -0
  150. package/dist/client/index.html +2 -2
  151. package/dist/codex.d.ts +1 -0
  152. package/dist/codex.d.ts.map +1 -1
  153. package/dist/codex.js +86 -45
  154. package/dist/codex.js.map +1 -1
  155. package/dist/config/builtin-coding-agents.js +4 -4
  156. package/dist/config/builtin-coding-agents.js.map +1 -1
  157. package/dist/config/index.d.ts +2 -0
  158. package/dist/config/index.d.ts.map +1 -1
  159. package/dist/config/index.js +2 -0
  160. package/dist/config/index.js.map +1 -1
  161. package/dist/gemini.d.ts +1 -0
  162. package/dist/gemini.d.ts.map +1 -1
  163. package/dist/gemini.js +42 -37
  164. package/dist/gemini.js.map +1 -1
  165. package/dist/index.d.ts +1 -1
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.js +122 -102
  168. package/dist/index.js.map +1 -1
  169. package/dist/launcher.d.ts.map +1 -1
  170. package/dist/launcher.js +18 -3
  171. package/dist/launcher.js.map +1 -1
  172. package/dist/logging/logger.d.ts.map +1 -1
  173. package/dist/logging/logger.js +26 -9
  174. package/dist/logging/logger.js.map +1 -1
  175. package/dist/opentui/highlights-eq9cgrbb.scm +604 -0
  176. package/dist/opentui/highlights-ghv9g403.scm +205 -0
  177. package/dist/opentui/highlights-hk7bwhj4.scm +284 -0
  178. package/dist/opentui/highlights-r812a2qc.scm +150 -0
  179. package/dist/opentui/highlights-x6tmsnaa.scm +115 -0
  180. package/dist/opentui/index.solid.d.ts +2 -0
  181. package/dist/opentui/index.solid.d.ts.map +1 -0
  182. package/dist/opentui/index.solid.js +52034 -0
  183. package/dist/opentui/index.solid.js.map +1 -0
  184. package/dist/opentui/injections-73j83es3.scm +27 -0
  185. package/dist/opentui/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  186. package/dist/opentui/tree-sitter-markdown-411r6y9b.wasm +0 -0
  187. package/dist/opentui/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  188. package/dist/opentui/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  189. package/dist/opentui/tree-sitter-zig-e78zbjpm.wasm +0 -0
  190. package/dist/repositories/worktree.repository.d.ts +1 -0
  191. package/dist/repositories/worktree.repository.d.ts.map +1 -1
  192. package/dist/repositories/worktree.repository.js +7 -0
  193. package/dist/repositories/worktree.repository.js.map +1 -1
  194. package/dist/services/codingAgentResolver.d.ts +2 -0
  195. package/dist/services/codingAgentResolver.d.ts.map +1 -1
  196. package/dist/services/codingAgentResolver.js +30 -4
  197. package/dist/services/codingAgentResolver.js.map +1 -1
  198. package/dist/services/dependency-installer.d.ts.map +1 -1
  199. package/dist/services/dependency-installer.js +0 -5
  200. package/dist/services/dependency-installer.js.map +1 -1
  201. package/dist/types/api.d.ts +3 -0
  202. package/dist/types/api.d.ts.map +1 -1
  203. package/dist/types/coding-agent.d.ts +62 -0
  204. package/dist/types/coding-agent.d.ts.map +1 -0
  205. package/dist/types/coding-agent.js +29 -0
  206. package/dist/types/coding-agent.js.map +1 -0
  207. package/dist/types/tools.d.ts +17 -0
  208. package/dist/types/tools.d.ts.map +1 -1
  209. package/dist/utils/coding-agent-colors.d.ts +88 -0
  210. package/dist/utils/coding-agent-colors.d.ts.map +1 -0
  211. package/dist/utils/coding-agent-colors.js +137 -0
  212. package/dist/utils/coding-agent-colors.js.map +1 -0
  213. package/dist/utils/command.d.ts +1 -1
  214. package/dist/utils/command.js +1 -1
  215. package/dist/utils/error-utils.d.ts +27 -0
  216. package/dist/utils/error-utils.d.ts.map +1 -0
  217. package/dist/utils/error-utils.js +98 -0
  218. package/dist/utils/error-utils.js.map +1 -0
  219. package/dist/utils/npmRegistry.d.ts +61 -0
  220. package/dist/utils/npmRegistry.d.ts.map +1 -0
  221. package/dist/utils/npmRegistry.js +180 -0
  222. package/dist/utils/npmRegistry.js.map +1 -0
  223. package/dist/utils/prompt.d.ts +1 -1
  224. package/dist/utils/prompt.js +1 -1
  225. package/dist/utils/session/parsers/codex.d.ts.map +1 -1
  226. package/dist/utils/session/parsers/codex.js +8 -1
  227. package/dist/utils/session/parsers/codex.js.map +1 -1
  228. package/dist/utils/terminal.d.ts +1 -0
  229. package/dist/utils/terminal.d.ts.map +1 -1
  230. package/dist/utils/terminal.js +20 -0
  231. package/dist/utils/terminal.js.map +1 -1
  232. package/dist/utils.d.ts +9 -0
  233. package/dist/utils.d.ts.map +1 -1
  234. package/dist/utils.js +33 -2
  235. package/dist/utils.js.map +1 -1
  236. package/dist/web/client/src/components/CodingAgentLaunchModal.d.ts.map +1 -1
  237. package/dist/web/client/src/components/CodingAgentLaunchModal.js +7 -16
  238. package/dist/web/client/src/components/CodingAgentLaunchModal.js.map +1 -1
  239. package/dist/web/client/src/components/branch-detail/BranchInfoCards.d.ts.map +1 -1
  240. package/dist/web/client/src/components/branch-detail/BranchInfoCards.js +7 -2
  241. package/dist/web/client/src/components/branch-detail/BranchInfoCards.js.map +1 -1
  242. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.d.ts.map +1 -1
  243. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.js +2 -1
  244. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.js.map +1 -1
  245. package/dist/web/client/src/components/branch-detail/ToolLauncher.d.ts +2 -2
  246. package/dist/web/client/src/components/branch-detail/ToolLauncher.d.ts.map +1 -1
  247. package/dist/web/client/src/components/branch-detail/ToolLauncher.js +5 -5
  248. package/dist/web/client/src/components/branch-detail/ToolLauncher.js.map +1 -1
  249. package/dist/web/client/src/lib/coding-agent-colors.d.ts +86 -0
  250. package/dist/web/client/src/lib/coding-agent-colors.d.ts.map +1 -0
  251. package/dist/web/client/src/lib/coding-agent-colors.js +135 -0
  252. package/dist/web/client/src/lib/coding-agent-colors.js.map +1 -0
  253. package/dist/web/client/src/pages/BranchDetailPage.js +10 -10
  254. package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
  255. package/dist/web/server/pty/manager.d.ts +2 -0
  256. package/dist/web/server/pty/manager.d.ts.map +1 -1
  257. package/dist/web/server/pty/manager.js +104 -0
  258. package/dist/web/server/pty/manager.js.map +1 -1
  259. package/dist/web/server/routes/sessions.d.ts.map +1 -1
  260. package/dist/web/server/routes/sessions.js +5 -1
  261. package/dist/web/server/routes/sessions.js.map +1 -1
  262. package/dist/web/server/services/branches.d.ts.map +1 -1
  263. package/dist/web/server/services/branches.js +10 -8
  264. package/dist/web/server/services/branches.js.map +1 -1
  265. package/dist/web/server/services/worktrees.js +2 -2
  266. package/dist/web/server/services/worktrees.js.map +1 -1
  267. package/dist/worktree.d.ts +47 -1
  268. package/dist/worktree.d.ts.map +1 -1
  269. package/dist/worktree.js +280 -94
  270. package/dist/worktree.js.map +1 -1
  271. package/package.json +12 -14
  272. package/src/claude.ts +68 -70
  273. package/src/cli/ui/App.solid.tsx +1823 -0
  274. package/src/cli/ui/__tests__/solid/AppSolid.cleanup.test.tsx +255 -0
  275. package/src/cli/ui/__tests__/solid/BranchListScreen.test.tsx +243 -0
  276. package/src/cli/ui/__tests__/solid/components/QuickStartStep.test.tsx +237 -0
  277. package/src/cli/ui/__tests__/solid/components/WizardPopup.test.tsx +231 -0
  278. package/src/cli/ui/__tests__/solid/components/WizardSteps.test.tsx +238 -0
  279. package/src/cli/ui/__tests__/utils/branchFormatter.test.ts +7 -289
  280. package/src/cli/ui/__tests__/utils/clipboard.test.ts +3 -3
  281. package/src/cli/ui/__tests__/utils/statisticsCalculator.test.ts +1 -1
  282. package/src/cli/ui/components/solid/Footer.tsx +36 -0
  283. package/src/cli/ui/components/{parts → solid}/Header.tsx +17 -28
  284. package/src/cli/ui/components/solid/HelpOverlay.tsx +194 -0
  285. package/src/cli/ui/components/solid/QuickStartStep.tsx +197 -0
  286. package/src/cli/ui/components/{parts → solid}/ScrollableList.tsx +7 -8
  287. package/src/cli/ui/components/solid/SearchInput.tsx +42 -0
  288. package/src/cli/ui/components/solid/SelectInput.tsx +84 -0
  289. package/src/cli/ui/components/solid/Stats.tsx +92 -0
  290. package/src/cli/ui/components/solid/TextInput.tsx +49 -0
  291. package/src/cli/ui/components/solid/WizardController.tsx +384 -0
  292. package/src/cli/ui/components/solid/WizardPopup.tsx +135 -0
  293. package/src/cli/ui/components/solid/WizardSteps.tsx +793 -0
  294. package/src/cli/ui/core/index.ts +17 -0
  295. package/src/cli/ui/core/keybindings.ts +367 -0
  296. package/src/cli/ui/core/theme.ts +234 -0
  297. package/src/cli/ui/core/types.ts +235 -0
  298. package/src/cli/ui/hooks/solid/useAsyncOperation.ts +76 -0
  299. package/src/cli/ui/hooks/solid/useFilter.ts +86 -0
  300. package/src/cli/ui/hooks/solid/useGitOperations.ts +80 -0
  301. package/src/cli/ui/hooks/solid/useKeyHandler.ts +103 -0
  302. package/src/cli/ui/hooks/solid/useScrollableList.ts +149 -0
  303. package/src/cli/ui/hooks/solid/useSelection.ts +77 -0
  304. package/src/cli/ui/hooks/solid/useTerminalSize.ts +22 -0
  305. package/src/cli/ui/index.solid.ts +28 -0
  306. package/src/cli/ui/screens/solid/BranchListScreen.tsx +1050 -0
  307. package/src/cli/ui/screens/solid/ConfirmScreen.tsx +74 -0
  308. package/src/cli/ui/screens/solid/EnvironmentScreen.tsx +159 -0
  309. package/src/cli/ui/screens/solid/ErrorScreen.tsx +42 -0
  310. package/src/cli/ui/screens/solid/InputScreen.tsx +55 -0
  311. package/src/cli/ui/screens/solid/LoadingIndicator.tsx +77 -0
  312. package/src/cli/ui/screens/solid/LogDetailScreen.tsx +75 -0
  313. package/src/cli/ui/screens/solid/LogScreen.tsx +175 -0
  314. package/src/cli/ui/screens/solid/ProfileEnvScreen.tsx +192 -0
  315. package/src/cli/ui/screens/solid/ProfileScreen.tsx +98 -0
  316. package/src/cli/ui/screens/solid/SelectorScreen.tsx +170 -0
  317. package/src/cli/ui/screens/solid/SettingsScreen.tsx +50 -0
  318. package/src/cli/ui/screens/solid/WorktreeCreateScreen.tsx +136 -0
  319. package/src/cli/ui/screens/solid/WorktreeDeleteScreen.tsx +40 -0
  320. package/src/cli/ui/stores/appStore.ts +208 -0
  321. package/src/cli/ui/stores/branchStore.ts +357 -0
  322. package/src/cli/ui/stores/index.ts +31 -0
  323. package/src/cli/ui/stores/uiStore.ts +226 -0
  324. package/src/cli/ui/types.ts +20 -3
  325. package/src/cli/ui/utils/__tests__/branchFormatter.test.ts +180 -0
  326. package/src/cli/ui/utils/branchFormatter.ts +8 -215
  327. package/src/cli/ui/utils/continueSession.ts +44 -0
  328. package/src/cli/ui/utils/modelOptions.test.ts +1 -1
  329. package/src/codex.ts +100 -43
  330. package/src/config/__tests__/saveSession.test.ts +143 -0
  331. package/src/config/builtin-coding-agents.ts +4 -4
  332. package/src/config/index.ts +4 -0
  333. package/src/gemini.ts +58 -43
  334. package/src/index.test.ts +12 -12
  335. package/src/index.ts +164 -142
  336. package/src/launcher.ts +22 -3
  337. package/src/logging/logger.ts +32 -10
  338. package/src/opentui/index.solid.ts +1 -0
  339. package/src/repositories/worktree.repository.ts +8 -0
  340. package/src/services/__tests__/BatchMergeService.test.ts +62 -66
  341. package/src/services/__tests__/WorktreeOrchestrator.test.ts +8 -7
  342. package/src/services/codingAgentResolver.ts +30 -4
  343. package/src/services/dependency-installer.ts +0 -7
  344. package/src/types/api.ts +3 -0
  345. package/src/types/coding-agent.ts +85 -0
  346. package/src/types/tools.ts +19 -0
  347. package/src/utils/__tests__/npmRegistry.test.ts +250 -0
  348. package/src/utils/__tests__/prompt.test.ts +4 -5
  349. package/src/utils/coding-agent-colors.ts +165 -0
  350. package/src/utils/command.ts +1 -1
  351. package/src/utils/error-utils.ts +133 -0
  352. package/src/utils/npmRegistry.ts +249 -0
  353. package/src/utils/prompt.ts +1 -1
  354. package/src/utils/session/parsers/codex.ts +9 -1
  355. package/src/utils/terminal.ts +24 -0
  356. package/src/utils.test.ts +1 -1
  357. package/src/utils.ts +37 -4
  358. package/src/web/client/src/components/CodingAgentLaunchModal.tsx +12 -21
  359. package/src/web/client/src/components/branch-detail/BranchInfoCards.tsx +16 -1
  360. package/src/web/client/src/components/branch-detail/SessionHistoryTable.tsx +7 -1
  361. package/src/web/client/src/components/branch-detail/ToolLauncher.tsx +11 -6
  362. package/src/web/client/src/lib/coding-agent-colors.ts +149 -0
  363. package/src/web/client/src/pages/BranchDetailPage.tsx +11 -11
  364. package/src/web/server/pty/manager.ts +139 -0
  365. package/src/web/server/routes/sessions.ts +6 -0
  366. package/src/web/server/services/branches.ts +11 -8
  367. package/src/web/server/services/worktrees.ts +2 -2
  368. package/src/worktree.ts +366 -107
  369. package/dist/cli/ui/components/App.d.ts +0 -25
  370. package/dist/cli/ui/components/App.d.ts.map +0 -1
  371. package/dist/cli/ui/components/App.js +0 -1006
  372. package/dist/cli/ui/components/App.js.map +0 -1
  373. package/dist/cli/ui/components/common/Confirm.d.ts +0 -13
  374. package/dist/cli/ui/components/common/Confirm.d.ts.map +0 -1
  375. package/dist/cli/ui/components/common/Confirm.js +0 -20
  376. package/dist/cli/ui/components/common/Confirm.js.map +0 -1
  377. package/dist/cli/ui/components/common/ErrorBoundary.d.ts +0 -23
  378. package/dist/cli/ui/components/common/ErrorBoundary.d.ts.map +0 -1
  379. package/dist/cli/ui/components/common/ErrorBoundary.js +0 -37
  380. package/dist/cli/ui/components/common/ErrorBoundary.js.map +0 -1
  381. package/dist/cli/ui/components/common/Input.d.ts +0 -19
  382. package/dist/cli/ui/components/common/Input.d.ts.map +0 -1
  383. package/dist/cli/ui/components/common/Input.js +0 -22
  384. package/dist/cli/ui/components/common/Input.js.map +0 -1
  385. package/dist/cli/ui/components/common/LoadingIndicator.d.ts +0 -19
  386. package/dist/cli/ui/components/common/LoadingIndicator.d.ts.map +0 -1
  387. package/dist/cli/ui/components/common/LoadingIndicator.js +0 -61
  388. package/dist/cli/ui/components/common/LoadingIndicator.js.map +0 -1
  389. package/dist/cli/ui/components/common/Select.d.ts +0 -38
  390. package/dist/cli/ui/components/common/Select.d.ts.map +0 -1
  391. package/dist/cli/ui/components/common/Select.js +0 -151
  392. package/dist/cli/ui/components/common/Select.js.map +0 -1
  393. package/dist/cli/ui/components/common/SpinnerIcon.d.ts +0 -20
  394. package/dist/cli/ui/components/common/SpinnerIcon.d.ts.map +0 -1
  395. package/dist/cli/ui/components/common/SpinnerIcon.js +0 -61
  396. package/dist/cli/ui/components/common/SpinnerIcon.js.map +0 -1
  397. package/dist/cli/ui/components/parts/Footer.d.ts +0 -15
  398. package/dist/cli/ui/components/parts/Footer.d.ts.map +0 -1
  399. package/dist/cli/ui/components/parts/Footer.js +0 -20
  400. package/dist/cli/ui/components/parts/Footer.js.map +0 -1
  401. package/dist/cli/ui/components/parts/Header.d.ts.map +0 -1
  402. package/dist/cli/ui/components/parts/Header.js +0 -24
  403. package/dist/cli/ui/components/parts/Header.js.map +0 -1
  404. package/dist/cli/ui/components/parts/MergeStatusList.d.ts +0 -13
  405. package/dist/cli/ui/components/parts/MergeStatusList.d.ts.map +0 -1
  406. package/dist/cli/ui/components/parts/MergeStatusList.js +0 -52
  407. package/dist/cli/ui/components/parts/MergeStatusList.js.map +0 -1
  408. package/dist/cli/ui/components/parts/ProgressBar.d.ts +0 -13
  409. package/dist/cli/ui/components/parts/ProgressBar.d.ts.map +0 -1
  410. package/dist/cli/ui/components/parts/ProgressBar.js +0 -53
  411. package/dist/cli/ui/components/parts/ProgressBar.js.map +0 -1
  412. package/dist/cli/ui/components/parts/ScrollableList.d.ts +0 -12
  413. package/dist/cli/ui/components/parts/ScrollableList.d.ts.map +0 -1
  414. package/dist/cli/ui/components/parts/ScrollableList.js +0 -11
  415. package/dist/cli/ui/components/parts/ScrollableList.js.map +0 -1
  416. package/dist/cli/ui/components/parts/Stats.d.ts +0 -10
  417. package/dist/cli/ui/components/parts/Stats.d.ts.map +0 -1
  418. package/dist/cli/ui/components/parts/Stats.js +0 -55
  419. package/dist/cli/ui/components/parts/Stats.js.map +0 -1
  420. package/dist/cli/ui/components/screens/BatchMergeProgressScreen.d.ts +0 -17
  421. package/dist/cli/ui/components/screens/BatchMergeProgressScreen.d.ts.map +0 -1
  422. package/dist/cli/ui/components/screens/BatchMergeProgressScreen.js +0 -42
  423. package/dist/cli/ui/components/screens/BatchMergeProgressScreen.js.map +0 -1
  424. package/dist/cli/ui/components/screens/BatchMergeResultScreen.d.ts +0 -17
  425. package/dist/cli/ui/components/screens/BatchMergeResultScreen.d.ts.map +0 -1
  426. package/dist/cli/ui/components/screens/BatchMergeResultScreen.js +0 -72
  427. package/dist/cli/ui/components/screens/BatchMergeResultScreen.js.map +0 -1
  428. package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts +0 -18
  429. package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts.map +0 -1
  430. package/dist/cli/ui/components/screens/BranchCreatorScreen.js +0 -151
  431. package/dist/cli/ui/components/screens/BranchCreatorScreen.js.map +0 -1
  432. package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +0 -1
  433. package/dist/cli/ui/components/screens/BranchListScreen.js +0 -476
  434. package/dist/cli/ui/components/screens/BranchListScreen.js.map +0 -1
  435. package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts +0 -30
  436. package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts.map +0 -1
  437. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js +0 -148
  438. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js.map +0 -1
  439. package/dist/cli/ui/components/screens/CodingAgentSelectorScreen.d.ts +0 -27
  440. package/dist/cli/ui/components/screens/CodingAgentSelectorScreen.d.ts.map +0 -1
  441. package/dist/cli/ui/components/screens/CodingAgentSelectorScreen.js +0 -93
  442. package/dist/cli/ui/components/screens/CodingAgentSelectorScreen.js.map +0 -1
  443. package/dist/cli/ui/components/screens/EnvironmentProfileScreen.d.ts +0 -19
  444. package/dist/cli/ui/components/screens/EnvironmentProfileScreen.d.ts.map +0 -1
  445. package/dist/cli/ui/components/screens/EnvironmentProfileScreen.js +0 -577
  446. package/dist/cli/ui/components/screens/EnvironmentProfileScreen.js.map +0 -1
  447. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts +0 -45
  448. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts.map +0 -1
  449. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js +0 -95
  450. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js.map +0 -1
  451. package/dist/cli/ui/components/screens/LogDatePickerScreen.d.ts +0 -10
  452. package/dist/cli/ui/components/screens/LogDatePickerScreen.d.ts.map +0 -1
  453. package/dist/cli/ui/components/screens/LogDatePickerScreen.js +0 -44
  454. package/dist/cli/ui/components/screens/LogDatePickerScreen.js.map +0 -1
  455. package/dist/cli/ui/components/screens/LogDetailScreen.d.ts.map +0 -1
  456. package/dist/cli/ui/components/screens/LogDetailScreen.js +0 -34
  457. package/dist/cli/ui/components/screens/LogDetailScreen.js.map +0 -1
  458. package/dist/cli/ui/components/screens/LogListScreen.d.ts.map +0 -1
  459. package/dist/cli/ui/components/screens/LogListScreen.js +0 -107
  460. package/dist/cli/ui/components/screens/LogListScreen.js.map +0 -1
  461. package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts +0 -24
  462. package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts.map +0 -1
  463. package/dist/cli/ui/components/screens/ModelSelectorScreen.js +0 -197
  464. package/dist/cli/ui/components/screens/ModelSelectorScreen.js.map +0 -1
  465. package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts +0 -29
  466. package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts.map +0 -1
  467. package/dist/cli/ui/components/screens/PRCleanupScreen.js +0 -92
  468. package/dist/cli/ui/components/screens/PRCleanupScreen.js.map +0 -1
  469. package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts +0 -31
  470. package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts.map +0 -1
  471. package/dist/cli/ui/components/screens/SessionSelectorScreen.js +0 -67
  472. package/dist/cli/ui/components/screens/SessionSelectorScreen.js.map +0 -1
  473. package/dist/cli/ui/hooks/useAppInput.d.ts +0 -21
  474. package/dist/cli/ui/hooks/useAppInput.d.ts.map +0 -1
  475. package/dist/cli/ui/hooks/useAppInput.js +0 -138
  476. package/dist/cli/ui/hooks/useAppInput.js.map +0 -1
  477. package/dist/cli/ui/hooks/useBatchMerge.d.ts +0 -17
  478. package/dist/cli/ui/hooks/useBatchMerge.d.ts.map +0 -1
  479. package/dist/cli/ui/hooks/useBatchMerge.js +0 -77
  480. package/dist/cli/ui/hooks/useBatchMerge.js.map +0 -1
  481. package/dist/cli/ui/hooks/useGitData.d.ts +0 -21
  482. package/dist/cli/ui/hooks/useGitData.d.ts.map +0 -1
  483. package/dist/cli/ui/hooks/useGitData.js +0 -229
  484. package/dist/cli/ui/hooks/useGitData.js.map +0 -1
  485. package/dist/cli/ui/hooks/useProfiles.d.ts +0 -41
  486. package/dist/cli/ui/hooks/useProfiles.d.ts.map +0 -1
  487. package/dist/cli/ui/hooks/useProfiles.js +0 -136
  488. package/dist/cli/ui/hooks/useProfiles.js.map +0 -1
  489. package/dist/cli/ui/hooks/useScreenState.d.ts +0 -12
  490. package/dist/cli/ui/hooks/useScreenState.d.ts.map +0 -1
  491. package/dist/cli/ui/hooks/useScreenState.js +0 -30
  492. package/dist/cli/ui/hooks/useScreenState.js.map +0 -1
  493. package/dist/cli/ui/hooks/useTerminalSize.d.ts +0 -9
  494. package/dist/cli/ui/hooks/useTerminalSize.d.ts.map +0 -1
  495. package/dist/cli/ui/hooks/useTerminalSize.js +0 -24
  496. package/dist/cli/ui/hooks/useTerminalSize.js.map +0 -1
  497. package/dist/cli/ui/hooks/useToolStatus.d.ts +0 -30
  498. package/dist/cli/ui/hooks/useToolStatus.d.ts.map +0 -1
  499. package/dist/cli/ui/hooks/useToolStatus.js +0 -49
  500. package/dist/cli/ui/hooks/useToolStatus.js.map +0 -1
  501. package/dist/cli/ui/screens/BranchActionSelectorScreen.d.ts +0 -24
  502. package/dist/cli/ui/screens/BranchActionSelectorScreen.d.ts.map +0 -1
  503. package/dist/cli/ui/screens/BranchActionSelectorScreen.js +0 -65
  504. package/dist/cli/ui/screens/BranchActionSelectorScreen.js.map +0 -1
  505. package/dist/client/assets/index-LNPtOrn3.js +0 -78
  506. package/src/cli/ui/__tests__/SKIPPED_TESTS.md +0 -119
  507. package/src/cli/ui/__tests__/acceptance/branchList.acceptance.test.tsx.skip +0 -239
  508. package/src/cli/ui/__tests__/acceptance/navigation.acceptance.test.tsx +0 -225
  509. package/src/cli/ui/__tests__/acceptance/realtimeUpdate.acceptance.test.tsx.skip +0 -219
  510. package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +0 -212
  511. package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +0 -440
  512. package/src/cli/ui/__tests__/components/App.test.tsx +0 -365
  513. package/src/cli/ui/__tests__/components/ModelSelectorScreen.initial.test.tsx +0 -91
  514. package/src/cli/ui/__tests__/components/common/Confirm.test.tsx +0 -80
  515. package/src/cli/ui/__tests__/components/common/ErrorBoundary.test.tsx +0 -104
  516. package/src/cli/ui/__tests__/components/common/Input.test.tsx +0 -100
  517. package/src/cli/ui/__tests__/components/common/LoadingIndicator.test.tsx +0 -148
  518. package/src/cli/ui/__tests__/components/common/Select.memo.test.tsx +0 -255
  519. package/src/cli/ui/__tests__/components/common/Select.test.tsx +0 -335
  520. package/src/cli/ui/__tests__/components/parts/Footer.test.tsx +0 -65
  521. package/src/cli/ui/__tests__/components/parts/Header.test.tsx +0 -55
  522. package/src/cli/ui/__tests__/components/parts/ScrollableList.test.tsx +0 -69
  523. package/src/cli/ui/__tests__/components/parts/Stats.test.tsx +0 -148
  524. package/src/cli/ui/__tests__/components/screens/BranchCreatorScreen.test.tsx +0 -253
  525. package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +0 -1070
  526. package/src/cli/ui/__tests__/components/screens/BranchQuickStartScreen.test.tsx +0 -142
  527. package/src/cli/ui/__tests__/components/screens/CodingAgentSelectorScreen.test.tsx +0 -174
  528. package/src/cli/ui/__tests__/components/screens/ExecutionModeSelectorScreen.test.tsx +0 -182
  529. package/src/cli/ui/__tests__/components/screens/LogDetailScreen.test.tsx +0 -57
  530. package/src/cli/ui/__tests__/components/screens/LogListScreen.test.tsx +0 -102
  531. package/src/cli/ui/__tests__/components/screens/PRCleanupScreen.test.tsx +0 -216
  532. package/src/cli/ui/__tests__/components/screens/SessionSelectorScreen.test.tsx +0 -147
  533. package/src/cli/ui/__tests__/hooks/useGitData.nonblocking.test.tsx +0 -206
  534. package/src/cli/ui/__tests__/hooks/useGitData.test.ts +0 -197
  535. package/src/cli/ui/__tests__/hooks/useGitData.test.ts.skip +0 -228
  536. package/src/cli/ui/__tests__/hooks/useScreenState.test.ts +0 -147
  537. package/src/cli/ui/__tests__/hooks/useTerminalSize.test.ts +0 -99
  538. package/src/cli/ui/__tests__/integration/branchList.test.tsx.skip +0 -253
  539. package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +0 -436
  540. package/src/cli/ui/__tests__/integration/navigation.test.tsx +0 -514
  541. package/src/cli/ui/__tests__/integration/realtimeUpdate.test.tsx +0 -509
  542. package/src/cli/ui/__tests__/integration/realtimeUpdate.test.tsx.skip +0 -216
  543. package/src/cli/ui/__tests__/performance/branchList.performance.test.tsx +0 -193
  544. package/src/cli/ui/__tests__/performance/useMemoOptimization.test.tsx +0 -234
  545. package/src/cli/ui/components/App.tsx +0 -1478
  546. package/src/cli/ui/components/common/Confirm.tsx +0 -44
  547. package/src/cli/ui/components/common/ErrorBoundary.tsx +0 -60
  548. package/src/cli/ui/components/common/Input.tsx +0 -58
  549. package/src/cli/ui/components/common/LoadingIndicator.tsx +0 -98
  550. package/src/cli/ui/components/common/Select.tsx +0 -247
  551. package/src/cli/ui/components/common/SpinnerIcon.tsx +0 -86
  552. package/src/cli/ui/components/parts/Footer.tsx +0 -41
  553. package/src/cli/ui/components/parts/Header.test.tsx +0 -75
  554. package/src/cli/ui/components/parts/MergeStatusList.tsx +0 -75
  555. package/src/cli/ui/components/parts/ProgressBar.tsx +0 -73
  556. package/src/cli/ui/components/parts/Stats.tsx +0 -88
  557. package/src/cli/ui/components/screens/BatchMergeProgressScreen.tsx +0 -74
  558. package/src/cli/ui/components/screens/BatchMergeResultScreen.tsx +0 -108
  559. package/src/cli/ui/components/screens/BranchCreatorScreen.tsx +0 -242
  560. package/src/cli/ui/components/screens/BranchListScreen.tsx +0 -744
  561. package/src/cli/ui/components/screens/BranchQuickStartScreen.tsx +0 -244
  562. package/src/cli/ui/components/screens/CodingAgentSelectorScreen.tsx +0 -159
  563. package/src/cli/ui/components/screens/EnvironmentProfileScreen.tsx +0 -928
  564. package/src/cli/ui/components/screens/ExecutionModeSelectorScreen.tsx +0 -176
  565. package/src/cli/ui/components/screens/LogDatePickerScreen.tsx +0 -83
  566. package/src/cli/ui/components/screens/LogDetailScreen.tsx +0 -67
  567. package/src/cli/ui/components/screens/LogListScreen.tsx +0 -192
  568. package/src/cli/ui/components/screens/ModelSelectorScreen.tsx +0 -320
  569. package/src/cli/ui/components/screens/PRCleanupScreen.tsx +0 -171
  570. package/src/cli/ui/components/screens/SessionSelectorScreen.tsx +0 -135
  571. package/src/cli/ui/hooks/useAppInput.ts +0 -172
  572. package/src/cli/ui/hooks/useBatchMerge.ts +0 -96
  573. package/src/cli/ui/hooks/useGitData.ts +0 -347
  574. package/src/cli/ui/hooks/useProfiles.ts +0 -211
  575. package/src/cli/ui/hooks/useScreenState.ts +0 -44
  576. package/src/cli/ui/hooks/useTerminalSize.ts +0 -33
  577. package/src/cli/ui/hooks/useToolStatus.ts +0 -68
  578. package/src/cli/ui/screens/BranchActionSelectorScreen.tsx +0 -111
  579. package/src/cli/ui/screens/__tests__/BranchActionSelectorScreen.test.tsx +0 -264
@@ -1,1070 +0,0 @@
1
- /**
2
- * @vitest-environment happy-dom
3
- */
4
- import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
5
- import { act, render } from "@testing-library/react";
6
- import { render as inkRender } from "ink-testing-library";
7
- import React from "react";
8
- import { BranchListScreen } from "../../../components/screens/BranchListScreen.js";
9
- import type { BranchInfo, BranchItem, Statistics } from "../../../types.js";
10
- import { formatBranchItem } from "../../../utils/branchFormatter.js";
11
- import { Window } from "happy-dom";
12
-
13
- const stripAnsi = (value: string): string =>
14
- value.replace(/\u001b\[[0-9;]*m/g, "");
15
- const stripControlSequences = (value: string): string =>
16
- value.replace(/\u001b\[([0-9;?]*)([A-Za-z])/g, (_, params, command) => {
17
- if (command === "C") {
18
- const count = Number(params || "1");
19
- return " ".repeat(Number.isNaN(count) ? 0 : count);
20
- }
21
- return "";
22
- });
23
-
24
- describe("BranchListScreen", () => {
25
- beforeEach(() => {
26
- vi.useFakeTimers();
27
- // Setup happy-dom
28
- const window = new Window();
29
- globalThis.window = window as unknown as typeof globalThis.window;
30
- globalThis.document =
31
- window.document as unknown as typeof globalThis.document;
32
- });
33
-
34
- afterEach(() => {
35
- vi.useRealTimers();
36
- });
37
-
38
- const mockBranches: BranchItem[] = [
39
- {
40
- name: "main",
41
- type: "local",
42
- branchType: "main",
43
- isCurrent: true,
44
- icons: ["⚡", "⭐"],
45
- hasChanges: false,
46
- label: "⚡ ⭐ main",
47
- value: "main",
48
- latestCommitTimestamp: 1_700_000_000,
49
- },
50
- {
51
- name: "feature/test",
52
- type: "local",
53
- branchType: "feature",
54
- isCurrent: false,
55
- icons: ["✨"],
56
- hasChanges: false,
57
- label: "✨ feature/test",
58
- value: "feature/test",
59
- latestCommitTimestamp: 1_699_000_000,
60
- },
61
- ];
62
-
63
- const mockStats: Statistics = {
64
- localCount: 2,
65
- remoteCount: 1,
66
- worktreeCount: 0,
67
- changesCount: 0,
68
- lastUpdated: new Date(),
69
- };
70
-
71
- it("should render header with title", () => {
72
- const onSelect = vi.fn();
73
- const { getByText } = render(
74
- <BranchListScreen
75
- branches={mockBranches}
76
- stats={mockStats}
77
- onSelect={onSelect}
78
- />,
79
- );
80
-
81
- expect(getByText(/gwt - Branch Selection/i)).toBeDefined();
82
- });
83
-
84
- it("should render statistics", () => {
85
- const onSelect = vi.fn();
86
- const { container, getByText } = render(
87
- <BranchListScreen
88
- branches={mockBranches}
89
- stats={mockStats}
90
- onSelect={onSelect}
91
- />,
92
- );
93
-
94
- expect(container.textContent).toContain("Local: 2");
95
- expect(getByText(/Remote:/)).toBeDefined();
96
- });
97
-
98
- it("should render branch list", () => {
99
- const onSelect = vi.fn();
100
- const { getByText } = render(
101
- <BranchListScreen
102
- branches={mockBranches}
103
- stats={mockStats}
104
- onSelect={onSelect}
105
- />,
106
- );
107
-
108
- expect(getByText(/main/)).toBeDefined();
109
- expect(getByText(/feature\/test/)).toBeDefined();
110
- });
111
-
112
- it("should render footer with actions", () => {
113
- const onSelect = vi.fn();
114
- const { getAllByText } = render(
115
- <BranchListScreen
116
- branches={mockBranches}
117
- stats={mockStats}
118
- onSelect={onSelect}
119
- />,
120
- );
121
-
122
- // Check for enter key (main screen doesn't have q key, exit is Ctrl+C only)
123
- expect(getAllByText(/enter/i).length).toBeGreaterThan(0);
124
- });
125
-
126
- it("should handle empty branch list", () => {
127
- const onSelect = vi.fn();
128
- const emptyStats: Statistics = {
129
- localCount: 0,
130
- remoteCount: 0,
131
- worktreeCount: 0,
132
- changesCount: 0,
133
- lastUpdated: new Date(),
134
- };
135
-
136
- const { container } = render(
137
- <BranchListScreen branches={[]} stats={emptyStats} onSelect={onSelect} />,
138
- );
139
-
140
- expect(container).toBeDefined();
141
- });
142
-
143
- it("should display loading indicator after the configured delay", async () => {
144
- const onSelect = vi.fn();
145
- const { getByText } = render(
146
- <BranchListScreen
147
- branches={mockBranches}
148
- stats={mockStats}
149
- onSelect={onSelect}
150
- loading={true}
151
- loadingIndicatorDelay={10}
152
- />,
153
- );
154
-
155
- await act(async () => {
156
- if (typeof vi.advanceTimersByTime === "function") {
157
- vi.advanceTimersByTime(10);
158
- } else {
159
- await new Promise((resolve) => setTimeout(resolve, 10));
160
- }
161
- });
162
-
163
- expect(getByText(/Loading Git information/i)).toBeDefined();
164
- });
165
-
166
- it("should display error state", () => {
167
- const onSelect = vi.fn();
168
- const error = new Error("Failed to load branches");
169
- const { getByText } = render(
170
- <BranchListScreen
171
- branches={[]}
172
- stats={mockStats}
173
- onSelect={onSelect}
174
- error={error}
175
- />,
176
- );
177
-
178
- expect(getByText(/Error:/i)).toBeDefined();
179
- expect(getByText(/Failed to load branches/i)).toBeDefined();
180
- });
181
-
182
- it("should use terminal height for layout calculation", () => {
183
- const onSelect = vi.fn();
184
-
185
- // Mock process.stdout
186
- const originalRows = process.stdout.rows;
187
- process.stdout.rows = 30;
188
-
189
- const { container } = render(
190
- <BranchListScreen
191
- branches={mockBranches}
192
- stats={mockStats}
193
- onSelect={onSelect}
194
- />,
195
- );
196
-
197
- expect(container).toBeDefined();
198
-
199
- // Restore
200
- process.stdout.rows = originalRows;
201
- });
202
-
203
- it("should display ASCII state icons", () => {
204
- const onSelect = vi.fn();
205
- const { container } = render(
206
- <BranchListScreen
207
- branches={mockBranches}
208
- stats={mockStats}
209
- onSelect={onSelect}
210
- />,
211
- );
212
-
213
- const text = stripAnsi(container.textContent ?? "");
214
- expect(text).toMatch(/\[ \]\s(🟢|🔴|⚪)\s(🛡|⚠)/); // state cluster with spacing
215
- });
216
-
217
- it("should display 🔴 for inaccessible worktree", async () => {
218
- const onSelect = vi.fn();
219
- const branches: BranchItem[] = [
220
- {
221
- ...formatBranchItem({
222
- name: "feature/missing-worktree",
223
- type: "local",
224
- branchType: "feature",
225
- isCurrent: false,
226
- hasUnpushedCommits: false,
227
- worktree: {
228
- path: "/tmp/wt-missing",
229
- locked: false,
230
- prunable: false,
231
- isAccessible: false,
232
- },
233
- }),
234
- safeToCleanup: false,
235
- },
236
- ];
237
-
238
- let renderResult: ReturnType<typeof inkRender>;
239
- await act(async () => {
240
- renderResult = inkRender(
241
- <BranchListScreen
242
- branches={branches}
243
- stats={mockStats}
244
- onSelect={onSelect}
245
- />,
246
- { stripAnsi: false },
247
- );
248
- });
249
-
250
- const frame = stripControlSequences(
251
- stripAnsi(renderResult.lastFrame() ?? ""),
252
- );
253
- expect(frame).toContain("[ ] 🔴 ⚠");
254
- });
255
-
256
- it("should render last tool usage when available and Unknown when not", () => {
257
- const onSelect = vi.fn();
258
- const branches: BranchItem[] = [
259
- {
260
- name: "feature/with-usage",
261
- type: "local",
262
- branchType: "feature",
263
- isCurrent: false,
264
- hasUnpushedCommits: false,
265
- label: "feature/with-usage",
266
- value: "feature/with-usage",
267
- icons: [],
268
- hasChanges: false,
269
- lastToolUsage: {
270
- branch: "feature/with-usage",
271
- worktreePath: "/wt/with",
272
- toolId: "codex-cli",
273
- toolLabel: "Codex",
274
- mode: "normal",
275
- model: null,
276
- timestamp: Date.UTC(2025, 10, 26, 14, 3),
277
- },
278
- lastToolUsageLabel: "Codex | 2025-11-26 14:03",
279
- },
280
- {
281
- name: "feature/without-usage",
282
- type: "local",
283
- branchType: "feature",
284
- isCurrent: false,
285
- hasUnpushedCommits: false,
286
- label: "feature/without-usage",
287
- value: "feature/without-usage",
288
- icons: [],
289
- hasChanges: false,
290
- latestCommitTimestamp: 1_730_000_000,
291
- lastToolUsage: null,
292
- lastToolUsageLabel: null,
293
- },
294
- ];
295
-
296
- const { lastFrame } = inkRender(
297
- <BranchListScreen
298
- branches={branches}
299
- stats={mockStats}
300
- onSelect={onSelect}
301
- />,
302
- );
303
-
304
- const output = stripAnsi(stripControlSequences(lastFrame() ?? ""));
305
- expect(output).toContain("Codex");
306
- expect(output).toMatch(/2025-11-26/); // date is shown (may wrap)
307
- expect(output).toContain("Unknown");
308
- });
309
-
310
- it("should render latest commit timestamp for each branch", () => {
311
- const onSelect = vi.fn();
312
- const { container } = render(
313
- <BranchListScreen
314
- branches={mockBranches}
315
- stats={mockStats}
316
- onSelect={onSelect}
317
- />,
318
- );
319
-
320
- const textContent = container.textContent ?? "";
321
- const matches = textContent.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}/g) ?? [];
322
- expect(matches.length).toBe(mockBranches.length);
323
- });
324
-
325
- it("should highlight the selected branch with cyan background", async () => {
326
- process.env.FORCE_COLOR = "1";
327
- const onSelect = vi.fn();
328
- let renderResult: ReturnType<typeof inkRender>;
329
- await act(async () => {
330
- renderResult = inkRender(
331
- <BranchListScreen
332
- branches={mockBranches}
333
- stats={mockStats}
334
- onSelect={onSelect}
335
- />,
336
- { stripAnsi: false },
337
- );
338
- });
339
-
340
- const frame = renderResult?.lastFrame() ?? "";
341
- expect(frame).toContain("\u001b[46m"); // cyan background ANSI code
342
- });
343
-
344
- it("should align timestamps even when unpushed icon is displayed", async () => {
345
- process.env.FORCE_COLOR = "1";
346
- const onSelect = vi.fn();
347
-
348
- const originalColumns = process.stdout.columns;
349
- const originalRows = process.stdout.rows;
350
- process.stdout.columns = 94;
351
- process.stdout.rows = 30; // Ensure enough rows for all branches to be visible
352
-
353
- const branchInfos: BranchInfo[] = [
354
- {
355
- name: "feature/update-ui",
356
- type: "local",
357
- branchType: "feature",
358
- isCurrent: false,
359
- hasUnpushedCommits: true,
360
- latestCommitTimestamp: 1_700_000_000,
361
- },
362
- {
363
- name: "origin/main",
364
- type: "remote",
365
- branchType: "main",
366
- isCurrent: false,
367
- hasUnpushedCommits: false,
368
- latestCommitTimestamp: 1_699_999_000,
369
- },
370
- {
371
- name: "main",
372
- type: "local",
373
- branchType: "main",
374
- isCurrent: true,
375
- hasUnpushedCommits: false,
376
- latestCommitTimestamp: 1_699_998_000,
377
- },
378
- ];
379
-
380
- const branchesWithUnpushed: BranchItem[] = branchInfos.map((branch) =>
381
- formatBranchItem(branch),
382
- );
383
-
384
- try {
385
- let renderResult: ReturnType<typeof inkRender>;
386
- await act(async () => {
387
- renderResult = inkRender(
388
- <BranchListScreen
389
- branches={branchesWithUnpushed}
390
- stats={mockStats}
391
- onSelect={onSelect}
392
- />,
393
- { stripAnsi: false },
394
- );
395
- });
396
-
397
- const frame = renderResult?.lastFrame() ?? "";
398
- const plain = stripControlSequences(stripAnsi(frame));
399
- const regex = /\d{4}-\d{2}-\d{2} \d{2}:\d{2}/g;
400
- let matches = plain.match(regex) ?? [];
401
- if (matches.length === 0) {
402
- matches = plain.replace(/\n+/g, " ").match(regex) ?? [];
403
- }
404
- expect(matches.length).toBeGreaterThanOrEqual(1);
405
- } finally {
406
- process.stdout.columns = originalColumns;
407
- process.stdout.rows = originalRows;
408
- }
409
- });
410
-
411
- it("toggles selection with space and shows ASCII state icons", async () => {
412
- const onSelect = vi.fn();
413
-
414
- const branches: BranchItem[] = [
415
- {
416
- ...formatBranchItem({
417
- name: "feature/login",
418
- type: "local",
419
- branchType: "feature",
420
- isCurrent: false,
421
- hasUnpushedCommits: false,
422
- worktree: {
423
- path: "/tmp/wt-login",
424
- locked: false,
425
- prunable: false,
426
- isAccessible: true,
427
- hasUncommittedChanges: false,
428
- },
429
- }),
430
- safeToCleanup: true,
431
- },
432
- {
433
- ...formatBranchItem({
434
- name: "feature/api",
435
- type: "local",
436
- branchType: "feature",
437
- isCurrent: false,
438
- hasUnpushedCommits: true,
439
- }),
440
- safeToCleanup: false,
441
- },
442
- ];
443
-
444
- const Wrapper = () => {
445
- const [selected, setSelected] = React.useState<string[]>([]);
446
- return (
447
- <BranchListScreen
448
- branches={branches}
449
- stats={mockStats}
450
- onSelect={onSelect}
451
- selectedBranches={selected}
452
- onToggleSelect={(name) =>
453
- setSelected((prev) =>
454
- prev.includes(name)
455
- ? prev.filter((n) => n !== name)
456
- : [...prev, name],
457
- )
458
- }
459
- />
460
- );
461
- };
462
-
463
- let renderResult: ReturnType<typeof inkRender>;
464
- await act(async () => {
465
- renderResult = inkRender(<Wrapper />, { stripAnsi: false });
466
- });
467
-
468
- const { stdin } = renderResult;
469
- await act(async () => {
470
- stdin.write(" ");
471
- });
472
-
473
- const frame = stripControlSequences(
474
- stripAnsi(renderResult.lastFrame() ?? ""),
475
- );
476
- expect(frame).toContain("[*] 🟢 🛡");
477
- expect(frame).toContain("feature/login");
478
- });
479
-
480
- describe("Filter Mode", () => {
481
- it("should always display filter input field", () => {
482
- // Note: Filter input is now always visible (no need to press 'f' key)
483
- const onSelect = vi.fn();
484
- const { container } = render(
485
- <BranchListScreen
486
- branches={mockBranches}
487
- stats={mockStats}
488
- onSelect={onSelect}
489
- />,
490
- );
491
-
492
- // Filter input field should be displayed by default
493
- expect(container.textContent).toContain("Filter:");
494
- });
495
-
496
- it("should enter filter mode when f key is pressed", () => {
497
- const onSelect = vi.fn();
498
- const { container } = render(
499
- <BranchListScreen
500
- branches={mockBranches}
501
- stats={mockStats}
502
- onSelect={onSelect}
503
- />,
504
- );
505
-
506
- // Initially should show prompt to press f
507
- expect(container.textContent).toContain("(press f to filter)");
508
-
509
- // Press 'f' key
510
- const fKeyEvent = new globalThis.window.KeyboardEvent("keydown", {
511
- key: "f",
512
- });
513
- document.dispatchEvent(fKeyEvent);
514
-
515
- // Filter input should be active (placeholder visible)
516
- // Select component should be disabled
517
- expect(container).toBeDefined();
518
- });
519
-
520
- it("should exit filter mode and return to branch selection when Esc is pressed in filter mode", () => {
521
- const onSelect = vi.fn();
522
- const { container } = render(
523
- <BranchListScreen
524
- branches={mockBranches}
525
- stats={mockStats}
526
- onSelect={onSelect}
527
- />,
528
- );
529
-
530
- // Enter filter mode first
531
- const fKeyEvent = new globalThis.window.KeyboardEvent("keydown", {
532
- key: "f",
533
- });
534
- document.dispatchEvent(fKeyEvent);
535
-
536
- // Press Escape
537
- const escKeyEvent = new globalThis.window.KeyboardEvent("keydown", {
538
- key: "Escape",
539
- });
540
- document.dispatchEvent(escKeyEvent);
541
-
542
- // Should return to branch selection mode
543
- // Select should be active, Input should be inactive
544
- expect(container.textContent).toContain("(press f to filter)");
545
- });
546
-
547
- it("should show branch list cursor highlight in filter mode", () => {
548
- process.env.FORCE_COLOR = "1";
549
- const onSelect = vi.fn();
550
- let renderResult: ReturnType<typeof inkRender>;
551
- act(() => {
552
- renderResult = inkRender(
553
- <BranchListScreen
554
- branches={mockBranches}
555
- stats={mockStats}
556
- onSelect={onSelect}
557
- testFilterMode={true}
558
- />,
559
- { stripAnsi: false },
560
- );
561
- });
562
-
563
- const frame = renderResult?.lastFrame() ?? "";
564
- // Should contain cyan background (cursor highlight) even in filter mode
565
- expect(frame).toContain("\u001b[46m");
566
- });
567
-
568
- it("should allow cursor movement with arrow keys in filter mode", () => {
569
- const onSelect = vi.fn();
570
- const { container } = render(
571
- <BranchListScreen
572
- branches={mockBranches}
573
- stats={mockStats}
574
- onSelect={onSelect}
575
- testFilterMode={true}
576
- />,
577
- );
578
-
579
- // Arrow keys should work in filter mode (Select component should not be disabled)
580
- // This test verifies that cursor movement is possible
581
- expect(container).toBeDefined();
582
- });
583
-
584
- it("should allow branch selection with Enter key in filter mode", () => {
585
- const onSelect = vi.fn();
586
- const { container } = render(
587
- <BranchListScreen
588
- branches={mockBranches}
589
- stats={mockStats}
590
- onSelect={onSelect}
591
- testFilterMode={true}
592
- />,
593
- );
594
-
595
- // Simulate Enter key (this will trigger onSelect if Select is enabled)
596
- // Note: Actual key event testing may not work in happy-dom environment
597
- // but the component should be set up to allow selection
598
- expect(container).toBeDefined();
599
- });
600
-
601
- it("should disable filter input cursor when in branch selection mode", () => {
602
- const onSelect = vi.fn();
603
- const { container } = render(
604
- <BranchListScreen
605
- branches={mockBranches}
606
- stats={mockStats}
607
- onSelect={onSelect}
608
- />,
609
- );
610
-
611
- // By default, should be in branch selection mode
612
- // Filter input cursor should be disabled/hidden
613
- expect(container).toBeDefined();
614
- });
615
-
616
- it("should filter branches in real-time as user types", () => {
617
- const onSelect = vi.fn();
618
- const branches: BranchItem[] = [
619
- ...mockBranches,
620
- {
621
- name: "bugfix/issue-123",
622
- type: "local",
623
- branchType: "bugfix",
624
- isCurrent: false,
625
- icons: ["🐛"],
626
- hasChanges: false,
627
- label: "🐛 bugfix/issue-123",
628
- value: "bugfix/issue-123",
629
- latestCommitTimestamp: 1_698_000_000,
630
- },
631
- ];
632
-
633
- const { container } = render(
634
- <BranchListScreen
635
- branches={branches}
636
- stats={mockStats}
637
- onSelect={onSelect}
638
- testFilterMode={true}
639
- testFilterQuery="feature"
640
- />,
641
- );
642
-
643
- // Only feature/test should be visible
644
- expect(container.textContent).toContain("feature/test");
645
- expect(container.textContent).not.toContain("bugfix/issue-123");
646
- });
647
-
648
- it("should clear filter query when Esc key is pressed (with query)", () => {
649
- // Note: Filter input remains visible, only the query is cleared
650
- const onSelect = vi.fn();
651
- const { container } = render(
652
- <BranchListScreen
653
- branches={mockBranches}
654
- stats={mockStats}
655
- onSelect={onSelect}
656
- />,
657
- );
658
-
659
- // Enter filter mode
660
- const fKeyEvent = new globalThis.window.KeyboardEvent("keydown", {
661
- key: "f",
662
- });
663
- document.dispatchEvent(fKeyEvent);
664
-
665
- // Type something in filter
666
- const input = container.querySelector("input");
667
- if (input) {
668
- input.value = "feature";
669
- input.dispatchEvent(new Event("input", { bubbles: true }));
670
- }
671
-
672
- // Press Escape (should clear query first)
673
- const escKeyEvent = new globalThis.window.KeyboardEvent("keydown", {
674
- key: "Escape",
675
- });
676
- document.dispatchEvent(escKeyEvent);
677
-
678
- // Filter input should still be visible, but query cleared
679
- // All branches should be visible again
680
- expect(container.textContent).toContain("Filter:");
681
- expect(container.textContent).toContain("main");
682
- expect(container.textContent).toContain("feature/test");
683
- });
684
-
685
- it("should exit filter mode when Esc is pressed with empty query", () => {
686
- const onSelect = vi.fn();
687
- const { container } = render(
688
- <BranchListScreen
689
- branches={mockBranches}
690
- stats={mockStats}
691
- onSelect={onSelect}
692
- />,
693
- );
694
-
695
- // Enter filter mode
696
- const fKeyEvent = new globalThis.window.KeyboardEvent("keydown", {
697
- key: "f",
698
- });
699
- document.dispatchEvent(fKeyEvent);
700
-
701
- // Press Escape with empty query (should exit filter mode)
702
- const escKeyEvent = new globalThis.window.KeyboardEvent("keydown", {
703
- key: "Escape",
704
- });
705
- document.dispatchEvent(escKeyEvent);
706
-
707
- // Should return to branch selection mode
708
- expect(container.textContent).toContain("(press f to filter)");
709
- });
710
-
711
- it("should perform case-insensitive search", () => {
712
- const onSelect = vi.fn();
713
- const { container } = render(
714
- <BranchListScreen
715
- branches={mockBranches}
716
- stats={mockStats}
717
- onSelect={onSelect}
718
- testFilterMode={true}
719
- testFilterQuery="FEATURE"
720
- />,
721
- );
722
-
723
- // "feature/test" should still be visible
724
- expect(container.textContent).toContain("feature/test");
725
- });
726
-
727
- it("should disable other key bindings (c, r, l) while typing in filter", () => {
728
- const onSelect = vi.fn();
729
- const onCleanupCommand = vi.fn();
730
- const onRefresh = vi.fn();
731
- const onOpenLogs = vi.fn();
732
-
733
- const inkApp = inkRender(
734
- <BranchListScreen
735
- branches={mockBranches}
736
- stats={mockStats}
737
- onSelect={onSelect}
738
- onCleanupCommand={onCleanupCommand}
739
- onRefresh={onRefresh}
740
- onOpenLogs={onOpenLogs}
741
- />,
742
- );
743
-
744
- act(() => {
745
- inkApp.stdin.write("f"); // enter filter mode
746
- });
747
-
748
- act(() => {
749
- inkApp.stdin.write("c");
750
- inkApp.stdin.write("r");
751
- inkApp.stdin.write("l");
752
- });
753
-
754
- expect(onCleanupCommand).not.toHaveBeenCalled();
755
- expect(onRefresh).not.toHaveBeenCalled();
756
- expect(onOpenLogs).not.toHaveBeenCalled();
757
-
758
- inkApp.unmount();
759
- });
760
-
761
- it("should open logs on l key", () => {
762
- const onSelect = vi.fn();
763
- const onOpenLogs = vi.fn();
764
-
765
- const inkApp = inkRender(
766
- <BranchListScreen
767
- branches={mockBranches}
768
- stats={mockStats}
769
- onSelect={onSelect}
770
- onOpenLogs={onOpenLogs}
771
- />,
772
- );
773
-
774
- act(() => {
775
- inkApp.stdin.write("l");
776
- });
777
-
778
- expect(onOpenLogs).toHaveBeenCalled();
779
-
780
- inkApp.unmount();
781
- });
782
-
783
- it("should display match count when filtering", () => {
784
- const onSelect = vi.fn();
785
- const branches: BranchItem[] = [
786
- ...mockBranches,
787
- {
788
- name: "feature/another",
789
- type: "local",
790
- branchType: "feature",
791
- isCurrent: false,
792
- icons: ["✨"],
793
- hasChanges: false,
794
- label: "✨ feature/another",
795
- value: "feature/another",
796
- latestCommitTimestamp: 1_698_000_000,
797
- },
798
- ];
799
-
800
- const { container } = render(
801
- <BranchListScreen
802
- branches={branches}
803
- stats={mockStats}
804
- onSelect={onSelect}
805
- testFilterMode={true}
806
- testFilterQuery="feature"
807
- />,
808
- );
809
-
810
- // Should show "Showing 2 of 3 branches"
811
- expect(container.textContent).toMatch(/Showing\s+2\s+of\s+3/i);
812
- });
813
-
814
- it("should show empty list when no branches match", () => {
815
- const onSelect = vi.fn();
816
- const { container } = render(
817
- <BranchListScreen
818
- branches={mockBranches}
819
- stats={mockStats}
820
- onSelect={onSelect}
821
- testFilterMode={true}
822
- testFilterQuery="nonexistent"
823
- />,
824
- );
825
-
826
- // Should show "Showing 0 of 2 branches"
827
- expect(container.textContent).toMatch(/Showing\s+0\s+of\s+2/i);
828
- });
829
-
830
- it("should search in PR titles when available", () => {
831
- const onSelect = vi.fn();
832
- const branchesWithPR: BranchItem[] = [
833
- ...mockBranches,
834
- {
835
- name: "feature/add-filter",
836
- type: "local",
837
- branchType: "feature",
838
- isCurrent: false,
839
- icons: ["✨", "🔀"],
840
- hasChanges: false,
841
- label: "✨ 🔀 feature/add-filter",
842
- value: "feature/add-filter",
843
- latestCommitTimestamp: 1_698_000_000,
844
- openPR: { number: 123, title: "Add search filter to branch list" },
845
- },
846
- ];
847
-
848
- const { container } = render(
849
- <BranchListScreen
850
- branches={branchesWithPR}
851
- stats={mockStats}
852
- onSelect={onSelect}
853
- testFilterMode={true}
854
- testFilterQuery="search"
855
- />,
856
- );
857
-
858
- // Branch with matching PR title should be visible
859
- expect(container.textContent).toContain("feature/add-filter");
860
- });
861
- });
862
-
863
- describe("Branch View Mode Toggle (TAB key)", () => {
864
- const mixedBranches: BranchItem[] = [
865
- {
866
- name: "main",
867
- type: "local",
868
- branchType: "main",
869
- isCurrent: true,
870
- icons: ["⚡"],
871
- hasChanges: false,
872
- label: "⚡ main",
873
- value: "main",
874
- latestCommitTimestamp: 1_700_000_000,
875
- },
876
- {
877
- name: "feature/test",
878
- type: "local",
879
- branchType: "feature",
880
- isCurrent: false,
881
- icons: ["✨"],
882
- hasChanges: false,
883
- label: "✨ feature/test",
884
- value: "feature/test",
885
- latestCommitTimestamp: 1_699_000_000,
886
- },
887
- {
888
- name: "origin/main",
889
- type: "remote",
890
- branchType: "main",
891
- isCurrent: false,
892
- icons: ["🌐"],
893
- hasChanges: false,
894
- label: "🌐 origin/main",
895
- value: "origin/main",
896
- remoteName: "origin/main",
897
- latestCommitTimestamp: 1_698_000_000,
898
- },
899
- {
900
- name: "origin/feature/remote-test",
901
- type: "remote",
902
- branchType: "feature",
903
- isCurrent: false,
904
- icons: ["🌐"],
905
- hasChanges: false,
906
- label: "🌐 origin/feature/remote-test",
907
- value: "origin/feature/remote-test",
908
- remoteName: "origin/feature/remote-test",
909
- latestCommitTimestamp: 1_697_000_000,
910
- },
911
- ];
912
-
913
- it("should default to 'all' view mode and display Mode: All in stats", () => {
914
- const onSelect = vi.fn();
915
- const { container } = render(
916
- <BranchListScreen
917
- branches={mixedBranches}
918
- stats={mockStats}
919
- onSelect={onSelect}
920
- />,
921
- );
922
-
923
- expect(container.textContent).toContain("Mode: All");
924
- });
925
-
926
- it("should filter to local branches only when view mode is 'local'", () => {
927
- const onSelect = vi.fn();
928
- const { container } = render(
929
- <BranchListScreen
930
- branches={mixedBranches}
931
- stats={mockStats}
932
- onSelect={onSelect}
933
- testViewMode="local"
934
- />,
935
- );
936
-
937
- expect(container.textContent).toContain("Mode: Local");
938
- expect(container.textContent).toContain("main");
939
- expect(container.textContent).toContain("feature/test");
940
- expect(container.textContent).not.toContain("origin/main");
941
- expect(container.textContent).not.toContain("origin/feature/remote-test");
942
- });
943
-
944
- it("should filter to remote branches only when view mode is 'remote'", () => {
945
- const onSelect = vi.fn();
946
- const { container } = render(
947
- <BranchListScreen
948
- branches={mixedBranches}
949
- stats={mockStats}
950
- onSelect={onSelect}
951
- testViewMode="remote"
952
- />,
953
- );
954
-
955
- expect(container.textContent).toContain("Mode: Remote");
956
- expect(container.textContent).not.toContain("feature/test");
957
- expect(container.textContent).toContain("origin/main");
958
- expect(container.textContent).toContain("origin/feature/remote-test");
959
- });
960
-
961
- it("should toggle view mode from all to local when TAB is pressed", () => {
962
- const onSelect = vi.fn();
963
- const onViewModeChange = vi.fn();
964
-
965
- const inkApp = inkRender(
966
- <BranchListScreen
967
- branches={mixedBranches}
968
- stats={mockStats}
969
- onSelect={onSelect}
970
- testOnViewModeChange={onViewModeChange}
971
- />,
972
- );
973
-
974
- act(() => {
975
- inkApp.stdin.write("\t"); // TAB key
976
- });
977
-
978
- expect(onViewModeChange).toHaveBeenCalledWith("local");
979
-
980
- inkApp.unmount();
981
- });
982
-
983
- it("should toggle view mode from local to remote when TAB is pressed", () => {
984
- const onSelect = vi.fn();
985
- const onViewModeChange = vi.fn();
986
-
987
- const inkApp = inkRender(
988
- <BranchListScreen
989
- branches={mixedBranches}
990
- stats={mockStats}
991
- onSelect={onSelect}
992
- testViewMode="local"
993
- testOnViewModeChange={onViewModeChange}
994
- />,
995
- );
996
-
997
- act(() => {
998
- inkApp.stdin.write("\t"); // TAB key
999
- });
1000
-
1001
- expect(onViewModeChange).toHaveBeenCalledWith("remote");
1002
-
1003
- inkApp.unmount();
1004
- });
1005
-
1006
- it("should toggle view mode from remote to all when TAB is pressed", () => {
1007
- const onSelect = vi.fn();
1008
- const onViewModeChange = vi.fn();
1009
-
1010
- const inkApp = inkRender(
1011
- <BranchListScreen
1012
- branches={mixedBranches}
1013
- stats={mockStats}
1014
- onSelect={onSelect}
1015
- testViewMode="remote"
1016
- testOnViewModeChange={onViewModeChange}
1017
- />,
1018
- );
1019
-
1020
- act(() => {
1021
- inkApp.stdin.write("\t"); // TAB key
1022
- });
1023
-
1024
- expect(onViewModeChange).toHaveBeenCalledWith("all");
1025
-
1026
- inkApp.unmount();
1027
- });
1028
-
1029
- it("should not toggle view mode when in filter mode", () => {
1030
- const onSelect = vi.fn();
1031
- const onViewModeChange = vi.fn();
1032
-
1033
- const inkApp = inkRender(
1034
- <BranchListScreen
1035
- branches={mixedBranches}
1036
- stats={mockStats}
1037
- onSelect={onSelect}
1038
- testFilterMode={true}
1039
- testOnViewModeChange={onViewModeChange}
1040
- />,
1041
- );
1042
-
1043
- act(() => {
1044
- inkApp.stdin.write("\t"); // TAB key
1045
- });
1046
-
1047
- expect(onViewModeChange).not.toHaveBeenCalled();
1048
-
1049
- inkApp.unmount();
1050
- });
1051
-
1052
- it("should combine view mode filter with search filter (AND condition)", () => {
1053
- const onSelect = vi.fn();
1054
- const { container } = render(
1055
- <BranchListScreen
1056
- branches={mixedBranches}
1057
- stats={mockStats}
1058
- onSelect={onSelect}
1059
- testViewMode="local"
1060
- testFilterQuery="feature"
1061
- />,
1062
- );
1063
-
1064
- // Only local branches matching "feature"
1065
- expect(container.textContent).toContain("feature/test");
1066
- expect(container.textContent).not.toContain("main");
1067
- expect(container.textContent).not.toContain("origin/feature/remote-test");
1068
- });
1069
- });
1070
- });