@akiojin/gwt 4.10.0 → 4.12.0

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