@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
@@ -0,0 +1,1823 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import { useKeyboard, useRenderer } from "@opentui/solid";
3
+ import {
4
+ createEffect,
5
+ createMemo,
6
+ createSignal,
7
+ onCleanup,
8
+ onMount,
9
+ } from "solid-js";
10
+ import type {
11
+ BranchItem,
12
+ BranchInfo,
13
+ CodingAgentId,
14
+ CleanupStatus,
15
+ InferenceLevel,
16
+ SelectedBranchState,
17
+ Statistics,
18
+ WorktreeInfo as UIWorktreeInfo,
19
+ } from "./types.js";
20
+ import type { FormattedLogEntry } from "../../logging/formatter.js";
21
+ import { BranchListScreen } from "./screens/solid/BranchListScreen.js";
22
+ import { HelpOverlay } from "./components/solid/HelpOverlay.js";
23
+ import {
24
+ WizardController,
25
+ type WizardResult,
26
+ } from "./components/solid/WizardController.js";
27
+ import {
28
+ SelectorScreen,
29
+ type SelectorItem,
30
+ } from "./screens/solid/SelectorScreen.js";
31
+ import { LogScreen } from "./screens/solid/LogScreen.js";
32
+ import { LogDetailScreen } from "./screens/solid/LogDetailScreen.js";
33
+ import { ErrorScreen } from "./screens/solid/ErrorScreen.js";
34
+ import { LoadingIndicatorScreen } from "./screens/solid/LoadingIndicator.js";
35
+ import { WorktreeCreateScreen } from "./screens/solid/WorktreeCreateScreen.js";
36
+ import { InputScreen } from "./screens/solid/InputScreen.js";
37
+ import { ConfirmScreen } from "./screens/solid/ConfirmScreen.js";
38
+ import { EnvironmentScreen } from "./screens/solid/EnvironmentScreen.js";
39
+ import { ProfileScreen } from "./screens/solid/ProfileScreen.js";
40
+ import { ProfileEnvScreen } from "./screens/solid/ProfileEnvScreen.js";
41
+ import { calculateStatistics } from "./utils/statisticsCalculator.js";
42
+ import { formatBranchItems } from "./utils/branchFormatter.js";
43
+ import { createLogger } from "../../logging/logger.js";
44
+
45
+ const logger = createLogger({ category: "app" });
46
+ import {
47
+ getDefaultInferenceForModel,
48
+ getDefaultModelOption,
49
+ normalizeModelId,
50
+ } from "./utils/modelOptions.js";
51
+ import {
52
+ resolveBaseBranchLabel,
53
+ resolveBaseBranchRef,
54
+ } from "./utils/baseBranch.js";
55
+ import {
56
+ getAllBranches,
57
+ getCurrentBranch,
58
+ getLocalBranches,
59
+ getRepositoryRoot,
60
+ deleteBranch,
61
+ } from "../../git.js";
62
+ import {
63
+ isProtectedBranchName,
64
+ getCleanupStatus,
65
+ listAdditionalWorktrees,
66
+ removeWorktree,
67
+ repairWorktrees,
68
+ type WorktreeInfo as WorktreeEntry,
69
+ } from "../../worktree.js";
70
+ import {
71
+ getConfig,
72
+ getLastToolUsageMap,
73
+ loadSession,
74
+ type ToolSessionEntry,
75
+ } from "../../config/index.js";
76
+ import { getAllCodingAgents } from "../../config/tools.js";
77
+ import {
78
+ buildLogFilePath,
79
+ getTodayLogDate,
80
+ readLogFileLines,
81
+ resolveLogDir,
82
+ } from "../../logging/reader.js";
83
+ import { parseLogLines } from "../../logging/formatter.js";
84
+ import { copyToClipboard } from "./utils/clipboard.js";
85
+ import { getPackageVersion } from "../../utils.js";
86
+ import {
87
+ createProfile,
88
+ deleteProfile,
89
+ loadProfiles,
90
+ setActiveProfile,
91
+ updateProfile,
92
+ } from "../../config/profiles.js";
93
+ import {
94
+ isValidProfileName,
95
+ type ProfilesConfig,
96
+ } from "../../types/profiles.js";
97
+ import { BRANCH_PREFIXES } from "../../config/constants.js";
98
+
99
+ export type ExecutionMode = "normal" | "continue" | "resume";
100
+
101
+ export interface SelectionResult {
102
+ branch: string;
103
+ displayName: string;
104
+ branchType: "local" | "remote";
105
+ remoteBranch?: string;
106
+ baseBranch?: string;
107
+ isNewBranch?: boolean;
108
+ tool: CodingAgentId;
109
+ mode: ExecutionMode;
110
+ skipPermissions: boolean;
111
+ model?: string | null;
112
+ inferenceLevel?: InferenceLevel;
113
+ sessionId?: string | null;
114
+ toolVersion?: string | null;
115
+ }
116
+
117
+ export type AppScreen =
118
+ | "branch-list"
119
+ | "tool-select"
120
+ | "mode-select"
121
+ | "skip-permissions"
122
+ | "log-list"
123
+ | "log-detail"
124
+ | "profile"
125
+ | "profile-env"
126
+ | "profile-input"
127
+ | "profile-confirm"
128
+ | "profile-os-env"
129
+ | "profile-error"
130
+ | "worktree-create"
131
+ | "loading"
132
+ | "error";
133
+
134
+ type ProfileInputMode = "create-profile" | "add-env" | "edit-env";
135
+ type ProfileConfirmMode = "delete-profile" | "delete-env";
136
+ type StatusColor = "cyan" | "green" | "yellow" | "red";
137
+
138
+ interface CleanupIndicator {
139
+ icon: string;
140
+ isSpinning?: boolean;
141
+ color?: StatusColor;
142
+ }
143
+
144
+ interface CleanupTask {
145
+ branch: string;
146
+ worktreePath: string | null;
147
+ cleanupType: "worktree-and-branch" | "branch-only";
148
+ isAccessible?: boolean;
149
+ }
150
+
151
+ export interface AppSolidProps {
152
+ onExit?: (result?: SelectionResult) => void;
153
+ loadingIndicatorDelay?: number;
154
+ initialScreen?: AppScreen;
155
+ version?: string | null;
156
+ workingDirectory?: string;
157
+ branches?: BranchItem[];
158
+ stats?: Statistics;
159
+ }
160
+
161
+ const DEFAULT_SCREEN: AppScreen = "branch-list";
162
+
163
+ const buildStats = (branches: BranchItem[]): Statistics =>
164
+ calculateStatistics(branches);
165
+
166
+ const applyCleanupStatus = (
167
+ items: BranchItem[],
168
+ statusByBranch: Map<string, CleanupStatus>,
169
+ ): BranchItem[] =>
170
+ items.map((branch) => {
171
+ const status = statusByBranch.get(branch.name);
172
+ if (!status) {
173
+ return { ...branch, safeToCleanup: false, isUnmerged: false };
174
+ }
175
+ const safeToCleanup =
176
+ status.hasUpstream && status.reasons.includes("no-diff-with-base");
177
+ const isUnmerged = status.hasUpstream && status.hasUniqueCommits;
178
+ const worktree = branch.worktree
179
+ ? {
180
+ ...branch.worktree,
181
+ ...(status.hasUncommittedChanges !== undefined
182
+ ? { hasUncommittedChanges: status.hasUncommittedChanges }
183
+ : {}),
184
+ }
185
+ : undefined;
186
+ const base: BranchItem = {
187
+ ...branch,
188
+ safeToCleanup,
189
+ isUnmerged,
190
+ hasUnpushedCommits: status.hasUnpushedCommits,
191
+ };
192
+ return worktree ? { ...base, worktree } : base;
193
+ });
194
+
195
+ const toLocalBranchName = (name: string): string => {
196
+ const segments = name.split("/");
197
+ if (segments.length <= 1) {
198
+ return name;
199
+ }
200
+ return segments.slice(1).join("/");
201
+ };
202
+
203
+ const toSelectedBranchState = (branch: BranchItem): SelectedBranchState => {
204
+ const isRemote = branch.type === "remote";
205
+ const baseName = isRemote ? toLocalBranchName(branch.name) : branch.name;
206
+ return {
207
+ name: baseName,
208
+ displayName: branch.name,
209
+ branchType: branch.type,
210
+ branchCategory: branch.branchType,
211
+ ...(isRemote ? { remoteBranch: branch.name } : {}),
212
+ };
213
+ };
214
+
215
+ export function AppSolid(props: AppSolidProps) {
216
+ const renderer = useRenderer();
217
+ let hasExited = false;
218
+
219
+ const exitApp = (result?: SelectionResult) => {
220
+ if (hasExited) return;
221
+ hasExited = true;
222
+ props.onExit?.(result);
223
+ renderer.destroy();
224
+ };
225
+
226
+ const [currentScreen, setCurrentScreen] = createSignal<AppScreen>(
227
+ props.initialScreen ?? DEFAULT_SCREEN,
228
+ );
229
+ const [screenStack, setScreenStack] = createSignal<AppScreen[]>([]);
230
+ const [helpVisible, setHelpVisible] = createSignal(false);
231
+ const [wizardVisible, setWizardVisible] = createSignal(false);
232
+
233
+ const [branchItems, setBranchItems] = createSignal<BranchItem[]>(
234
+ props.branches ?? [],
235
+ );
236
+ const [stats, setStats] = createSignal<Statistics>(
237
+ props.stats ?? buildStats(props.branches ?? []),
238
+ );
239
+ const [loading, setLoading] = createSignal(!props.branches);
240
+ const [error, setError] = createSignal<Error | null>(null);
241
+
242
+ const [toolItems, setToolItems] = createSignal<SelectorItem[]>([]);
243
+ const [toolError, setToolError] = createSignal<Error | null>(null);
244
+
245
+ const [version, setVersion] = createSignal<string | null>(
246
+ props.version ?? null,
247
+ );
248
+
249
+ const workingDirectory = createMemo(
250
+ () => props.workingDirectory ?? process.cwd(),
251
+ );
252
+
253
+ const [selectedBranch, setSelectedBranch] =
254
+ createSignal<SelectedBranchState | null>(null);
255
+ const [selectedTool, setSelectedTool] = createSignal<CodingAgentId | null>(
256
+ null,
257
+ );
258
+ const [selectedMode, setSelectedMode] = createSignal<ExecutionMode>("normal");
259
+ const [selectedBranches, setSelectedBranches] = createSignal<string[]>([]);
260
+ const [branchFooterMessage, setBranchFooterMessage] = createSignal<{
261
+ text: string;
262
+ isSpinning?: boolean;
263
+ color?: StatusColor;
264
+ } | null>(null);
265
+ const [branchInputLocked, setBranchInputLocked] = createSignal(false);
266
+ const [cleanupIndicators, setCleanupIndicators] = createSignal<
267
+ Record<string, CleanupIndicator>
268
+ >({});
269
+ const [cleanupStatusByBranch, setCleanupStatusByBranch] = createSignal<
270
+ Map<string, CleanupStatus>
271
+ >(new Map());
272
+ const [cleanupSafetyLoading, setCleanupSafetyLoading] = createSignal(false);
273
+ const [isNewBranch, setIsNewBranch] = createSignal(false);
274
+ const [newBranchBaseRef, setNewBranchBaseRef] = createSignal<string | null>(
275
+ null,
276
+ );
277
+ const [creationSource, setCreationSource] =
278
+ createSignal<SelectedBranchState | null>(null);
279
+ const [createBranchName, setCreateBranchName] = createSignal("", {
280
+ equals: false,
281
+ });
282
+ const [suppressCreateKey, setSuppressCreateKey] = createSignal<string | null>(
283
+ null,
284
+ );
285
+ const [defaultBaseBranch, setDefaultBaseBranch] = createSignal("main");
286
+
287
+ // セッション履歴(最終使用エージェントなど)
288
+ const [sessionHistory, setSessionHistory] = createSignal<ToolSessionEntry[]>(
289
+ [],
290
+ );
291
+
292
+ // 選択中ブランチの履歴をフィルタリング
293
+ const historyForBranch = createMemo(() => {
294
+ const history = sessionHistory();
295
+ const branch = selectedBranch();
296
+ if (!branch) return [];
297
+ // 選択中ブランチにマッチする履歴エントリを新しい順で返す
298
+ return history
299
+ .filter((entry) => entry.branch === branch.name)
300
+ .sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
301
+ });
302
+
303
+ const [logEntries, setLogEntries] = createSignal<FormattedLogEntry[]>([]);
304
+ const [logLoading, setLogLoading] = createSignal(false);
305
+ const [logError, setLogError] = createSignal<string | null>(null);
306
+ const [logSelectedEntry, setLogSelectedEntry] =
307
+ createSignal<FormattedLogEntry | null>(null);
308
+ const [logSelectedDate, _setLogSelectedDate] = createSignal<string | null>(
309
+ getTodayLogDate(),
310
+ );
311
+ const [logNotification, setLogNotification] = createSignal<{
312
+ message: string;
313
+ tone: "success" | "error";
314
+ } | null>(null);
315
+
316
+ const [profileItems, setProfileItems] = createSignal<
317
+ { name: string; displayName?: string; isActive?: boolean }[]
318
+ >([]);
319
+ const [activeProfile, setActiveProfileName] = createSignal<string | null>(
320
+ null,
321
+ );
322
+ const [profileError, setProfileError] = createSignal<Error | null>(null);
323
+ const [profilesConfig, setProfilesConfig] =
324
+ createSignal<ProfilesConfig | null>(null);
325
+ const [profileActionError, setProfileActionError] =
326
+ createSignal<Error | null>(null);
327
+ const [profileActionHint, setProfileActionHint] = createSignal<string | null>(
328
+ null,
329
+ );
330
+ const [selectedProfileName, setSelectedProfileName] = createSignal<
331
+ string | null
332
+ >(null);
333
+ const [profileInputValue, setProfileInputValue] = createSignal("", {
334
+ equals: false,
335
+ });
336
+ const [profileInputMode, setProfileInputMode] =
337
+ createSignal<ProfileInputMode>("create-profile");
338
+ const [profileInputSuppressKey, setProfileInputSuppressKey] = createSignal<
339
+ string | null
340
+ >(null);
341
+ const [profileEnvKey, setProfileEnvKey] = createSignal<string | null>(null);
342
+ const [profileConfirmMode, setProfileConfirmMode] =
343
+ createSignal<ProfileConfirmMode>("delete-profile");
344
+
345
+ const logDir = createMemo(() => resolveLogDir(workingDirectory()));
346
+ const selectedProfileConfig = createMemo(() => {
347
+ const name = selectedProfileName();
348
+ const config = profilesConfig();
349
+ if (!name || !config?.profiles?.[name]) {
350
+ return null;
351
+ }
352
+ return { name, profile: config.profiles[name] };
353
+ });
354
+ const profileEnvVariables = createMemo(() => {
355
+ const entry = selectedProfileConfig();
356
+ if (!entry) {
357
+ return [];
358
+ }
359
+ return Object.entries(entry.profile.env ?? {})
360
+ .map(([key, value]) => ({ key, value }))
361
+ .sort((a, b) => a.key.localeCompare(b.key));
362
+ });
363
+ const osEnvVariables = createMemo(() =>
364
+ Object.entries(process.env)
365
+ .filter(([, value]) => typeof value === "string")
366
+ .map(([key, value]) => ({ key, value: String(value) }))
367
+ .sort((a, b) => a.key.localeCompare(b.key)),
368
+ );
369
+
370
+ let cleanupSafetyRequestId = 0;
371
+ const refreshCleanupSafety = async () => {
372
+ const requestId = ++cleanupSafetyRequestId;
373
+ setCleanupSafetyLoading(true);
374
+ try {
375
+ const cleanupStatuses = await getCleanupStatus();
376
+ if (requestId !== cleanupSafetyRequestId) {
377
+ return;
378
+ }
379
+ const statusByBranch = new Map(
380
+ cleanupStatuses.map((status) => [status.branch, status]),
381
+ );
382
+ setCleanupStatusByBranch(statusByBranch);
383
+ setBranchItems((items) => applyCleanupStatus(items, statusByBranch));
384
+ } catch (err) {
385
+ if (requestId !== cleanupSafetyRequestId) {
386
+ return;
387
+ }
388
+ logger.warn({ err }, "Failed to refresh cleanup safety indicators");
389
+ const empty = new Map<string, CleanupStatus>();
390
+ setCleanupStatusByBranch(empty);
391
+ setBranchItems((items) => applyCleanupStatus(items, empty));
392
+ } finally {
393
+ if (requestId === cleanupSafetyRequestId) {
394
+ setCleanupSafetyLoading(false);
395
+ }
396
+ }
397
+ };
398
+
399
+ let logNotificationTimer: ReturnType<typeof setTimeout> | null = null;
400
+ let branchFooterTimer: ReturnType<typeof setTimeout> | null = null;
401
+ const BRANCH_LOAD_TIMEOUT_MS = 3000;
402
+ const BRANCH_FULL_LOAD_TIMEOUT_MS = 8000;
403
+
404
+ const isHelpKey = (key: {
405
+ name: string;
406
+ sequence: string;
407
+ ctrl: boolean;
408
+ meta: boolean;
409
+ super?: boolean;
410
+ hyper?: boolean;
411
+ option?: boolean;
412
+ }) => {
413
+ if (key.ctrl || key.meta || key.super || key.hyper || key.option) {
414
+ return false;
415
+ }
416
+ return key.name === "h" || key.sequence === "h" || key.sequence === "?";
417
+ };
418
+
419
+ useKeyboard((key) => {
420
+ if (key.repeated) {
421
+ return;
422
+ }
423
+
424
+ if (helpVisible()) {
425
+ if (key.name === "escape" || isHelpKey(key)) {
426
+ setHelpVisible(false);
427
+ }
428
+ return;
429
+ }
430
+
431
+ if (isHelpKey(key)) {
432
+ setHelpVisible(true);
433
+ }
434
+ });
435
+
436
+ const navigateTo = (screen: AppScreen) => {
437
+ setScreenStack((prev) => [...prev, currentScreen()]);
438
+ setCurrentScreen(screen);
439
+ };
440
+
441
+ const goBack = () => {
442
+ const stack = screenStack();
443
+ if (stack.length === 0) {
444
+ return;
445
+ }
446
+ const previous = stack[stack.length - 1] ?? DEFAULT_SCREEN;
447
+ setScreenStack(stack.slice(0, -1));
448
+
449
+ // branch-list に戻る場合は選択状態をリセット
450
+ if (previous === "branch-list") {
451
+ setSelectedBranch(null);
452
+ setSelectedTool(null);
453
+ setSelectedMode("normal");
454
+ setIsNewBranch(false);
455
+ setNewBranchBaseRef(null);
456
+ setCreationSource(null);
457
+ }
458
+
459
+ setCurrentScreen(previous);
460
+ };
461
+
462
+ const openProfileError = (err: unknown, hint: string) => {
463
+ setProfileActionError(err instanceof Error ? err : new Error(String(err)));
464
+ setProfileActionHint(hint);
465
+ navigateTo("profile-error");
466
+ };
467
+
468
+ const showLogNotification = (message: string, tone: "success" | "error") => {
469
+ setLogNotification({ message, tone });
470
+ if (logNotificationTimer) {
471
+ clearTimeout(logNotificationTimer);
472
+ }
473
+ logNotificationTimer = setTimeout(() => {
474
+ setLogNotification(null);
475
+ }, 2000);
476
+ };
477
+
478
+ const loadLogEntries = async (date: string | null) => {
479
+ const targetDate = date ?? getTodayLogDate();
480
+ setLogLoading(true);
481
+ setLogError(null);
482
+ try {
483
+ const filePath = buildLogFilePath(logDir(), targetDate);
484
+ const lines = await readLogFileLines(filePath);
485
+ const parsed = parseLogLines(lines, { limit: 100 });
486
+ setLogEntries(parsed);
487
+ } catch (err) {
488
+ setLogEntries([]);
489
+ setLogError(err instanceof Error ? err.message : "Failed to load logs");
490
+ } finally {
491
+ setLogLoading(false);
492
+ }
493
+ };
494
+
495
+ const refreshBranches = async () => {
496
+ setLoading(true);
497
+ setError(null);
498
+ void refreshCleanupSafety();
499
+ try {
500
+ const repoRoot = await getRepositoryRoot();
501
+ const worktreesPromise = listAdditionalWorktrees();
502
+ const localBranchesPromise = getLocalBranches(repoRoot);
503
+ const currentBranchPromise = getCurrentBranch(repoRoot);
504
+ const lastToolUsagePromise = getLastToolUsageMap(repoRoot);
505
+
506
+ const [localBranches, currentBranch, worktrees, lastToolUsageMap] =
507
+ await Promise.all([
508
+ withTimeout(localBranchesPromise, BRANCH_LOAD_TIMEOUT_MS).catch(
509
+ () => [],
510
+ ),
511
+ withTimeout(currentBranchPromise, BRANCH_LOAD_TIMEOUT_MS).catch(
512
+ () => null,
513
+ ),
514
+ withTimeout(worktreesPromise, BRANCH_LOAD_TIMEOUT_MS).catch(() => []),
515
+ withTimeout(lastToolUsagePromise, BRANCH_LOAD_TIMEOUT_MS).catch(
516
+ () => new Map<string, ToolSessionEntry>(),
517
+ ),
518
+ ]);
519
+
520
+ if (currentBranch) {
521
+ localBranches.forEach((branch) => {
522
+ if (branch.name === currentBranch) {
523
+ branch.isCurrent = true;
524
+ }
525
+ });
526
+ }
527
+
528
+ const initial = buildBranchList(
529
+ localBranches,
530
+ worktrees,
531
+ lastToolUsageMap,
532
+ );
533
+ const initialItems = applyCleanupStatus(
534
+ initial.items,
535
+ cleanupStatusByBranch(),
536
+ );
537
+ setBranchItems(initialItems);
538
+ setStats(buildStats(initialItems));
539
+
540
+ void (async () => {
541
+ const [branches, latestWorktrees] = await Promise.all([
542
+ withTimeout(
543
+ getAllBranches(repoRoot),
544
+ BRANCH_FULL_LOAD_TIMEOUT_MS,
545
+ ).catch(() => localBranches),
546
+ withTimeout(
547
+ listAdditionalWorktrees(),
548
+ BRANCH_FULL_LOAD_TIMEOUT_MS,
549
+ ).catch(() => worktrees),
550
+ ]);
551
+
552
+ const full = buildBranchList(
553
+ branches,
554
+ latestWorktrees,
555
+ lastToolUsageMap,
556
+ );
557
+ const fullItems = applyCleanupStatus(
558
+ full.items,
559
+ cleanupStatusByBranch(),
560
+ );
561
+ setBranchItems(fullItems);
562
+ setStats(buildStats(fullItems));
563
+ })();
564
+ } catch (err) {
565
+ setError(err instanceof Error ? err : new Error(String(err)));
566
+ } finally {
567
+ setLoading(false);
568
+ }
569
+ };
570
+
571
+ const refreshProfiles = async () => {
572
+ try {
573
+ const config = await loadProfiles();
574
+ setProfilesConfig(config);
575
+ const items = Object.entries(config.profiles ?? {}).map(
576
+ ([name, profile]) => ({
577
+ name,
578
+ displayName: profile.displayName,
579
+ isActive: config.activeProfile === name,
580
+ }),
581
+ );
582
+ items.sort((a, b) => a.name.localeCompare(b.name));
583
+ setProfileItems(items);
584
+ setActiveProfileName(config.activeProfile ?? null);
585
+ setProfileError(null);
586
+ } catch (err) {
587
+ setProfilesConfig(null);
588
+ setProfileItems([]);
589
+ setActiveProfileName(null);
590
+ setProfileError(err instanceof Error ? err : new Error(String(err)));
591
+ }
592
+ };
593
+
594
+ createEffect(() => {
595
+ if (props.branches) {
596
+ const statusByBranch = cleanupStatusByBranch();
597
+ const nextItems = applyCleanupStatus(props.branches, statusByBranch);
598
+ setBranchItems(nextItems);
599
+ setStats(props.stats ?? buildStats(nextItems));
600
+ setLoading(false);
601
+ }
602
+ });
603
+
604
+ onMount(() => {
605
+ if (!props.branches) {
606
+ void refreshBranches();
607
+ }
608
+ });
609
+
610
+ onMount(() => {
611
+ if (props.branches) {
612
+ void refreshCleanupSafety();
613
+ }
614
+ });
615
+
616
+ onMount(() => {
617
+ if (props.version === undefined) {
618
+ void getPackageVersion()
619
+ .then((value) => setVersion(value ?? null))
620
+ .catch(() => setVersion(null));
621
+ }
622
+ });
623
+
624
+ onMount(() => {
625
+ void refreshProfiles();
626
+ });
627
+
628
+ onMount(() => {
629
+ void getConfig()
630
+ .then((config) => setDefaultBaseBranch(config.defaultBaseBranch))
631
+ .catch(() => setDefaultBaseBranch("main"));
632
+ });
633
+
634
+ // セッション履歴をロード(最終使用エージェントなど)
635
+ onMount(() => {
636
+ void getRepositoryRoot()
637
+ .then((repoRoot) => loadSession(repoRoot))
638
+ .then((session) => {
639
+ if (session?.history) {
640
+ setSessionHistory(session.history);
641
+ }
642
+ })
643
+ .catch(() => setSessionHistory([]));
644
+ });
645
+
646
+ onMount(() => {
647
+ void getAllCodingAgents()
648
+ .then((agents) => {
649
+ setToolItems(
650
+ agents.map((agent) => ({
651
+ label: agent.displayName,
652
+ value: agent.id,
653
+ })),
654
+ );
655
+ })
656
+ .catch((err) => {
657
+ setToolItems([]);
658
+ setToolError(err instanceof Error ? err : new Error(String(err)));
659
+ });
660
+ });
661
+
662
+ createEffect(() => {
663
+ if (currentScreen() === "log-list") {
664
+ void loadLogEntries(logSelectedDate());
665
+ }
666
+ });
667
+
668
+ onCleanup(() => {
669
+ if (logNotificationTimer) {
670
+ clearTimeout(logNotificationTimer);
671
+ }
672
+ if (branchFooterTimer) {
673
+ clearTimeout(branchFooterTimer);
674
+ }
675
+ });
676
+
677
+ const showBranchFooterMessage = (
678
+ text: string,
679
+ color: StatusColor,
680
+ options?: { spinning?: boolean; timeoutMs?: number },
681
+ ) => {
682
+ if (branchFooterTimer) {
683
+ clearTimeout(branchFooterTimer);
684
+ branchFooterTimer = null;
685
+ }
686
+ setBranchFooterMessage({
687
+ text,
688
+ color,
689
+ ...(options?.spinning ? { isSpinning: true } : {}),
690
+ });
691
+ const timeout = options?.timeoutMs ?? 2000;
692
+ if (timeout > 0) {
693
+ branchFooterTimer = setTimeout(() => {
694
+ setBranchFooterMessage(null);
695
+ }, timeout);
696
+ }
697
+ };
698
+
699
+ const setCleanupIndicator = (
700
+ branchName: string,
701
+ indicator: CleanupIndicator | null,
702
+ ) => {
703
+ setCleanupIndicators((prev) => {
704
+ const next = { ...prev };
705
+ if (indicator) {
706
+ next[branchName] = indicator;
707
+ } else {
708
+ delete next[branchName];
709
+ }
710
+ return next;
711
+ });
712
+ };
713
+
714
+ const handleBranchSelect = (branch: BranchItem) => {
715
+ setSelectedBranch(toSelectedBranchState(branch));
716
+ setIsNewBranch(false);
717
+ setNewBranchBaseRef(null);
718
+ setCreationSource(null);
719
+ setSelectedTool(null);
720
+ setSelectedMode("normal");
721
+ // FR-044: ウィザードポップアップをレイヤー表示
722
+ setWizardVisible(true);
723
+ };
724
+
725
+ const handleQuickCreate = (branch: BranchItem | null) => {
726
+ // 選択中のブランチをベースにウィザードを開始
727
+ if (branch) {
728
+ setSelectedBranch(toSelectedBranchState(branch));
729
+ }
730
+ setCreationSource(branch ? toSelectedBranchState(branch) : null);
731
+ setCreateBranchName("");
732
+ setSuppressCreateKey("n");
733
+ // FR-044: ウィザードポップアップをレイヤー表示(アクション選択から開始)
734
+ setWizardVisible(true);
735
+ };
736
+
737
+ // FR-049: Escapeキーでウィザードをキャンセル
738
+ const handleWizardClose = () => {
739
+ setWizardVisible(false);
740
+ };
741
+
742
+ // ウィザード完了時の処理
743
+ const handleWizardComplete = (result: WizardResult) => {
744
+ setWizardVisible(false);
745
+
746
+ const branch = selectedBranch();
747
+ if (!branch) {
748
+ return;
749
+ }
750
+
751
+ // 新規ブランチ作成の場合、ブランチ名を設定
752
+ const isCreatingNew = result.isNewBranch ?? false;
753
+ const finalBranchName = result.branchName
754
+ ? `${result.branchType ?? ""}${result.branchName}`
755
+ : branch.name;
756
+
757
+ // 新規作成時は選択中のブランチがベースとなる
758
+ const baseBranchRef = isCreatingNew ? branch.name : null;
759
+ const normalizedModel = normalizeModelId(result.tool, result.model);
760
+
761
+ exitApp({
762
+ branch: finalBranchName,
763
+ displayName: branch.displayName,
764
+ branchType: branch.branchType,
765
+ ...(branch.remoteBranch ? { remoteBranch: branch.remoteBranch } : {}),
766
+ ...(isCreatingNew
767
+ ? {
768
+ isNewBranch: true,
769
+ ...(baseBranchRef ? { baseBranch: baseBranchRef } : {}),
770
+ }
771
+ : {}),
772
+ tool: result.tool,
773
+ mode: result.mode,
774
+ skipPermissions: result.skipPermissions,
775
+ ...(normalizedModel !== undefined ? { model: normalizedModel } : {}),
776
+ ...(result.reasoningLevel !== undefined
777
+ ? { inferenceLevel: result.reasoningLevel }
778
+ : {}),
779
+ ...(result.toolVersion !== undefined
780
+ ? { toolVersion: result.toolVersion }
781
+ : {}),
782
+ });
783
+ };
784
+
785
+ // FR-010: クイックスタートからのResume(前回設定で続きから)
786
+ const handleWizardResume = (entry: ToolSessionEntry) => {
787
+ setWizardVisible(false);
788
+
789
+ const branch = selectedBranch();
790
+ if (!branch) {
791
+ return;
792
+ }
793
+
794
+ const normalizedModel = normalizeModelId(
795
+ entry.toolId as CodingAgentId,
796
+ entry.model ?? undefined,
797
+ );
798
+
799
+ exitApp({
800
+ branch: branch.name,
801
+ displayName: branch.displayName,
802
+ branchType: branch.branchType,
803
+ ...(branch.remoteBranch ? { remoteBranch: branch.remoteBranch } : {}),
804
+ tool: entry.toolId as CodingAgentId,
805
+ mode: "continue",
806
+ skipPermissions: entry.skipPermissions ?? false,
807
+ ...(normalizedModel !== undefined ? { model: normalizedModel } : {}),
808
+ ...(entry.reasoningLevel
809
+ ? { inferenceLevel: entry.reasoningLevel as InferenceLevel }
810
+ : {}),
811
+ ...(entry.sessionId ? { sessionId: entry.sessionId } : {}),
812
+ ...(entry.toolVersion !== undefined
813
+ ? { toolVersion: entry.toolVersion }
814
+ : {}),
815
+ });
816
+ };
817
+
818
+ // FR-010: クイックスタートからのStartNew(前回設定で新規)
819
+ const handleWizardStartNew = (entry: ToolSessionEntry) => {
820
+ setWizardVisible(false);
821
+
822
+ const branch = selectedBranch();
823
+ if (!branch) {
824
+ return;
825
+ }
826
+
827
+ const normalizedModel = normalizeModelId(
828
+ entry.toolId as CodingAgentId,
829
+ entry.model ?? undefined,
830
+ );
831
+
832
+ exitApp({
833
+ branch: branch.name,
834
+ displayName: branch.displayName,
835
+ branchType: branch.branchType,
836
+ ...(branch.remoteBranch ? { remoteBranch: branch.remoteBranch } : {}),
837
+ tool: entry.toolId as CodingAgentId,
838
+ mode: "normal",
839
+ skipPermissions: entry.skipPermissions ?? false,
840
+ ...(normalizedModel !== undefined ? { model: normalizedModel } : {}),
841
+ ...(entry.reasoningLevel
842
+ ? { inferenceLevel: entry.reasoningLevel as InferenceLevel }
843
+ : {}),
844
+ ...(entry.toolVersion !== undefined
845
+ ? { toolVersion: entry.toolVersion }
846
+ : {}),
847
+ });
848
+ };
849
+
850
+ const buildSkipNotice = (
851
+ skip: {
852
+ unsafe: number;
853
+ protected: number;
854
+ remote: number;
855
+ current: number;
856
+ },
857
+ unsafeLabel: string,
858
+ ): string | null => {
859
+ const parts: string[] = [];
860
+ if (skip.unsafe > 0) {
861
+ parts.push(`${skip.unsafe} ${unsafeLabel}`);
862
+ }
863
+ if (skip.protected > 0) {
864
+ parts.push(`${skip.protected} protected`);
865
+ }
866
+ if (skip.remote > 0) {
867
+ parts.push(`${skip.remote} remote-only`);
868
+ }
869
+ if (skip.current > 0) {
870
+ parts.push(`${skip.current} current`);
871
+ }
872
+ if (parts.length === 0) {
873
+ return null;
874
+ }
875
+ return `Skipped branches: ${parts.join(", ")}.`;
876
+ };
877
+
878
+ const handleCleanupCommand = async () => {
879
+ if (branchInputLocked()) {
880
+ return;
881
+ }
882
+
883
+ const selection = selectedBranches();
884
+ const hasSelection = selection.length > 0;
885
+ const skipCounts = {
886
+ unsafe: 0,
887
+ protected: 0,
888
+ remote: 0,
889
+ current: 0,
890
+ };
891
+
892
+ setBranchInputLocked(true);
893
+ setCleanupIndicators({});
894
+ showBranchFooterMessage(
895
+ hasSelection
896
+ ? "Preparing cleanup..."
897
+ : "Scanning for cleanup candidates...",
898
+ "yellow",
899
+ { spinning: true, timeoutMs: 0 },
900
+ );
901
+
902
+ try {
903
+ const tasks: CleanupTask[] = [];
904
+
905
+ if (hasSelection) {
906
+ const branchMap = new Map(
907
+ branchItems().map((branch) => [branch.name, branch]),
908
+ );
909
+ for (const branchName of selection) {
910
+ const branch = branchMap.get(branchName);
911
+ if (!branch) {
912
+ continue;
913
+ }
914
+ if (branch.type === "remote") {
915
+ skipCounts.remote += 1;
916
+ continue;
917
+ }
918
+ if (isProtectedBranchName(branch.name)) {
919
+ skipCounts.protected += 1;
920
+ continue;
921
+ }
922
+ if (branch.isCurrent) {
923
+ skipCounts.current += 1;
924
+ continue;
925
+ }
926
+ const hasUncommitted =
927
+ branch.worktree?.hasUncommittedChanges === true;
928
+ const hasUnpushed = branch.hasUnpushedCommits === true;
929
+ if (hasUncommitted || hasUnpushed) {
930
+ skipCounts.unsafe += 1;
931
+ continue;
932
+ }
933
+ if (branch.safeToCleanup !== true) {
934
+ skipCounts.unsafe += 1;
935
+ continue;
936
+ }
937
+ const worktreePath = branch.worktree?.path ?? null;
938
+ const cleanupType: CleanupTask["cleanupType"] = worktreePath
939
+ ? "worktree-and-branch"
940
+ : "branch-only";
941
+ const baseTask = {
942
+ branch: branch.name,
943
+ worktreePath,
944
+ cleanupType,
945
+ };
946
+ const isAccessible = branch.worktree?.isAccessible;
947
+ tasks.push(
948
+ isAccessible === undefined
949
+ ? baseTask
950
+ : { ...baseTask, isAccessible },
951
+ );
952
+ }
953
+ } else {
954
+ // FR-028: 選択が0件の場合は警告を表示して処理をスキップ
955
+ setBranchInputLocked(false);
956
+ showBranchFooterMessage("No branches selected.", "yellow");
957
+ return;
958
+ }
959
+
960
+ const skipNotice = buildSkipNotice(
961
+ skipCounts,
962
+ hasSelection
963
+ ? "unsafe (uncommitted/unpushed, unmerged, or missing upstream)"
964
+ : "with uncommitted or unpushed changes",
965
+ );
966
+
967
+ if (tasks.length === 0) {
968
+ const baseMessage = hasSelection
969
+ ? "No eligible branches selected for cleanup."
970
+ : "No cleanup candidates found.";
971
+ showBranchFooterMessage(
972
+ skipNotice ? `${baseMessage} ${skipNotice}` : baseMessage,
973
+ "yellow",
974
+ );
975
+ return;
976
+ }
977
+
978
+ setCleanupIndicators(() => {
979
+ const next: Record<string, CleanupIndicator> = {};
980
+ tasks.forEach((task) => {
981
+ next[task.branch] = {
982
+ icon: "-",
983
+ isSpinning: true,
984
+ color: "yellow",
985
+ };
986
+ });
987
+ return next;
988
+ });
989
+
990
+ showBranchFooterMessage(
991
+ `Cleaning up ${tasks.length} branch(es)...`,
992
+ "yellow",
993
+ { spinning: true, timeoutMs: 0 },
994
+ );
995
+
996
+ let successCount = 0;
997
+ let failedCount = 0;
998
+
999
+ for (const task of tasks) {
1000
+ try {
1001
+ if (task.cleanupType === "worktree-and-branch" && task.worktreePath) {
1002
+ await removeWorktree(
1003
+ task.worktreePath,
1004
+ task.isAccessible === false,
1005
+ );
1006
+ }
1007
+ await deleteBranch(task.branch, true);
1008
+ setCleanupIndicator(task.branch, { icon: "v", color: "green" });
1009
+ successCount += 1;
1010
+ } catch {
1011
+ setCleanupIndicator(task.branch, { icon: "x", color: "red" });
1012
+ failedCount += 1;
1013
+ }
1014
+ }
1015
+
1016
+ setSelectedBranches([]);
1017
+ await refreshBranches();
1018
+
1019
+ const skippedTotal =
1020
+ skipCounts.unsafe +
1021
+ skipCounts.protected +
1022
+ skipCounts.remote +
1023
+ skipCounts.current;
1024
+ const summaryParts = [`${successCount} cleaned`];
1025
+ if (failedCount > 0) {
1026
+ summaryParts.push(`${failedCount} failed`);
1027
+ }
1028
+ if (skippedTotal > 0) {
1029
+ summaryParts.push(`${skippedTotal} skipped`);
1030
+ }
1031
+ const summary = `Cleanup finished: ${summaryParts.join(", ")}.`;
1032
+ const message = skipNotice ? `${summary} ${skipNotice}` : summary;
1033
+ showBranchFooterMessage(message, failedCount > 0 ? "red" : "green");
1034
+ } catch (err) {
1035
+ const errorMessage = err instanceof Error ? err.message : String(err);
1036
+ showBranchFooterMessage(`Cleanup failed: ${errorMessage}`, "red");
1037
+ } finally {
1038
+ setBranchInputLocked(false);
1039
+ }
1040
+ };
1041
+
1042
+ const handleRepairWorktrees = async () => {
1043
+ if (branchInputLocked()) {
1044
+ return;
1045
+ }
1046
+
1047
+ // FR-002/FR-007: 選択済みブランチのみを対象とする
1048
+ const selection = selectedBranches();
1049
+ if (selection.length === 0) {
1050
+ showBranchFooterMessage("No branches selected.", "yellow");
1051
+ return;
1052
+ }
1053
+
1054
+ // 選択されたブランチのうち、inaccessibleなものを修復対象とする
1055
+ const selectionSet = new Set(selection);
1056
+ const targets = branchItems()
1057
+ .filter(
1058
+ (branch) =>
1059
+ selectionSet.has(branch.name) &&
1060
+ branch.worktreeStatus === "inaccessible",
1061
+ )
1062
+ .map((branch) => branch.name);
1063
+
1064
+ if (targets.length === 0) {
1065
+ showBranchFooterMessage("No worktrees to repair.", "yellow");
1066
+ return;
1067
+ }
1068
+
1069
+ setBranchInputLocked(true);
1070
+ showBranchFooterMessage("Repairing worktrees...", "yellow", {
1071
+ spinning: true,
1072
+ timeoutMs: 0,
1073
+ });
1074
+ try {
1075
+ const result = await repairWorktrees(targets);
1076
+ await refreshBranches();
1077
+
1078
+ // エラー詳細をログに出力
1079
+ if (result.failedCount > 0) {
1080
+ logger.error(
1081
+ { failures: result.failures, targets },
1082
+ "Worktree repair failed for some branches",
1083
+ );
1084
+ }
1085
+
1086
+ const message =
1087
+ result.failedCount > 0
1088
+ ? `Repair finished: ${result.repairedCount} repaired, ${result.failedCount} failed.`
1089
+ : result.repairedCount > 0
1090
+ ? `Repaired ${result.repairedCount} worktree(s).`
1091
+ : "No worktrees repaired.";
1092
+ showBranchFooterMessage(
1093
+ message,
1094
+ result.failedCount > 0 ? "red" : "green",
1095
+ );
1096
+ } catch (err) {
1097
+ const errorMessage = err instanceof Error ? err.message : String(err);
1098
+ logger.error({ error: err, targets }, "Worktree repair threw an error");
1099
+ showBranchFooterMessage(`Repair failed: ${errorMessage}`, "red");
1100
+ } finally {
1101
+ setBranchInputLocked(false);
1102
+ }
1103
+ };
1104
+
1105
+ const openProfileCreate = () => {
1106
+ setProfileInputMode("create-profile");
1107
+ setProfileInputValue("");
1108
+ setProfileEnvKey(null);
1109
+ setProfileInputSuppressKey("n");
1110
+ navigateTo("profile-input");
1111
+ };
1112
+
1113
+ const openProfileDelete = (profile: { name: string }) => {
1114
+ setSelectedProfileName(profile.name);
1115
+ setProfileConfirmMode("delete-profile");
1116
+ navigateTo("profile-confirm");
1117
+ };
1118
+
1119
+ const openProfileEnv = (profile: { name: string }) => {
1120
+ setSelectedProfileName(profile.name);
1121
+ navigateTo("profile-env");
1122
+ };
1123
+
1124
+ const openProfileEnvAdd = () => {
1125
+ setProfileInputMode("add-env");
1126
+ setProfileInputValue("");
1127
+ setProfileEnvKey(null);
1128
+ setProfileInputSuppressKey("a");
1129
+ navigateTo("profile-input");
1130
+ };
1131
+
1132
+ const openProfileEnvEdit = (variable: { key: string; value: string }) => {
1133
+ setProfileInputMode("edit-env");
1134
+ setProfileEnvKey(variable.key);
1135
+ setProfileInputValue(variable.value);
1136
+ setProfileInputSuppressKey("e");
1137
+ navigateTo("profile-input");
1138
+ };
1139
+
1140
+ const openProfileEnvDelete = (variable: { key: string }) => {
1141
+ setProfileConfirmMode("delete-env");
1142
+ setProfileEnvKey(variable.key);
1143
+ navigateTo("profile-confirm");
1144
+ };
1145
+
1146
+ const handleProfileInputChange = (value: string) => {
1147
+ const suppressKey = profileInputSuppressKey();
1148
+ if (suppressKey && profileInputValue() === "" && value === suppressKey) {
1149
+ setProfileInputSuppressKey(null);
1150
+ setProfileInputValue("");
1151
+ return;
1152
+ }
1153
+ setProfileInputSuppressKey(null);
1154
+ setProfileInputValue(value);
1155
+ };
1156
+
1157
+ const submitProfileInput = async (value: string) => {
1158
+ const mode = profileInputMode();
1159
+ const trimmed = value.trim();
1160
+
1161
+ if (mode === "create-profile") {
1162
+ if (!trimmed) {
1163
+ openProfileError(
1164
+ new Error("Profile name is required."),
1165
+ "Invalid name.",
1166
+ );
1167
+ return;
1168
+ }
1169
+ if (!isValidProfileName(trimmed)) {
1170
+ openProfileError(
1171
+ new Error(`Invalid profile name: "${trimmed}".`),
1172
+ "Profile names must use lowercase letters, numbers, and hyphens.",
1173
+ );
1174
+ return;
1175
+ }
1176
+ try {
1177
+ await createProfile(trimmed, { displayName: trimmed, env: {} });
1178
+ await refreshProfiles();
1179
+ setSelectedProfileName(trimmed);
1180
+ setProfileInputValue("");
1181
+ setProfileInputSuppressKey(null);
1182
+ goBack();
1183
+ } catch (err) {
1184
+ openProfileError(err, "Unable to create profile.");
1185
+ }
1186
+ return;
1187
+ }
1188
+
1189
+ const entry = selectedProfileConfig();
1190
+ if (!entry) {
1191
+ openProfileError(
1192
+ new Error("Profile not selected."),
1193
+ "Profile not found.",
1194
+ );
1195
+ return;
1196
+ }
1197
+
1198
+ if (mode === "add-env") {
1199
+ const separatorIndex = trimmed.indexOf("=");
1200
+ if (separatorIndex <= 0) {
1201
+ openProfileError(
1202
+ new Error("Environment variable must be in KEY=VALUE format."),
1203
+ "Invalid environment variable.",
1204
+ );
1205
+ return;
1206
+ }
1207
+ const key = trimmed.slice(0, separatorIndex).trim();
1208
+ const valuePart = trimmed.slice(separatorIndex + 1);
1209
+ if (!key) {
1210
+ openProfileError(
1211
+ new Error("Environment variable key is required."),
1212
+ "Invalid environment variable.",
1213
+ );
1214
+ return;
1215
+ }
1216
+ const nextEnv = { ...(entry.profile.env ?? {}) };
1217
+ nextEnv[key] = valuePart;
1218
+ try {
1219
+ await updateProfile(entry.name, { env: nextEnv });
1220
+ await refreshProfiles();
1221
+ setProfileInputValue("");
1222
+ setProfileInputSuppressKey(null);
1223
+ goBack();
1224
+ } catch (err) {
1225
+ openProfileError(err, "Unable to update profile.");
1226
+ }
1227
+ return;
1228
+ }
1229
+
1230
+ if (mode === "edit-env") {
1231
+ const envKey = profileEnvKey();
1232
+ if (!envKey) {
1233
+ openProfileError(
1234
+ new Error("Environment variable not selected."),
1235
+ "Select a variable to edit.",
1236
+ );
1237
+ return;
1238
+ }
1239
+ const nextEnv = { ...(entry.profile.env ?? {}) };
1240
+ nextEnv[envKey] = value;
1241
+ try {
1242
+ await updateProfile(entry.name, { env: nextEnv });
1243
+ await refreshProfiles();
1244
+ setProfileInputValue("");
1245
+ setProfileInputSuppressKey(null);
1246
+ goBack();
1247
+ } catch (err) {
1248
+ openProfileError(err, "Unable to update profile.");
1249
+ }
1250
+ }
1251
+ };
1252
+
1253
+ const confirmProfileAction = async (confirmed: boolean) => {
1254
+ if (!confirmed) {
1255
+ goBack();
1256
+ return;
1257
+ }
1258
+
1259
+ const mode = profileConfirmMode();
1260
+ const entry = selectedProfileConfig();
1261
+
1262
+ if (mode === "delete-profile") {
1263
+ const name = selectedProfileName();
1264
+ if (!name) {
1265
+ openProfileError(
1266
+ new Error("Profile not selected."),
1267
+ "Profile not found.",
1268
+ );
1269
+ return;
1270
+ }
1271
+ try {
1272
+ await deleteProfile(name);
1273
+ await refreshProfiles();
1274
+ setProfileEnvKey(null);
1275
+ goBack();
1276
+ } catch (err) {
1277
+ openProfileError(err, "Unable to delete profile.");
1278
+ }
1279
+ return;
1280
+ }
1281
+
1282
+ if (mode === "delete-env") {
1283
+ if (!entry) {
1284
+ openProfileError(
1285
+ new Error("Profile not selected."),
1286
+ "Profile not found.",
1287
+ );
1288
+ return;
1289
+ }
1290
+ const envKey = profileEnvKey();
1291
+ if (!envKey) {
1292
+ openProfileError(
1293
+ new Error("Environment variable not selected."),
1294
+ "Select a variable to delete.",
1295
+ );
1296
+ return;
1297
+ }
1298
+ const nextEnv = { ...(entry.profile.env ?? {}) };
1299
+ delete nextEnv[envKey];
1300
+ try {
1301
+ await updateProfile(entry.name, { env: nextEnv });
1302
+ await refreshProfiles();
1303
+ setProfileEnvKey(null);
1304
+ goBack();
1305
+ } catch (err) {
1306
+ openProfileError(err, "Unable to update profile.");
1307
+ }
1308
+ }
1309
+ };
1310
+
1311
+ const toggleSelectedBranch = (branchName: string) => {
1312
+ setSelectedBranches((prev) => {
1313
+ const next = new Set(prev);
1314
+ if (next.has(branchName)) {
1315
+ next.delete(branchName);
1316
+ } else {
1317
+ next.add(branchName);
1318
+ }
1319
+ return Array.from(next);
1320
+ });
1321
+ };
1322
+
1323
+ const handleToolSelect = (item: SelectorItem) => {
1324
+ setSelectedTool(item.value as CodingAgentId);
1325
+ navigateTo("mode-select");
1326
+ };
1327
+
1328
+ const handleModeSelect = (item: SelectorItem) => {
1329
+ setSelectedMode(item.value as ExecutionMode);
1330
+ navigateTo("skip-permissions");
1331
+ };
1332
+
1333
+ const finalizeSelection = (skipPermissions: boolean) => {
1334
+ const branch = selectedBranch();
1335
+ const tool = selectedTool();
1336
+ if (!branch || !tool) {
1337
+ return;
1338
+ }
1339
+
1340
+ const defaultModel = getDefaultModelOption(tool);
1341
+ const resolvedModel = defaultModel?.id ?? null;
1342
+ const normalizedModel = normalizeModelId(tool, resolvedModel);
1343
+ const resolvedInference = getDefaultInferenceForModel(defaultModel);
1344
+ const baseRef = newBranchBaseRef();
1345
+
1346
+ exitApp({
1347
+ branch: branch.name,
1348
+ displayName: branch.displayName,
1349
+ branchType: branch.branchType,
1350
+ ...(branch.remoteBranch ? { remoteBranch: branch.remoteBranch } : {}),
1351
+ ...(isNewBranch()
1352
+ ? {
1353
+ isNewBranch: true,
1354
+ ...(baseRef ? { baseBranch: baseRef } : {}),
1355
+ }
1356
+ : {}),
1357
+ tool,
1358
+ mode: selectedMode(),
1359
+ skipPermissions,
1360
+ ...(normalizedModel !== undefined ? { model: normalizedModel } : {}),
1361
+ ...(resolvedInference !== undefined
1362
+ ? { inferenceLevel: resolvedInference }
1363
+ : {}),
1364
+ });
1365
+ };
1366
+
1367
+ const renderCurrentScreen = () => {
1368
+ const screen = currentScreen();
1369
+
1370
+ if (screen === "branch-list") {
1371
+ const cleanupUI = {
1372
+ indicators: cleanupIndicators(),
1373
+ footerMessage: branchFooterMessage(),
1374
+ inputLocked: branchInputLocked(),
1375
+ safetyLoading: cleanupSafetyLoading(),
1376
+ };
1377
+ return (
1378
+ <BranchListScreen
1379
+ branches={branchItems()}
1380
+ stats={stats()}
1381
+ onSelect={handleBranchSelect}
1382
+ onQuit={() => exitApp(undefined)}
1383
+ onCleanupCommand={handleCleanupCommand}
1384
+ onRefresh={refreshBranches}
1385
+ onRepairWorktrees={handleRepairWorktrees}
1386
+ loading={loading()}
1387
+ error={error()}
1388
+ loadingIndicatorDelay={props.loadingIndicatorDelay ?? 0}
1389
+ lastUpdated={stats().lastUpdated}
1390
+ version={version()}
1391
+ workingDirectory={workingDirectory()}
1392
+ activeProfile={activeProfile()}
1393
+ onOpenLogs={() => navigateTo("log-list")}
1394
+ onOpenProfiles={() => navigateTo("profile")}
1395
+ selectedBranches={selectedBranches()}
1396
+ onToggleSelect={toggleSelectedBranch}
1397
+ onCreateBranch={handleQuickCreate}
1398
+ cleanupUI={cleanupUI}
1399
+ helpVisible={helpVisible()}
1400
+ wizardVisible={wizardVisible()}
1401
+ />
1402
+ );
1403
+ }
1404
+
1405
+ if (screen === "tool-select") {
1406
+ if (toolError()) {
1407
+ return (
1408
+ <ErrorScreen
1409
+ error={toolError() as Error}
1410
+ onBack={goBack}
1411
+ hint="Unable to load available tools."
1412
+ />
1413
+ );
1414
+ }
1415
+ return (
1416
+ <SelectorScreen
1417
+ title="Select tool"
1418
+ items={toolItems()}
1419
+ onSelect={handleToolSelect}
1420
+ onBack={goBack}
1421
+ helpVisible={helpVisible()}
1422
+ />
1423
+ );
1424
+ }
1425
+
1426
+ if (screen === "mode-select") {
1427
+ return (
1428
+ <SelectorScreen
1429
+ title="Execution mode"
1430
+ items={[
1431
+ { label: "Normal", value: "normal" },
1432
+ { label: "Continue", value: "continue" },
1433
+ { label: "Resume", value: "resume" },
1434
+ ]}
1435
+ onSelect={handleModeSelect}
1436
+ onBack={goBack}
1437
+ helpVisible={helpVisible()}
1438
+ />
1439
+ );
1440
+ }
1441
+
1442
+ if (screen === "skip-permissions") {
1443
+ return (
1444
+ <SelectorScreen
1445
+ title="Skip permission prompts?"
1446
+ items={[
1447
+ { label: "Yes", value: "true" },
1448
+ { label: "No", value: "false" },
1449
+ ]}
1450
+ onSelect={(item) => finalizeSelection(item.value === "true")}
1451
+ onBack={goBack}
1452
+ helpVisible={helpVisible()}
1453
+ />
1454
+ );
1455
+ }
1456
+
1457
+ if (screen === "log-list") {
1458
+ return (
1459
+ <LogScreen
1460
+ entries={logEntries()}
1461
+ loading={logLoading()}
1462
+ error={logError()}
1463
+ onBack={goBack}
1464
+ onSelect={(entry) => {
1465
+ setLogSelectedEntry(entry);
1466
+ navigateTo("log-detail");
1467
+ }}
1468
+ onCopy={async (entry) => {
1469
+ try {
1470
+ await copyToClipboard(entry.json);
1471
+ showLogNotification("Copied to clipboard.", "success");
1472
+ } catch {
1473
+ showLogNotification("Failed to copy to clipboard.", "error");
1474
+ }
1475
+ }}
1476
+ notification={logNotification()}
1477
+ version={version()}
1478
+ selectedDate={logSelectedDate()}
1479
+ helpVisible={helpVisible()}
1480
+ />
1481
+ );
1482
+ }
1483
+
1484
+ if (screen === "log-detail") {
1485
+ return (
1486
+ <LogDetailScreen
1487
+ entry={logSelectedEntry()}
1488
+ onBack={goBack}
1489
+ onCopy={async (entry) => {
1490
+ try {
1491
+ await copyToClipboard(entry.json);
1492
+ showLogNotification("Copied to clipboard.", "success");
1493
+ } catch {
1494
+ showLogNotification("Failed to copy to clipboard.", "error");
1495
+ }
1496
+ }}
1497
+ notification={logNotification()}
1498
+ version={version()}
1499
+ helpVisible={helpVisible()}
1500
+ />
1501
+ );
1502
+ }
1503
+
1504
+ if (screen === "profile") {
1505
+ if (profileError()) {
1506
+ return (
1507
+ <ErrorScreen
1508
+ error={profileError() as Error}
1509
+ onBack={goBack}
1510
+ hint="Unable to load profiles."
1511
+ />
1512
+ );
1513
+ }
1514
+ return (
1515
+ <ProfileScreen
1516
+ profiles={profileItems()}
1517
+ version={version()}
1518
+ helpVisible={helpVisible()}
1519
+ onCreate={openProfileCreate}
1520
+ onDelete={openProfileDelete}
1521
+ onEdit={openProfileEnv}
1522
+ onSelect={(profile) => {
1523
+ void setActiveProfile(profile.name)
1524
+ .then(() => {
1525
+ void refreshProfiles();
1526
+ })
1527
+ .catch((err) => {
1528
+ openProfileError(err, "Unable to set active profile.");
1529
+ });
1530
+ }}
1531
+ onBack={goBack}
1532
+ />
1533
+ );
1534
+ }
1535
+
1536
+ if (screen === "profile-env") {
1537
+ const entry = selectedProfileConfig();
1538
+ if (!entry) {
1539
+ return (
1540
+ <ErrorScreen
1541
+ error="Profile not found."
1542
+ onBack={goBack}
1543
+ hint="Select a profile before editing."
1544
+ />
1545
+ );
1546
+ }
1547
+ return (
1548
+ <ProfileEnvScreen
1549
+ profileName={entry.name}
1550
+ variables={profileEnvVariables()}
1551
+ onAdd={openProfileEnvAdd}
1552
+ onEdit={openProfileEnvEdit}
1553
+ onDelete={openProfileEnvDelete}
1554
+ onViewOsEnv={() => navigateTo("profile-os-env")}
1555
+ onBack={goBack}
1556
+ version={version()}
1557
+ helpVisible={helpVisible()}
1558
+ />
1559
+ );
1560
+ }
1561
+
1562
+ if (screen === "profile-input") {
1563
+ const mode = profileInputMode();
1564
+ const envKey = profileEnvKey();
1565
+ const message =
1566
+ mode === "create-profile"
1567
+ ? "New profile name"
1568
+ : mode === "add-env"
1569
+ ? "Add environment variable"
1570
+ : `Edit value for ${envKey ?? "(unknown)"}`;
1571
+ const label =
1572
+ mode === "create-profile"
1573
+ ? "Profile name"
1574
+ : mode === "add-env"
1575
+ ? "KEY=VALUE"
1576
+ : "Value";
1577
+ const placeholder =
1578
+ mode === "create-profile"
1579
+ ? "development"
1580
+ : mode === "add-env"
1581
+ ? "MY_VAR=value"
1582
+ : undefined;
1583
+
1584
+ return (
1585
+ <InputScreen
1586
+ message={message}
1587
+ value={profileInputValue()}
1588
+ onChange={handleProfileInputChange}
1589
+ onSubmit={(value) => void submitProfileInput(value)}
1590
+ onCancel={() => {
1591
+ setProfileInputSuppressKey(null);
1592
+ goBack();
1593
+ }}
1594
+ label={label}
1595
+ {...(placeholder !== undefined ? { placeholder } : {})}
1596
+ width={32}
1597
+ helpVisible={helpVisible()}
1598
+ />
1599
+ );
1600
+ }
1601
+
1602
+ if (screen === "profile-confirm") {
1603
+ const mode = profileConfirmMode();
1604
+ const profileName = selectedProfileName();
1605
+ const envKey = profileEnvKey();
1606
+ const message =
1607
+ mode === "delete-profile"
1608
+ ? `Delete profile ${profileName ?? "(unknown)"}?`
1609
+ : `Delete ${envKey ?? "(unknown)"}?`;
1610
+
1611
+ return (
1612
+ <ConfirmScreen
1613
+ message={message}
1614
+ onConfirm={(confirmed) => void confirmProfileAction(confirmed)}
1615
+ defaultNo
1616
+ helpVisible={helpVisible()}
1617
+ />
1618
+ );
1619
+ }
1620
+
1621
+ if (screen === "profile-os-env") {
1622
+ const highlightKeys = profileEnvVariables().map(
1623
+ (variable) => variable.key,
1624
+ );
1625
+ return (
1626
+ <EnvironmentScreen
1627
+ variables={osEnvVariables()}
1628
+ highlightKeys={highlightKeys}
1629
+ onBack={goBack}
1630
+ version={version()}
1631
+ helpVisible={helpVisible()}
1632
+ />
1633
+ );
1634
+ }
1635
+
1636
+ if (screen === "profile-error") {
1637
+ return (
1638
+ <ErrorScreen
1639
+ error={profileActionError() ?? "Profile error"}
1640
+ onBack={() => {
1641
+ setProfileActionError(null);
1642
+ setProfileActionHint(null);
1643
+ goBack();
1644
+ }}
1645
+ {...(profileActionHint()
1646
+ ? { hint: profileActionHint() as string }
1647
+ : {})}
1648
+ helpVisible={helpVisible()}
1649
+ />
1650
+ );
1651
+ }
1652
+
1653
+ if (screen === "worktree-create") {
1654
+ const baseBranchRef = resolveBaseBranchRef(creationSource(), null, () =>
1655
+ defaultBaseBranch(),
1656
+ );
1657
+ const baseBranchLabel = resolveBaseBranchLabel(
1658
+ creationSource(),
1659
+ null,
1660
+ () => defaultBaseBranch(),
1661
+ );
1662
+
1663
+ return (
1664
+ <WorktreeCreateScreen
1665
+ branchName={createBranchName()}
1666
+ baseBranch={baseBranchLabel}
1667
+ version={version()}
1668
+ helpVisible={helpVisible()}
1669
+ onChange={(value) => {
1670
+ const suppressKey = suppressCreateKey();
1671
+ if (
1672
+ suppressKey &&
1673
+ createBranchName() === "" &&
1674
+ value === suppressKey
1675
+ ) {
1676
+ setSuppressCreateKey(null);
1677
+ setCreateBranchName("");
1678
+ return;
1679
+ }
1680
+ setSuppressCreateKey(null);
1681
+ setCreateBranchName(value);
1682
+ }}
1683
+ onSubmit={(value, branchType) => {
1684
+ const trimmed = value.trim();
1685
+ if (!trimmed) {
1686
+ return;
1687
+ }
1688
+ // Add prefix based on selected branch type
1689
+ const prefixKey =
1690
+ branchType.toUpperCase() as keyof typeof BRANCH_PREFIXES;
1691
+ const prefix = BRANCH_PREFIXES[prefixKey] ?? "";
1692
+ const fullBranchName = `${prefix}${trimmed}`;
1693
+
1694
+ setSelectedBranch({
1695
+ name: fullBranchName,
1696
+ displayName: fullBranchName,
1697
+ branchType: "local",
1698
+ branchCategory: branchType,
1699
+ });
1700
+ setIsNewBranch(true);
1701
+ setNewBranchBaseRef(baseBranchRef);
1702
+ setSelectedTool(null);
1703
+ setSelectedMode("normal");
1704
+ setSuppressCreateKey(null);
1705
+ navigateTo("tool-select");
1706
+ }}
1707
+ onCancel={() => {
1708
+ setSuppressCreateKey(null);
1709
+ goBack();
1710
+ }}
1711
+ />
1712
+ );
1713
+ }
1714
+
1715
+ if (screen === "loading") {
1716
+ return (
1717
+ <LoadingIndicatorScreen
1718
+ message="Loading..."
1719
+ delay={props.loadingIndicatorDelay ?? 0}
1720
+ />
1721
+ );
1722
+ }
1723
+
1724
+ if (screen === "error") {
1725
+ return (
1726
+ <ErrorScreen
1727
+ error={error() ?? "Unknown error"}
1728
+ helpVisible={helpVisible()}
1729
+ />
1730
+ );
1731
+ }
1732
+
1733
+ return (
1734
+ <ErrorScreen
1735
+ error={`Unknown screen: ${screen}`}
1736
+ onBack={goBack}
1737
+ helpVisible={helpVisible()}
1738
+ />
1739
+ );
1740
+ };
1741
+
1742
+ return (
1743
+ <>
1744
+ {renderCurrentScreen()}
1745
+ <HelpOverlay visible={helpVisible()} context={currentScreen()} />
1746
+ {/* FR-044: ウィザードポップアップをレイヤー表示 */}
1747
+ <WizardController
1748
+ visible={wizardVisible()}
1749
+ selectedBranchName={selectedBranch()?.name ?? ""}
1750
+ history={historyForBranch()}
1751
+ onClose={handleWizardClose}
1752
+ onComplete={handleWizardComplete}
1753
+ onResume={handleWizardResume}
1754
+ onStartNew={handleWizardStartNew}
1755
+ />
1756
+ </>
1757
+ );
1758
+ }
1759
+ const withTimeout = async <T,>(
1760
+ promise: Promise<T>,
1761
+ timeoutMs: number,
1762
+ ): Promise<T> =>
1763
+ new Promise<T>((resolve, reject) => {
1764
+ const timer = setTimeout(() => {
1765
+ reject(new Error("timeout"));
1766
+ }, timeoutMs);
1767
+
1768
+ promise
1769
+ .then((value) => {
1770
+ clearTimeout(timer);
1771
+ resolve(value);
1772
+ })
1773
+ .catch((err) => {
1774
+ clearTimeout(timer);
1775
+ reject(err);
1776
+ });
1777
+ });
1778
+
1779
+ const buildBranchList = (
1780
+ branches: BranchInfo[],
1781
+ worktrees: WorktreeEntry[],
1782
+ lastToolUsageMap?: Map<string, ToolSessionEntry>,
1783
+ ) => {
1784
+ const localBranchNames = new Set(
1785
+ branches.filter((branch) => branch.type === "local").map((b) => b.name),
1786
+ );
1787
+
1788
+ const filtered = branches.filter((branch) => {
1789
+ if (branch.type === "remote") {
1790
+ const remoteName = branch.name.replace(/^origin\//, "");
1791
+ return !localBranchNames.has(remoteName);
1792
+ }
1793
+ return true;
1794
+ });
1795
+
1796
+ const worktreeMap = new Map<string, UIWorktreeInfo>();
1797
+ for (const worktree of worktrees) {
1798
+ worktreeMap.set(worktree.branch, {
1799
+ path: worktree.path,
1800
+ locked: worktree.locked ?? false,
1801
+ prunable: worktree.prunable ?? false,
1802
+ isAccessible: worktree.isAccessible ?? true,
1803
+ ...(worktree.hasUncommittedChanges !== undefined
1804
+ ? { hasUncommittedChanges: worktree.hasUncommittedChanges }
1805
+ : {}),
1806
+ });
1807
+ }
1808
+
1809
+ const enriched = filtered.map((branch) => {
1810
+ const lastToolUsage = lastToolUsageMap?.get(branch.name);
1811
+ const baseBranch = lastToolUsage ? { ...branch, lastToolUsage } : branch;
1812
+ if (branch.type === "local") {
1813
+ const worktree = worktreeMap.get(branch.name);
1814
+ if (worktree) {
1815
+ return { ...baseBranch, worktree };
1816
+ }
1817
+ }
1818
+ return baseBranch;
1819
+ });
1820
+
1821
+ const items = formatBranchItems(enriched, worktreeMap);
1822
+ return { items, worktreeMap };
1823
+ };