@akiojin/gwt 2.13.0 → 2.14.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 (295) hide show
  1. package/README.ja.md +33 -0
  2. package/README.md +31 -0
  3. package/dist/claude.d.ts.map +1 -1
  4. package/dist/claude.js +17 -11
  5. package/dist/claude.js.map +1 -1
  6. package/dist/cli/ui/components/App.d.ts +0 -6
  7. package/dist/cli/ui/components/App.d.ts.map +1 -1
  8. package/dist/cli/ui/components/App.js +27 -88
  9. package/dist/cli/ui/components/App.js.map +1 -1
  10. package/dist/cli/ui/components/common/Select.js +2 -2
  11. package/dist/cli/ui/components/common/Select.js.map +1 -1
  12. package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
  13. package/dist/cli/ui/components/screens/BranchListScreen.js +14 -2
  14. package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
  15. package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts.map +1 -1
  16. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js +3 -3
  17. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js.map +1 -1
  18. package/dist/cli/ui/utils/continueSession.d.ts.map +1 -1
  19. package/dist/cli/ui/utils/continueSession.js +1 -1
  20. package/dist/cli/ui/utils/continueSession.js.map +1 -1
  21. package/dist/client/assets/index-DPWWHorC.js +72 -0
  22. package/dist/client/assets/index-DsDNCy5f.css +1 -0
  23. package/dist/client/index.html +2 -2
  24. package/dist/codex.d.ts.map +1 -1
  25. package/dist/codex.js +21 -11
  26. package/dist/codex.js.map +1 -1
  27. package/dist/config/builtin-tools.d.ts.map +1 -1
  28. package/dist/config/builtin-tools.js +3 -2
  29. package/dist/config/builtin-tools.js.map +1 -1
  30. package/dist/config/shared-env.d.ts +41 -0
  31. package/dist/config/shared-env.d.ts.map +1 -0
  32. package/dist/config/shared-env.js +114 -0
  33. package/dist/config/shared-env.js.map +1 -0
  34. package/dist/gemini.d.ts.map +1 -1
  35. package/dist/gemini.js +20 -17
  36. package/dist/gemini.js.map +1 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +49 -7
  39. package/dist/index.js.map +1 -1
  40. package/dist/logging/logger.d.ts.map +1 -1
  41. package/dist/logging/logger.js +4 -1
  42. package/dist/logging/logger.js.map +1 -1
  43. package/dist/qwen.d.ts.map +1 -1
  44. package/dist/qwen.js +15 -11
  45. package/dist/qwen.js.map +1 -1
  46. package/dist/services/aiToolResolver.d.ts +41 -0
  47. package/dist/services/aiToolResolver.d.ts.map +1 -0
  48. package/dist/services/aiToolResolver.js +194 -0
  49. package/dist/services/aiToolResolver.js.map +1 -0
  50. package/dist/services/customToolResolver.d.ts +10 -0
  51. package/dist/services/customToolResolver.d.ts.map +1 -0
  52. package/dist/services/customToolResolver.js +71 -0
  53. package/dist/services/customToolResolver.js.map +1 -0
  54. package/dist/shared/aiToolConstants.d.ts +9 -0
  55. package/dist/shared/aiToolConstants.d.ts.map +1 -0
  56. package/dist/shared/aiToolConstants.js +29 -0
  57. package/dist/shared/aiToolConstants.js.map +1 -0
  58. package/dist/types/tools.d.ts +12 -0
  59. package/dist/types/tools.d.ts.map +1 -1
  60. package/dist/utils/prompt.d.ts.map +1 -1
  61. package/dist/utils/prompt.js.map +1 -1
  62. package/dist/utils/session.d.ts.map +1 -1
  63. package/dist/utils/session.js +15 -6
  64. package/dist/utils/session.js.map +1 -1
  65. package/dist/utils/terminal.d.ts +12 -3
  66. package/dist/utils/terminal.d.ts.map +1 -1
  67. package/dist/utils/terminal.js +5 -34
  68. package/dist/utils/terminal.js.map +1 -1
  69. package/dist/utils/webui.d.ts +8 -0
  70. package/dist/utils/webui.d.ts.map +1 -0
  71. package/dist/utils/webui.js +35 -0
  72. package/dist/utils/webui.js.map +1 -0
  73. package/dist/web/client/src/components/AIToolLaunchModal.d.ts +9 -0
  74. package/dist/web/client/src/components/AIToolLaunchModal.d.ts.map +1 -0
  75. package/dist/web/client/src/components/AIToolLaunchModal.js +363 -0
  76. package/dist/web/client/src/components/AIToolLaunchModal.js.map +1 -0
  77. package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
  78. package/dist/web/client/src/components/BranchGraph.js +46 -49
  79. package/dist/web/client/src/components/BranchGraph.js.map +1 -1
  80. package/dist/web/client/src/components/CustomToolForm.d.ts +23 -0
  81. package/dist/web/client/src/components/CustomToolForm.d.ts.map +1 -0
  82. package/dist/web/client/src/components/CustomToolForm.js +209 -0
  83. package/dist/web/client/src/components/CustomToolForm.js.map +1 -0
  84. package/dist/web/client/src/components/CustomToolList.d.ts +10 -0
  85. package/dist/web/client/src/components/CustomToolList.d.ts.map +1 -0
  86. package/dist/web/client/src/components/CustomToolList.js +57 -0
  87. package/dist/web/client/src/components/CustomToolList.js.map +1 -0
  88. package/dist/web/client/src/components/EnvEditor.d.ts.map +1 -1
  89. package/dist/web/client/src/components/EnvEditor.js +33 -26
  90. package/dist/web/client/src/components/EnvEditor.js.map +1 -1
  91. package/dist/web/client/src/components/EnvironmentEditor.d.ts +17 -0
  92. package/dist/web/client/src/components/EnvironmentEditor.d.ts.map +1 -0
  93. package/dist/web/client/src/components/EnvironmentEditor.js +22 -0
  94. package/dist/web/client/src/components/EnvironmentEditor.js.map +1 -0
  95. package/dist/web/client/src/components/Terminal.d.ts.map +1 -1
  96. package/dist/web/client/src/components/Terminal.js +10 -3
  97. package/dist/web/client/src/components/Terminal.js.map +1 -1
  98. package/dist/web/client/src/components/branch-detail/BranchInfoCards.d.ts +10 -0
  99. package/dist/web/client/src/components/branch-detail/BranchInfoCards.d.ts.map +1 -0
  100. package/dist/web/client/src/components/branch-detail/BranchInfoCards.js +104 -0
  101. package/dist/web/client/src/components/branch-detail/BranchInfoCards.js.map +1 -0
  102. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.d.ts +22 -0
  103. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.d.ts.map +1 -0
  104. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.js +79 -0
  105. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.js.map +1 -0
  106. package/dist/web/client/src/components/branch-detail/TerminalPanel.d.ts +11 -0
  107. package/dist/web/client/src/components/branch-detail/TerminalPanel.d.ts.map +1 -0
  108. package/dist/web/client/src/components/branch-detail/TerminalPanel.js +32 -0
  109. package/dist/web/client/src/components/branch-detail/TerminalPanel.js.map +1 -0
  110. package/dist/web/client/src/components/branch-detail/ToolLauncher.d.ts +40 -0
  111. package/dist/web/client/src/components/branch-detail/ToolLauncher.d.ts.map +1 -0
  112. package/dist/web/client/src/components/branch-detail/ToolLauncher.js +147 -0
  113. package/dist/web/client/src/components/branch-detail/ToolLauncher.js.map +1 -0
  114. package/dist/web/client/src/components/branch-detail/index.d.ts +5 -0
  115. package/dist/web/client/src/components/branch-detail/index.d.ts.map +1 -0
  116. package/dist/web/client/src/components/branch-detail/index.js +5 -0
  117. package/dist/web/client/src/components/branch-detail/index.js.map +1 -0
  118. package/dist/web/client/src/components/common/BranchCard.d.ts +17 -0
  119. package/dist/web/client/src/components/common/BranchCard.d.ts.map +1 -0
  120. package/dist/web/client/src/components/common/BranchCard.js +36 -0
  121. package/dist/web/client/src/components/common/BranchCard.js.map +1 -0
  122. package/dist/web/client/src/components/common/MetricCard.d.ts +10 -0
  123. package/dist/web/client/src/components/common/MetricCard.d.ts.map +1 -0
  124. package/dist/web/client/src/components/common/MetricCard.js +10 -0
  125. package/dist/web/client/src/components/common/MetricCard.js.map +1 -0
  126. package/dist/web/client/src/components/common/PageHeader.d.ts +12 -0
  127. package/dist/web/client/src/components/common/PageHeader.d.ts.map +1 -0
  128. package/dist/web/client/src/components/common/PageHeader.js +14 -0
  129. package/dist/web/client/src/components/common/PageHeader.js.map +1 -0
  130. package/dist/web/client/src/components/common/SearchInput.d.ts +14 -0
  131. package/dist/web/client/src/components/common/SearchInput.d.ts.map +1 -0
  132. package/dist/web/client/src/components/common/SearchInput.js +15 -0
  133. package/dist/web/client/src/components/common/SearchInput.js.map +1 -0
  134. package/dist/web/client/src/components/common/StatusBadge.d.ts +10 -0
  135. package/dist/web/client/src/components/common/StatusBadge.d.ts.map +1 -0
  136. package/dist/web/client/src/components/common/StatusBadge.js +15 -0
  137. package/dist/web/client/src/components/common/StatusBadge.js.map +1 -0
  138. package/dist/web/client/src/components/common/index.d.ts +6 -0
  139. package/dist/web/client/src/components/common/index.d.ts.map +1 -0
  140. package/dist/web/client/src/components/common/index.js +6 -0
  141. package/dist/web/client/src/components/common/index.js.map +1 -0
  142. package/dist/web/client/src/components/ui/alert.d.ts +9 -0
  143. package/dist/web/client/src/components/ui/alert.d.ts.map +1 -0
  144. package/dist/web/client/src/components/ui/alert.js +25 -0
  145. package/dist/web/client/src/components/ui/alert.js.map +1 -0
  146. package/dist/web/client/src/components/ui/badge.d.ts +10 -0
  147. package/dist/web/client/src/components/ui/badge.d.ts.map +1 -0
  148. package/dist/web/client/src/components/ui/badge.js +25 -0
  149. package/dist/web/client/src/components/ui/badge.js.map +1 -0
  150. package/dist/web/client/src/components/ui/button.d.ts +12 -0
  151. package/dist/web/client/src/components/ui/button.d.ts.map +1 -0
  152. package/dist/web/client/src/components/ui/button.js +33 -0
  153. package/dist/web/client/src/components/ui/button.js.map +1 -0
  154. package/dist/web/client/src/components/ui/card.d.ts +9 -0
  155. package/dist/web/client/src/components/ui/card.d.ts.map +1 -0
  156. package/dist/web/client/src/components/ui/card.js +16 -0
  157. package/dist/web/client/src/components/ui/card.js.map +1 -0
  158. package/dist/web/client/src/components/ui/index.d.ts +8 -0
  159. package/dist/web/client/src/components/ui/index.d.ts.map +1 -0
  160. package/dist/web/client/src/components/ui/index.js +8 -0
  161. package/dist/web/client/src/components/ui/index.js.map +1 -0
  162. package/dist/web/client/src/components/ui/input.d.ts +4 -0
  163. package/dist/web/client/src/components/ui/input.d.ts.map +1 -0
  164. package/dist/web/client/src/components/ui/input.js +8 -0
  165. package/dist/web/client/src/components/ui/input.js.map +1 -0
  166. package/dist/web/client/src/components/ui/select.d.ts +14 -0
  167. package/dist/web/client/src/components/ui/select.d.ts.map +1 -0
  168. package/dist/web/client/src/components/ui/select.js +39 -0
  169. package/dist/web/client/src/components/ui/select.js.map +1 -0
  170. package/dist/web/client/src/components/ui/table.d.ts +11 -0
  171. package/dist/web/client/src/components/ui/table.d.ts.map +1 -0
  172. package/dist/web/client/src/components/ui/table.js +21 -0
  173. package/dist/web/client/src/components/ui/table.js.map +1 -0
  174. package/dist/web/client/src/hooks/useSessions.d.ts.map +1 -1
  175. package/dist/web/client/src/hooks/useSessions.js +6 -1
  176. package/dist/web/client/src/hooks/useSessions.js.map +1 -1
  177. package/dist/web/client/src/lib/utils.d.ts +7 -0
  178. package/dist/web/client/src/lib/utils.d.ts.map +1 -0
  179. package/dist/web/client/src/lib/utils.js +10 -0
  180. package/dist/web/client/src/lib/utils.js.map +1 -0
  181. package/dist/web/client/src/lib/websocket.d.ts +7 -0
  182. package/dist/web/client/src/lib/websocket.d.ts.map +1 -1
  183. package/dist/web/client/src/lib/websocket.js +44 -0
  184. package/dist/web/client/src/lib/websocket.js.map +1 -1
  185. package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
  186. package/dist/web/client/src/pages/BranchDetailPage.js +113 -361
  187. package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
  188. package/dist/web/client/src/pages/BranchListPage.d.ts.map +1 -1
  189. package/dist/web/client/src/pages/BranchListPage.js +89 -127
  190. package/dist/web/client/src/pages/BranchListPage.js.map +1 -1
  191. package/dist/web/client/src/pages/ConfigManagementPage.d.ts.map +1 -1
  192. package/dist/web/client/src/pages/ConfigManagementPage.js +46 -41
  193. package/dist/web/client/src/pages/ConfigManagementPage.js.map +1 -1
  194. package/dist/web/client/src/pages/ConfigPage.d.ts +3 -0
  195. package/dist/web/client/src/pages/ConfigPage.d.ts.map +1 -0
  196. package/dist/web/client/src/pages/ConfigPage.js +216 -0
  197. package/dist/web/client/src/pages/ConfigPage.js.map +1 -0
  198. package/dist/web/client/vite.config.d.ts.map +1 -1
  199. package/dist/web/client/vite.config.js +8 -1
  200. package/dist/web/client/vite.config.js.map +1 -1
  201. package/dist/web/server/index.d.ts +24 -2
  202. package/dist/web/server/index.d.ts.map +1 -1
  203. package/dist/web/server/index.js +46 -15
  204. package/dist/web/server/index.js.map +1 -1
  205. package/dist/web/server/pty/manager.d.ts +12 -10
  206. package/dist/web/server/pty/manager.d.ts.map +1 -1
  207. package/dist/web/server/pty/manager.js +76 -43
  208. package/dist/web/server/pty/manager.js.map +1 -1
  209. package/dist/web/server/routes/sessions.d.ts.map +1 -1
  210. package/dist/web/server/routes/sessions.js +35 -2
  211. package/dist/web/server/routes/sessions.js.map +1 -1
  212. package/dist/web/server/routes/worktrees.js +2 -2
  213. package/dist/web/server/routes/worktrees.js.map +1 -1
  214. package/dist/web/server/services/worktrees.d.ts.map +1 -1
  215. package/dist/web/server/services/worktrees.js +7 -1
  216. package/dist/web/server/services/worktrees.js.map +1 -1
  217. package/dist/web/server/tray.d.ts +24 -0
  218. package/dist/web/server/tray.d.ts.map +1 -0
  219. package/dist/web/server/tray.js +79 -0
  220. package/dist/web/server/tray.js.map +1 -0
  221. package/dist/web/server/websocket/handler.d.ts +18 -2
  222. package/dist/web/server/websocket/handler.d.ts.map +1 -1
  223. package/dist/web/server/websocket/handler.js +82 -9
  224. package/dist/web/server/websocket/handler.js.map +1 -1
  225. package/package.json +15 -2
  226. package/src/claude.ts +26 -15
  227. package/src/cli/ui/__tests__/components/common/Select.test.tsx +11 -0
  228. package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +17 -1
  229. package/src/cli/ui/__tests__/components/screens/BranchQuickStartScreen.test.tsx +4 -4
  230. package/src/cli/ui/components/App.tsx +33 -133
  231. package/src/cli/ui/components/common/Select.tsx +2 -2
  232. package/src/cli/ui/components/screens/BranchListScreen.tsx +43 -23
  233. package/src/cli/ui/components/screens/BranchQuickStartScreen.tsx +41 -46
  234. package/src/cli/ui/utils/continueSession.ts +1 -7
  235. package/src/codex.ts +31 -22
  236. package/src/config/builtin-tools.ts +6 -2
  237. package/src/config/shared-env.ts +139 -0
  238. package/src/gemini.ts +35 -22
  239. package/src/index.ts +54 -7
  240. package/src/logging/logger.ts +5 -2
  241. package/src/qwen.ts +28 -19
  242. package/src/services/aiToolResolver.ts +276 -0
  243. package/src/services/customToolResolver.ts +98 -0
  244. package/src/shared/aiToolConstants.ts +30 -0
  245. package/src/trayicon.d.ts +30 -0
  246. package/src/types/tools.ts +15 -0
  247. package/src/utils/prompt.ts +15 -9
  248. package/src/utils/session.ts +80 -26
  249. package/src/utils/terminal.ts +11 -41
  250. package/src/utils/webui.ts +43 -0
  251. package/src/web/client/components.json +21 -0
  252. package/src/web/client/src/components/AIToolLaunchModal.tsx +575 -0
  253. package/src/web/client/src/components/BranchGraph.tsx +95 -75
  254. package/src/web/client/src/components/CustomToolForm.tsx +386 -0
  255. package/src/web/client/src/components/CustomToolList.tsx +119 -0
  256. package/src/web/client/src/components/EnvEditor.tsx +91 -81
  257. package/src/web/client/src/components/EnvironmentEditor.tsx +97 -0
  258. package/src/web/client/src/components/Terminal.tsx +11 -3
  259. package/src/web/client/src/components/branch-detail/BranchInfoCards.tsx +179 -0
  260. package/src/web/client/src/components/branch-detail/SessionHistoryTable.tsx +181 -0
  261. package/src/web/client/src/components/branch-detail/TerminalPanel.tsx +92 -0
  262. package/src/web/client/src/components/branch-detail/ToolLauncher.tsx +327 -0
  263. package/src/web/client/src/components/branch-detail/index.ts +4 -0
  264. package/src/web/client/src/components/common/BranchCard.tsx +117 -0
  265. package/src/web/client/src/components/common/MetricCard.tsx +22 -0
  266. package/src/web/client/src/components/common/PageHeader.tsx +44 -0
  267. package/src/web/client/src/components/common/SearchInput.tsx +40 -0
  268. package/src/web/client/src/components/common/StatusBadge.tsx +37 -0
  269. package/src/web/client/src/components/common/index.ts +5 -0
  270. package/src/web/client/src/components/ui/alert.tsx +63 -0
  271. package/src/web/client/src/components/ui/badge.tsx +44 -0
  272. package/src/web/client/src/components/ui/button.tsx +57 -0
  273. package/src/web/client/src/components/ui/card.tsx +82 -0
  274. package/src/web/client/src/components/ui/index.ts +32 -0
  275. package/src/web/client/src/components/ui/input.tsx +21 -0
  276. package/src/web/client/src/components/ui/select.tsx +156 -0
  277. package/src/web/client/src/components/ui/table.tsx +119 -0
  278. package/src/web/client/src/hooks/useSessions.ts +10 -1
  279. package/src/web/client/src/index.css +46 -816
  280. package/src/web/client/src/lib/utils.ts +10 -0
  281. package/src/web/client/src/lib/websocket.ts +48 -1
  282. package/src/web/client/src/pages/BranchDetailPage.tsx +222 -694
  283. package/src/web/client/src/pages/BranchListPage.tsx +190 -236
  284. package/src/web/client/src/pages/ConfigManagementPage.tsx +94 -76
  285. package/src/web/client/src/pages/ConfigPage.tsx +362 -0
  286. package/src/web/client/vite.config.ts +8 -1
  287. package/src/web/server/index.ts +72 -15
  288. package/src/web/server/pty/manager.ts +128 -55
  289. package/src/web/server/routes/sessions.ts +59 -7
  290. package/src/web/server/routes/worktrees.ts +3 -3
  291. package/src/web/server/services/worktrees.ts +12 -4
  292. package/src/web/server/tray.ts +93 -0
  293. package/src/web/server/websocket/handler.ts +119 -13
  294. package/dist/client/assets/index-DeNwPosA.css +0 -1
  295. package/dist/client/assets/index-Dl798X5w.js +0 -32
@@ -9,7 +9,17 @@ import {
9
9
  } from "../hooks/useSessions";
10
10
  import { useConfig } from "../hooks/useConfig";
11
11
  import { ApiError } from "../lib/api";
12
- import { Terminal } from "../components/Terminal";
12
+ import { PageHeader } from "@/components/common/PageHeader";
13
+ import { Button } from "@/components/ui/button";
14
+ import { Badge } from "@/components/ui/badge";
15
+ import { Alert, AlertDescription } from "@/components/ui/alert";
16
+ import {
17
+ SessionHistoryTable,
18
+ ToolLauncher,
19
+ BranchInfoCards,
20
+ TerminalPanel,
21
+ type SelectableTool,
22
+ } from "@/components/branch-detail";
13
23
  import type {
14
24
  Branch,
15
25
  CustomAITool,
@@ -19,44 +29,6 @@ import type {
19
29
  type ToolType = "claude-code" | "codex-cli" | "custom";
20
30
  type ToolMode = "normal" | "continue" | "resume";
21
31
 
22
- type SelectableTool =
23
- | { id: "claude-code"; label: string; target: "claude" }
24
- | { id: "codex-cli"; label: string; target: "codex" }
25
- | { id: string; label: string; target: "custom"; definition: CustomAITool };
26
-
27
- interface ToolSummary {
28
- command: string;
29
- defaultArgs?: string[] | null;
30
- modeArgs?: {
31
- normal?: string[];
32
- continue?: string[];
33
- resume?: string[];
34
- };
35
- permissionSkipArgs?: string[] | null;
36
- }
37
-
38
- const BUILTIN_TOOL_SUMMARIES: Record<string, ToolSummary> = {
39
- "claude-code": {
40
- command: "claude",
41
- defaultArgs: [],
42
- modeArgs: {
43
- normal: [],
44
- continue: ["-c"],
45
- resume: ["-r"],
46
- },
47
- permissionSkipArgs: ["--dangerously-skip-permissions"],
48
- },
49
- "codex-cli": {
50
- command: "codex",
51
- defaultArgs: ["--auto-approve", "--verbose"],
52
- modeArgs: {
53
- normal: [],
54
- continue: ["resume", "--last"],
55
- resume: ["resume"],
56
- },
57
- },
58
- };
59
-
60
32
  interface BannerState {
61
33
  type: "success" | "error" | "info";
62
34
  message: string;
@@ -73,15 +45,6 @@ const MERGE_STATUS_LABEL: Record<Branch["mergeStatus"], string> = {
73
45
  unknown: "状態不明",
74
46
  };
75
47
 
76
- const MERGE_STATUS_TONE: Record<
77
- Branch["mergeStatus"],
78
- "success" | "warning" | "muted"
79
- > = {
80
- merged: "success",
81
- unmerged: "warning",
82
- unknown: "muted",
83
- };
84
-
85
48
  export function BranchDetailPage() {
86
49
  const { branchName } = useParams<{ branchName: string }>();
87
50
  const decodedBranchName = branchName ? decodeURIComponent(branchName) : "";
@@ -115,58 +78,76 @@ export function BranchDetailPage() {
115
78
  [branch?.commitDate],
116
79
  );
117
80
 
81
+ // Handle fullscreen body overflow
118
82
  useEffect(() => {
119
- if (!isTerminalFullscreen) {
120
- return undefined;
121
- }
122
-
83
+ if (!isTerminalFullscreen) return undefined;
123
84
  const previousOverflow = document.body.style.overflow;
124
85
  document.body.style.overflow = "hidden";
125
-
126
86
  return () => {
127
87
  document.body.style.overflow = previousOverflow;
128
88
  };
129
89
  }, [isTerminalFullscreen]);
130
90
 
91
+ // Loading state
131
92
  if (isLoading) {
132
93
  return (
133
- <div className="app-shell">
134
- <div className="page-state page-state--centered">
135
- <h1>読み込み中</h1>
136
- <p>ブランチ情報を取得しています...</p>
137
- </div>
94
+ <div className="min-h-screen bg-background">
95
+ <PageHeader
96
+ eyebrow="BRANCH DETAIL"
97
+ title="読み込み中..."
98
+ subtitle="ブランチ情報を取得しています"
99
+ />
100
+ <main className="mx-auto max-w-7xl px-6 py-8">
101
+ <div className="flex items-center justify-center py-20">
102
+ <div className="text-center">
103
+ <div className="mb-4 text-4xl">⏳</div>
104
+ <p className="text-muted-foreground">Loading branch...</p>
105
+ </div>
106
+ </div>
107
+ </main>
138
108
  </div>
139
109
  );
140
110
  }
141
111
 
112
+ // Error state
142
113
  if (error) {
143
114
  return (
144
- <div className="app-shell">
145
- <div className="page-state page-state--centered">
146
- <h1>ブランチの取得に失敗しました</h1>
147
- <p>{error instanceof Error ? error.message : "未知のエラーです"}</p>
148
- <Link to="/" className="button button--ghost">
149
- ブランチ一覧に戻る
150
- </Link>
151
- </div>
115
+ <div className="min-h-screen bg-background">
116
+ <PageHeader eyebrow="BRANCH DETAIL" title="エラー" />
117
+ <main className="mx-auto max-w-7xl px-6 py-8">
118
+ <Alert variant="destructive">
119
+ <AlertDescription>
120
+ {error instanceof Error ? error.message : "未知のエラーです"}
121
+ </AlertDescription>
122
+ </Alert>
123
+ <div className="mt-4">
124
+ <Button variant="ghost" asChild>
125
+ <Link to="/">← ブランチ一覧に戻る</Link>
126
+ </Button>
127
+ </div>
128
+ </main>
152
129
  </div>
153
130
  );
154
131
  }
155
132
 
133
+ // Not found state
156
134
  if (!branch) {
157
135
  return (
158
- <div className="app-shell">
159
- <div className="page-state page-state--centered">
160
- <h1>Branch not found</h1>
161
- <p>指定されたブランチは存在しません。</p>
162
- <Link to="/" className="button button--ghost">
163
- ブランチ一覧に戻る
164
- </Link>
165
- </div>
136
+ <div className="min-h-screen bg-background">
137
+ <PageHeader eyebrow="BRANCH DETAIL" title="Branch not found" />
138
+ <main className="mx-auto max-w-7xl px-6 py-8">
139
+ <p className="mb-4 text-muted-foreground">
140
+ 指定されたブランチは存在しません。
141
+ </p>
142
+ <Button variant="ghost" asChild>
143
+ <Link to="/">← ブランチ一覧に戻る</Link>
144
+ </Button>
145
+ </main>
166
146
  </div>
167
147
  );
168
148
  }
169
149
 
150
+ // Computed values
170
151
  const canStartSession = Boolean(branch.worktreePath);
171
152
  const divergenceInfo = branch.divergence ?? null;
172
153
  const hasBlockingDivergence = Boolean(
@@ -174,13 +155,14 @@ export function BranchDetailPage() {
174
155
  );
175
156
  const needsRemoteSync = Boolean(
176
157
  branch.worktreePath &&
177
- divergenceInfo &&
178
- divergenceInfo.behind > 0 &&
179
- divergenceInfo.ahead === 0 &&
180
- !hasBlockingDivergence,
158
+ divergenceInfo &&
159
+ divergenceInfo.behind > 0 &&
160
+ divergenceInfo.ahead === 0 &&
161
+ !hasBlockingDivergence,
181
162
  );
182
163
  const isSyncingBranch = syncBranch.isPending;
183
164
 
165
+ // Available tools
184
166
  const customTools: CustomAITool[] = config?.tools ?? [];
185
167
  const availableTools: SelectableTool[] = useMemo(
186
168
  () => [
@@ -198,6 +180,7 @@ export function BranchDetailPage() {
198
180
  [customTools],
199
181
  );
200
182
 
183
+ // Ensure selected tool is valid
201
184
  useEffect(() => {
202
185
  if (!availableTools.length) {
203
186
  setSelectedToolId("claude-code");
@@ -205,9 +188,7 @@ export function BranchDetailPage() {
205
188
  }
206
189
  if (!availableTools.find((tool) => tool.id === selectedToolId)) {
207
190
  const first = availableTools[0];
208
- if (first) {
209
- setSelectedToolId(first.id);
210
- }
191
+ if (first) setSelectedToolId(first.id);
211
192
  }
212
193
  }, [availableTools, selectedToolId]);
213
194
 
@@ -215,43 +196,41 @@ export function BranchDetailPage() {
215
196
  (tool) => tool.id === selectedToolId,
216
197
  );
217
198
 
218
- const selectedToolSummary: ToolSummary | null = useMemo(() => {
219
- if (!selectedTool) {
220
- return null;
221
- }
222
- if (selectedTool.target === "custom") {
223
- return {
224
- command: selectedTool.definition.command,
225
- defaultArgs: selectedTool.definition.defaultArgs ?? null,
226
- modeArgs: selectedTool.definition.modeArgs,
227
- permissionSkipArgs: selectedTool.definition.permissionSkipArgs ?? null,
228
- };
229
- }
230
- return BUILTIN_TOOL_SUMMARIES[selectedTool.id] ?? null;
231
- }, [selectedTool]);
199
+ // Branch sessions
200
+ const branchSessions = useMemo(() => {
201
+ return (sessionsData ?? [])
202
+ .filter((session) => session.worktreePath === branch?.worktreePath)
203
+ .sort((a, b) => (b.startedAt ?? "").localeCompare(a.startedAt ?? ""));
204
+ }, [sessionsData, branch?.worktreePath]);
232
205
 
233
- const argsPreview = useMemo(() => {
234
- if (!selectedToolSummary) {
235
- return null;
236
- }
237
- const args: string[] = [];
238
- if (selectedToolSummary.defaultArgs?.length) {
239
- args.push(...selectedToolSummary.defaultArgs);
240
- }
241
- const mode = selectedToolSummary.modeArgs?.[selectedMode];
242
- if (mode?.length) {
243
- args.push(...mode);
244
- }
245
- if (skipPermissions && selectedToolSummary.permissionSkipArgs?.length) {
246
- args.push(...selectedToolSummary.permissionSkipArgs);
247
- }
248
- const extraArgs = parseExtraArgs(extraArgsText);
249
- if (extraArgs.length) {
250
- args.push(...extraArgs);
251
- }
252
- return { command: selectedToolSummary.command, args };
253
- }, [selectedToolSummary, selectedMode, skipPermissions, extraArgsText]);
206
+ // Latest tool usage
207
+ const latestToolUsage: LastToolUsage | null = useMemo(() => {
208
+ if (branch?.lastToolUsage) return branch.lastToolUsage;
209
+ const first = branchSessions[0];
210
+ if (!first) return null;
211
+ return {
212
+ branch: branch.name,
213
+ worktreePath: branch.worktreePath ?? null,
214
+ toolId:
215
+ first.toolType === "custom"
216
+ ? (first.toolName ?? "custom")
217
+ : (first.toolType as LastToolUsage["toolId"]),
218
+ toolLabel:
219
+ first.toolType === "custom"
220
+ ? (first.toolName ?? "Custom")
221
+ : toolLabel(first.toolType),
222
+ mode: first.mode ?? "normal",
223
+ model: null,
224
+ timestamp: first.startedAt ? Date.parse(first.startedAt) : Date.now(),
225
+ };
226
+ }, [
227
+ branch?.lastToolUsage,
228
+ branch?.name,
229
+ branch?.worktreePath,
230
+ branchSessions,
231
+ ]);
254
232
 
233
+ // Handlers
255
234
  const handleCreateWorktree = async () => {
256
235
  try {
257
236
  await createWorktree.mutateAsync({
@@ -290,8 +269,7 @@ export function BranchDetailPage() {
290
269
  if (needsRemoteSync) {
291
270
  setBanner({
292
271
  type: "error",
293
- message:
294
- "リモートの更新を取り込むまでAIツールは起動できません。『最新の変更を同期』を実行してください。",
272
+ message: "リモートの更新を取り込むまでAIツールは起動できません。",
295
273
  });
296
274
  return;
297
275
  }
@@ -299,17 +277,14 @@ export function BranchDetailPage() {
299
277
  if (hasBlockingDivergence) {
300
278
  setBanner({
301
279
  type: "error",
302
- message:
303
- "リモートとローカルの双方で進捗が発生しているため、CLIと同様にAIツールの起動をブロックしました。先に rebase/merge 等で差分を解消してください。",
280
+ message: "差分を解消してから起動してください。",
304
281
  });
305
282
  return;
306
283
  }
307
284
 
308
285
  if (
309
286
  skipPermissions &&
310
- !window.confirm(
311
- "権限チェックをスキップして起動します。自己責任で実行してください。続行しますか?",
312
- )
287
+ !window.confirm("権限チェックをスキップして起動します。続行しますか?")
313
288
  ) {
314
289
  return;
315
290
  }
@@ -322,7 +297,10 @@ export function BranchDetailPage() {
322
297
  : selectedTool.target === "custom"
323
298
  ? "custom"
324
299
  : "claude-code";
325
- const extraArgs = parseExtraArgs(extraArgsText);
300
+ const extraArgs = extraArgsText
301
+ .split(/\s+/)
302
+ .map((c) => c.trim())
303
+ .filter(Boolean);
326
304
  const sessionRequest = {
327
305
  toolType,
328
306
  toolName: selectedTool.target === "custom" ? selectedTool.id : null,
@@ -360,9 +338,7 @@ export function BranchDetailPage() {
360
338
  try {
361
339
  await deleteSession.mutateAsync(sessionId);
362
340
  setBanner({ type: "success", message: "セッションを終了しました" });
363
- if (activeSessionId === sessionId) {
364
- setActiveSessionId(null);
365
- }
341
+ if (activeSessionId === sessionId) setActiveSessionId(null);
366
342
  } catch (err) {
367
343
  setBanner({
368
344
  type: "error",
@@ -408,40 +384,6 @@ export function BranchDetailPage() {
408
384
  }
409
385
  };
410
386
 
411
- const branchSessions = useMemo(() => {
412
- return (sessionsData ?? [])
413
- .filter((session) => session.worktreePath === branch?.worktreePath)
414
- .sort((a, b) => (b.startedAt ?? "").localeCompare(a.startedAt ?? ""));
415
- }, [sessionsData, branch?.worktreePath]);
416
-
417
- const latestToolUsage: LastToolUsage | null = useMemo(() => {
418
- if (branch?.lastToolUsage) {
419
- return branch.lastToolUsage;
420
- }
421
- const first = branchSessions[0];
422
- if (!first) return null;
423
- return {
424
- branch: branch.name,
425
- worktreePath: branch.worktreePath ?? null,
426
- toolId:
427
- first.toolType === "custom"
428
- ? (first.toolName ?? "custom")
429
- : (first.toolType as LastToolUsage["toolId"]),
430
- toolLabel:
431
- first.toolType === "custom"
432
- ? (first.toolName ?? "Custom")
433
- : toolLabel(first.toolType),
434
- mode: first.mode ?? "normal",
435
- model: null,
436
- timestamp: first.startedAt ? Date.parse(first.startedAt) : Date.now(),
437
- };
438
- }, [
439
- branch?.lastToolUsage,
440
- branch?.name,
441
- branch?.worktreePath,
442
- branchSessions,
443
- ]);
444
-
445
387
  const handleSessionExit = (code: number) => {
446
388
  setActiveSessionId(null);
447
389
  setIsTerminalFullscreen(false);
@@ -452,488 +394,132 @@ export function BranchDetailPage() {
452
394
  };
453
395
 
454
396
  return (
455
- <div className="app-shell">
456
- <header className="page-hero page-hero--compact">
457
- <Link to="/" className="link-back">
458
- ← ブランチ一覧に戻る
459
- </Link>
460
- <p className="page-hero__eyebrow">BRANCH DETAIL</p>
461
- <h1>{branch.name}</h1>
462
- <p className="page-hero__subtitle">
463
- 最新コミット {branch.commitHash.slice(0, 7)} ・ {formattedCommitDate}
464
- </p>
465
- <div className="badge-group">
466
- <span className={`status-badge status-badge--${branch.type}`}>
397
+ <div className="min-h-screen bg-background">
398
+ {/* Fullscreen backdrop */}
399
+ {isTerminalFullscreen && (
400
+ <div
401
+ className="fixed inset-0 z-40 bg-black/80"
402
+ aria-hidden="true"
403
+ onClick={() => setIsTerminalFullscreen(false)}
404
+ />
405
+ )}
406
+
407
+ {/* Header */}
408
+ <PageHeader
409
+ eyebrow="BRANCH DETAIL"
410
+ title={branch.name}
411
+ subtitle={`最新コミット ${branch.commitHash.slice(0, 7)} · ${formattedCommitDate}`}
412
+ >
413
+ <div className="mt-4 flex flex-wrap gap-2">
414
+ <Badge variant={branch.type === "local" ? "local" : "remote"}>
467
415
  {BRANCH_TYPE_LABEL[branch.type]}
468
- </span>
469
- <span
470
- className={`status-badge status-badge--${MERGE_STATUS_TONE[branch.mergeStatus]}`}
416
+ </Badge>
417
+ <Badge
418
+ variant={
419
+ branch.mergeStatus === "merged"
420
+ ? "success"
421
+ : branch.mergeStatus === "unmerged"
422
+ ? "warning"
423
+ : "outline"
424
+ }
471
425
  >
472
426
  {MERGE_STATUS_LABEL[branch.mergeStatus]}
473
- </span>
474
- <span
475
- className={`status-badge ${
476
- branch.worktreePath
477
- ? "status-badge--success"
478
- : "status-badge--muted"
479
- }`}
480
- >
427
+ </Badge>
428
+ <Badge variant={branch.worktreePath ? "success" : "outline"}>
481
429
  {branch.worktreePath ? "Worktreeあり" : "Worktree未作成"}
482
- </span>
483
- </div>
484
- <div className="badge-group" style={{ marginTop: "0.5rem" }}>
485
- {latestToolUsage ? (
486
- <>
487
- <span className="status-badge status-badge--muted">
488
- {renderToolUsage(latestToolUsage)}
489
- </span>
490
- <span className="status-badge status-badge--muted">
491
- {formatUsageTimestamp(latestToolUsage.timestamp)} ・ worktree:{" "}
492
- {latestToolUsage.worktreePath ?? branch.worktreePath ?? "N/A"}
493
- </span>
494
- </>
495
- ) : (
496
- <span className="status-badge status-badge--muted">Unknown</span>
497
- )}
430
+ </Badge>
498
431
  </div>
499
- <div className="page-hero__actions">
432
+ <div className="mt-4 flex flex-wrap gap-2">
433
+ <Button variant="ghost" size="sm" asChild>
434
+ <Link to="/">← ブランチ一覧</Link>
435
+ </Button>
500
436
  {!canStartSession ? (
501
- <button
502
- type="button"
503
- className="button button--primary"
437
+ <Button
504
438
  onClick={handleCreateWorktree}
505
439
  disabled={createWorktree.isPending}
506
440
  >
507
441
  {createWorktree.isPending ? "作成中..." : "Worktreeを作成"}
508
- </button>
442
+ </Button>
509
443
  ) : (
510
- <Link to="/config" className="button button--secondary">
511
- カスタムツール設定を開く
512
- </Link>
444
+ <Button variant="secondary" asChild>
445
+ <Link to="/config">カスタムツール設定</Link>
446
+ </Button>
513
447
  )}
514
448
  </div>
515
-
516
- {banner && (
517
- <div className={`inline-banner inline-banner--${banner.type}`}>
518
- {banner.message}
519
- </div>
520
- )}
521
- </header>
522
-
523
- {isTerminalFullscreen && (
524
- <div
525
- className="terminal-overlay-backdrop"
526
- aria-hidden="true"
527
- onClick={() => setIsTerminalFullscreen(false)}
528
- />
449
+ </PageHeader>
450
+
451
+ {/* Banner */}
452
+ {banner && (
453
+ <div className="mx-auto max-w-7xl px-6 pt-4">
454
+ <Alert
455
+ variant={
456
+ banner.type === "error"
457
+ ? "destructive"
458
+ : banner.type === "success"
459
+ ? "success"
460
+ : "info"
461
+ }
462
+ >
463
+ <AlertDescription>{banner.message}</AlertDescription>
464
+ </Alert>
465
+ </div>
529
466
  )}
530
- <main className="page-content page-content--wide">
531
- <div className="page-layout page-layout--split">
532
- <div className="info-stack">
533
- <section className="section-card">
534
- <header className="terminal-section__header">
535
- <div>
536
- <h2>AIツール起動</h2>
537
- <p className="section-card__body">
538
- Web UI
539
- から直接AIツールを起動できます。設定したカスタムツールも一覧に表示されます。
540
- </p>
541
- </div>
542
- {configError && (
543
- <span className="pill pill--warning">
544
- 設定の取得に失敗しました
545
- </span>
546
- )}
547
- </header>
548
-
549
- {!canStartSession ? (
550
- <p className="section-card__body">
551
- Worktreeが未作成のため、先にWorktreeを作成してください。
552
- </p>
553
- ) : (
554
- <div className="tool-form">
555
- <div className="form-grid">
556
- <label className="form-field">
557
- <span>AIツール</span>
558
- <select
559
- value={selectedToolId}
560
- onChange={(event) =>
561
- setSelectedToolId(event.target.value)
562
- }
563
- disabled={isConfigLoading}
564
- >
565
- {availableTools.map((tool) => (
566
- <option key={tool.id} value={tool.id}>
567
- {tool.label}
568
- </option>
569
- ))}
570
- </select>
571
- </label>
572
467
 
573
- <label className="form-field">
574
- <span>起動モード</span>
575
- <select
576
- value={selectedMode}
577
- onChange={(event) =>
578
- setSelectedMode(event.target.value as ToolMode)
579
- }
580
- >
581
- <option value="normal">normal</option>
582
- <option value="continue">continue</option>
583
- <option value="resume">resume</option>
584
- </select>
585
- </label>
586
-
587
- <label className="form-field">
588
- <span>追加引数 (スペース区切り)</span>
589
- <input
590
- type="text"
591
- value={extraArgsText}
592
- onChange={(event) =>
593
- setExtraArgsText(event.target.value)
594
- }
595
- placeholder="--flag value"
596
- />
597
- </label>
598
- </div>
599
-
600
- <label className="form-field">
601
- <span>
602
- <input
603
- type="checkbox"
604
- checked={skipPermissions}
605
- onChange={(event) =>
606
- setSkipPermissions(event.target.checked)
607
- }
608
- />
609
- <span style={{ marginLeft: "0.5rem" }}>
610
- 権限チェックをスキップ (自己責任)
611
- </span>
612
- </span>
613
- </label>
614
- {skipPermissions && (
615
- <div className="inline-banner inline-banner--warning">
616
- <p>
617
- 権限チェックをスキップすることで、CLI での
618
- `--dangerously-skip-permissions`
619
- 指定と同様のリスクを負います。
620
- </p>
621
- </div>
622
- )}
623
- {needsRemoteSync && (
624
- <div
625
- className="inline-banner inline-banner--info"
626
- data-testid="sync-required"
627
- >
628
- <p>
629
- リモートに未取得の更新 ({branch.divergence?.behind ?? 0}{" "}
630
- commits)
631
- があるため、AIツールを起動する前に同期してください。
632
- </p>
633
- <p className="section-card__body">
634
- CLI の `git fetch --all` と `git pull --ff-only`
635
- と同じ処理を Web UI から実行できます。
636
- </p>
637
- </div>
638
- )}
639
- {hasBlockingDivergence && (
640
- <div
641
- className="inline-banner inline-banner--warning"
642
- data-testid="divergence-warning"
643
- >
644
- <p>
645
- リモートとローカルの両方に未解決の差分があるため、Web UI
646
- でも CLI と同様に起動をブロックしています。
647
- </p>
648
- <ul className="list-muted">
649
- <li>
650
- git fetch && git pull --ff-only origin {branch.name}
651
- </li>
652
- <li>
653
- 必要に応じて git push origin {branch.name}{" "}
654
- でローカル進捗を共有
655
- </li>
656
- </ul>
657
- <p className="section-card__body">
658
- rebase / merge
659
- などで差分を解消した後にページを更新してください。
660
- </p>
661
- </div>
662
- )}
663
-
664
- <div className="tool-card__actions">
665
- <button
666
- type="button"
667
- className="button button--primary"
668
- onClick={handleStartSession}
669
- disabled={
670
- isStartingSession ||
671
- !selectedTool ||
672
- hasBlockingDivergence ||
673
- needsRemoteSync ||
674
- isSyncingBranch
675
- }
676
- >
677
- {isStartingSession ? "起動中..." : "セッションを起動"}
678
- </button>
679
- <button
680
- type="button"
681
- className="button button--secondary"
682
- onClick={handleSyncBranch}
683
- disabled={!branch.worktreePath || isSyncingBranch}
684
- >
685
- {isSyncingBranch ? "同期中..." : "最新の変更を同期"}
686
- </button>
687
- <Link to="/config" className="button button--ghost">
688
- 設定を編集
689
- </Link>
690
- </div>
691
-
692
- {selectedToolSummary && (
693
- <dl className="metadata-grid metadata-grid--compact">
694
- <div>
695
- <dt>コマンド</dt>
696
- <dd className="tool-card__command">
697
- {selectedToolSummary.command}
698
- </dd>
699
- </div>
700
- <div>
701
- <dt>defaultArgs</dt>
702
- <dd>{renderArgs(selectedToolSummary.defaultArgs)}</dd>
703
- </div>
704
- <div>
705
- <dt>permissionSkipArgs</dt>
706
- <dd>
707
- {renderArgs(selectedToolSummary.permissionSkipArgs)}
708
- </dd>
709
- </div>
710
- {argsPreview && (
711
- <div className="metadata-grid__full">
712
- <dt>最終的に実行されるコマンド</dt>
713
- <dd className="tool-card__command">
714
- {argsPreview.command} {argsPreview.args.join(" ")}
715
- </dd>
716
- </div>
717
- )}
718
- </dl>
719
- )}
720
- </div>
721
- )}
722
- </section>
723
- <section className="section-card">
724
- <header className="terminal-section__header">
725
- <div>
726
- <h2>セッション履歴</h2>
727
- <p className="section-card__body">
728
- この Worktree に紐づいた最新の AI
729
- セッションが表示されます。CLI からの起動分も共有されます。
730
- </p>
731
- </div>
732
- {isSessionsLoading && (
733
- <span className="pill">読み込み中...</span>
734
- )}
735
- </header>
736
- {branchSessions.length === 0 ? (
737
- <p className="section-card__body">
738
- セッション履歴はまだありません。
739
- </p>
740
- ) : (
741
- <div className="session-table-wrapper">
742
- <table className="session-table">
743
- <thead>
744
- <tr>
745
- <th>状態</th>
746
- <th>ツール</th>
747
- <th>モード</th>
748
- <th>開始時刻</th>
749
- <th>終了時刻</th>
750
- <th>操作</th>
751
- </tr>
752
- </thead>
753
- <tbody>
754
- {branchSessions.slice(0, 5).map((session) => (
755
- <tr key={session.sessionId}>
756
- <td>
757
- <span
758
- className={`status-pill status-pill--${session.status}`}
759
- >
760
- {SESSION_STATUS_LABEL[session.status]}
761
- </span>
762
- </td>
763
- <td>
764
- {session.toolType === "custom"
765
- ? (session.toolName ?? "custom")
766
- : toolLabel(session.toolType)}
767
- </td>
768
- <td>{session.mode}</td>
769
- <td>{formatDate(session.startedAt)}</td>
770
- <td>
771
- {session.endedAt
772
- ? formatDate(session.endedAt)
773
- : "--"}
774
- </td>
775
- <td>
776
- {session.status === "running" ? (
777
- <button
778
- type="button"
779
- className="button button--ghost"
780
- onClick={() =>
781
- handleTerminateSession(session.sessionId)
782
- }
783
- disabled={
784
- terminatingSessionId === session.sessionId ||
785
- deleteSession.isPending
786
- }
787
- >
788
- {terminatingSessionId === session.sessionId
789
- ? "終了中..."
790
- : "終了"}
791
- </button>
792
- ) : (
793
- <span className="session-table__muted">--</span>
794
- )}
795
- </td>
796
- </tr>
797
- ))}
798
- </tbody>
799
- </table>
800
- </div>
801
- )}
802
- </section>
803
- <section className="section-card">
804
- <header>
805
- <h2>ブランチインサイト</h2>
806
- </header>
807
- <dl className="metadata-grid">
808
- <div>
809
- <dt>コミット</dt>
810
- <dd>{branch.commitHash}</dd>
811
- </div>
812
- <div>
813
- <dt>Author</dt>
814
- <dd>{branch.author ?? "N/A"}</dd>
815
- </div>
816
- <div>
817
- <dt>更新日</dt>
818
- <dd>{formattedCommitDate}</dd>
819
- </div>
820
- <div>
821
- <dt>Worktree</dt>
822
- <dd>{branch.worktreePath ?? "未作成"}</dd>
823
- </div>
824
- </dl>
825
- </section>
826
-
827
- <section className="section-card">
828
- <header>
829
- <h2>コミット情報</h2>
830
- </header>
831
- <p className="section-card__body">
832
- {branch.commitMessage ?? "コミットメッセージがありません。"}
833
- </p>
834
- </section>
835
-
836
- {branch.divergence && (
837
- <section className="section-card">
838
- <header>
839
- <h2>差分状況</h2>
840
- </header>
841
- <div className="pill-group">
842
- <span className="pill">Ahead {branch.divergence.ahead}</span>
843
- <span className="pill">
844
- Behind {branch.divergence.behind}
845
- </span>
846
- <span
847
- className={`pill ${
848
- branch.divergence.upToDate
849
- ? "pill--success"
850
- : "pill--warning"
851
- }`}
852
- >
853
- {branch.divergence.upToDate ? "最新" : "更新あり"}
854
- </span>
855
- </div>
856
- </section>
857
- )}
858
-
859
- <section className="section-card">
860
- <header>
861
- <h2>Worktree情報</h2>
862
- </header>
863
- <ul className="list-muted">
864
- <li>
865
- パス: <strong>{branch.worktreePath ?? "未作成"}</strong>
866
- </li>
867
- <li>
868
- AIツールの起動にはクリーンなワークツリーであることを推奨します。
869
- </li>
870
- <li>
871
- Worktreeを再作成すると既存のローカル変更が失われる可能性があります。
872
- </li>
873
- </ul>
874
- </section>
468
+ {/* Main Content */}
469
+ <main className="mx-auto max-w-7xl space-y-6 px-6 py-8">
470
+ <div className="grid gap-6 lg:grid-cols-[1fr_400px]">
471
+ {/* Left Column - Tool Launcher & Session History */}
472
+ <div className="space-y-6">
473
+ <ToolLauncher
474
+ branch={branch}
475
+ availableTools={availableTools}
476
+ selectedToolId={selectedToolId}
477
+ selectedMode={selectedMode}
478
+ skipPermissions={skipPermissions}
479
+ extraArgsText={extraArgsText}
480
+ isConfigLoading={isConfigLoading}
481
+ configError={configError ?? null}
482
+ isStartingSession={isStartingSession}
483
+ isSyncingBranch={isSyncingBranch}
484
+ needsRemoteSync={needsRemoteSync}
485
+ hasBlockingDivergence={hasBlockingDivergence}
486
+ onToolChange={setSelectedToolId}
487
+ onModeChange={setSelectedMode}
488
+ onSkipPermissionsChange={setSkipPermissions}
489
+ onExtraArgsChange={setExtraArgsText}
490
+ onStartSession={handleStartSession}
491
+ onSyncBranch={handleSyncBranch}
492
+ />
493
+
494
+ <SessionHistoryTable
495
+ sessions={branchSessions}
496
+ isLoading={isSessionsLoading}
497
+ terminatingSessionId={terminatingSessionId}
498
+ isDeleting={deleteSession.isPending}
499
+ onTerminate={handleTerminateSession}
500
+ onSelectSession={setActiveSessionId}
501
+ />
502
+
503
+ <BranchInfoCards
504
+ branch={branch}
505
+ formattedCommitDate={formattedCommitDate}
506
+ latestToolUsage={latestToolUsage}
507
+ />
875
508
  </div>
876
509
 
877
- <div className="terminal-column">
878
- {activeSessionId ? (
879
- <section
880
- className={`section-card terminal-section ${
881
- isTerminalFullscreen ? "terminal-section--fullscreen" : ""
882
- }`}
883
- data-testid="active-terminal"
884
- >
885
- <div className="terminal-section__header">
886
- <div>
887
- <h2>ターミナルセッション</h2>
888
- <p className="section-card__body">
889
- 出力はリアルタイムにストリームされます。終了するとこのパネルは自動で閉じます。
890
- </p>
891
- </div>
892
- <div className="terminal-section__controls">
893
- <button
894
- type="button"
895
- className="button button--ghost"
896
- onClick={() => setIsTerminalFullscreen((prev) => !prev)}
897
- >
898
- {isTerminalFullscreen
899
- ? "通常表示に戻す"
900
- : "ターミナルを最大化"}
901
- </button>
902
- </div>
903
- </div>
904
- <div className="terminal-surface">
905
- <Terminal
906
- sessionId={activeSessionId}
907
- onExit={handleSessionExit}
908
- onError={(message) =>
909
- setBanner({
910
- type: "error",
911
- message: message ?? "不明なエラー",
912
- })
913
- }
914
- />
915
- </div>
916
- {isTerminalFullscreen && (
917
- <button
918
- type="button"
919
- className="terminal-section__close"
920
- aria-label="ターミナルを閉じる"
921
- onClick={() => setIsTerminalFullscreen(false)}
922
- >
923
- ×
924
- </button>
925
- )}
926
- </section>
927
- ) : (
928
- <section className="section-card session-hint">
929
- <header>
930
- <h2>セッションは未起動</h2>
931
- </header>
932
- <p className="section-card__body">
933
- 上部のアクションからAIツールを起動すると、このエリアにターミナルが表示されます。
934
- </p>
935
- </section>
936
- )}
510
+ {/* Right Column - Terminal */}
511
+ <div className="lg:sticky lg:top-6 lg:self-start">
512
+ <TerminalPanel
513
+ sessionId={activeSessionId}
514
+ isFullscreen={isTerminalFullscreen}
515
+ onToggleFullscreen={() =>
516
+ setIsTerminalFullscreen((prev) => !prev)
517
+ }
518
+ onExit={handleSessionExit}
519
+ onError={(message) =>
520
+ setBanner({ type: "error", message: message ?? "不明なエラー" })
521
+ }
522
+ />
937
523
  </div>
938
524
  </div>
939
525
  </main>
@@ -941,91 +527,33 @@ export function BranchDetailPage() {
941
527
  );
942
528
  }
943
529
 
944
- function formatDate(value?: string | null) {
945
- if (!value) {
946
- return "日時不明";
947
- }
948
-
530
+ // Helper functions
531
+ function formatDate(value?: string | null): string {
532
+ if (!value) return "日時不明";
949
533
  try {
950
- const date = new Date(value);
951
534
  return new Intl.DateTimeFormat("ja-JP", {
952
535
  year: "numeric",
953
536
  month: "short",
954
537
  day: "numeric",
955
538
  hour: "2-digit",
956
539
  minute: "2-digit",
957
- }).format(date);
958
- } catch (_err) {
540
+ }).format(new Date(value));
541
+ } catch {
959
542
  return value;
960
543
  }
961
544
  }
962
545
 
963
- function formatError(error: unknown, fallback: string) {
546
+ function formatError(error: unknown, fallback: string): string {
964
547
  if (error instanceof ApiError) {
965
548
  return `${error.message}${error.details ? `\n${error.details}` : ""}`;
966
549
  }
967
- if (error instanceof Error) {
968
- return error.message;
969
- }
550
+ if (error instanceof Error) return error.message;
970
551
  return fallback;
971
552
  }
972
553
 
973
- function toolLabel(tool: ToolType, selectedTool?: SelectableTool) {
974
- if (tool === "custom" && selectedTool?.target === "custom") {
554
+ function toolLabel(tool: string, selectedTool?: SelectableTool): string {
555
+ if (tool === "custom" && selectedTool?.target === "custom")
975
556
  return selectedTool.label;
976
- }
977
- if (tool === "codex-cli") {
978
- return "Codex CLI";
979
- }
557
+ if (tool === "codex-cli") return "Codex CLI";
980
558
  return "Claude Code";
981
559
  }
982
-
983
- function renderArgs(args?: string[] | null) {
984
- if (!args || args.length === 0) {
985
- return <span className="tool-card__muted">未設定</span>;
986
- }
987
- return args.join(" ");
988
- }
989
-
990
- const SESSION_STATUS_LABEL: Record<
991
- "pending" | "running" | "completed" | "failed",
992
- string
993
- > = {
994
- pending: "pending",
995
- running: "running",
996
- completed: "completed",
997
- failed: "failed",
998
- };
999
-
1000
- function renderToolUsage(usage: LastToolUsage): string {
1001
- const modeLabel =
1002
- usage.mode === "normal"
1003
- ? "New"
1004
- : usage.mode === "continue"
1005
- ? "Continue"
1006
- : usage.mode === "resume"
1007
- ? "Resume"
1008
- : null;
1009
- const toolText = mapToolLabel(usage.toolId, usage.toolLabel);
1010
- return [toolText, modeLabel, usage.model].filter(Boolean).join(" | ");
1011
- }
1012
-
1013
- function formatUsageTimestamp(value: number): string {
1014
- return formatDate(new Date(value).toISOString());
1015
- }
1016
-
1017
- function mapToolLabel(toolId: string, toolLabel?: string | null): string {
1018
- if (toolId === "claude-code") return "Claude";
1019
- if (toolId === "codex-cli") return "Codex";
1020
- if (toolId === "gemini-cli") return "Gemini";
1021
- if (toolId === "qwen-cli") return "Qwen";
1022
- if (toolLabel) return toolLabel;
1023
- return "Custom";
1024
- }
1025
-
1026
- function parseExtraArgs(value: string): string[] {
1027
- return value
1028
- .split(/\s+/)
1029
- .map((chunk) => chunk.trim())
1030
- .filter(Boolean);
1031
- }