@akiojin/gwt 4.11.6 → 4.12.1

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 (199) hide show
  1. package/bin/gwt.js +36 -10
  2. package/dist/claude.d.ts +1 -0
  3. package/dist/claude.d.ts.map +1 -1
  4. package/dist/claude.js +81 -24
  5. package/dist/claude.js.map +1 -1
  6. package/dist/cli/ui/App.solid.d.ts.map +1 -1
  7. package/dist/cli/ui/App.solid.js +280 -50
  8. package/dist/cli/ui/App.solid.js.map +1 -1
  9. package/dist/cli/ui/components/solid/QuickStartStep.d.ts.map +1 -1
  10. package/dist/cli/ui/components/solid/QuickStartStep.js +35 -22
  11. package/dist/cli/ui/components/solid/QuickStartStep.js.map +1 -1
  12. package/dist/cli/ui/components/solid/SelectInput.d.ts.map +1 -1
  13. package/dist/cli/ui/components/solid/SelectInput.js +2 -1
  14. package/dist/cli/ui/components/solid/SelectInput.js.map +1 -1
  15. package/dist/cli/ui/components/solid/WizardController.d.ts.map +1 -1
  16. package/dist/cli/ui/components/solid/WizardController.js +67 -13
  17. package/dist/cli/ui/components/solid/WizardController.js.map +1 -1
  18. package/dist/cli/ui/components/solid/WizardSteps.d.ts +5 -0
  19. package/dist/cli/ui/components/solid/WizardSteps.d.ts.map +1 -1
  20. package/dist/cli/ui/components/solid/WizardSteps.js +50 -70
  21. package/dist/cli/ui/components/solid/WizardSteps.js.map +1 -1
  22. package/dist/cli/ui/core/theme.d.ts +9 -0
  23. package/dist/cli/ui/core/theme.d.ts.map +1 -1
  24. package/dist/cli/ui/core/theme.js +21 -0
  25. package/dist/cli/ui/core/theme.js.map +1 -1
  26. package/dist/cli/ui/screens/solid/BranchListScreen.d.ts +9 -2
  27. package/dist/cli/ui/screens/solid/BranchListScreen.d.ts.map +1 -1
  28. package/dist/cli/ui/screens/solid/BranchListScreen.js +101 -28
  29. package/dist/cli/ui/screens/solid/BranchListScreen.js.map +1 -1
  30. package/dist/cli/ui/screens/solid/ConfirmScreen.d.ts +2 -1
  31. package/dist/cli/ui/screens/solid/ConfirmScreen.d.ts.map +1 -1
  32. package/dist/cli/ui/screens/solid/ConfirmScreen.js +11 -3
  33. package/dist/cli/ui/screens/solid/ConfirmScreen.js.map +1 -1
  34. package/dist/cli/ui/screens/solid/EnvironmentScreen.d.ts.map +1 -1
  35. package/dist/cli/ui/screens/solid/EnvironmentScreen.js +9 -10
  36. package/dist/cli/ui/screens/solid/EnvironmentScreen.js.map +1 -1
  37. package/dist/cli/ui/screens/solid/LogScreen.d.ts +7 -1
  38. package/dist/cli/ui/screens/solid/LogScreen.d.ts.map +1 -1
  39. package/dist/cli/ui/screens/solid/LogScreen.js +254 -16
  40. package/dist/cli/ui/screens/solid/LogScreen.js.map +1 -1
  41. package/dist/cli/ui/screens/solid/ProfileEnvScreen.d.ts.map +1 -1
  42. package/dist/cli/ui/screens/solid/ProfileEnvScreen.js +8 -5
  43. package/dist/cli/ui/screens/solid/ProfileEnvScreen.js.map +1 -1
  44. package/dist/cli/ui/screens/solid/SelectorScreen.d.ts.map +1 -1
  45. package/dist/cli/ui/screens/solid/SelectorScreen.js +12 -4
  46. package/dist/cli/ui/screens/solid/SelectorScreen.js.map +1 -1
  47. package/dist/cli/ui/types.d.ts +1 -0
  48. package/dist/cli/ui/types.d.ts.map +1 -1
  49. package/dist/cli/ui/utils/branchFormatter.d.ts +1 -0
  50. package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -1
  51. package/dist/cli/ui/utils/branchFormatter.js +29 -7
  52. package/dist/cli/ui/utils/branchFormatter.js.map +1 -1
  53. package/dist/cli/ui/utils/continueSession.d.ts +14 -0
  54. package/dist/cli/ui/utils/continueSession.d.ts.map +1 -1
  55. package/dist/cli/ui/utils/continueSession.js +61 -3
  56. package/dist/cli/ui/utils/continueSession.js.map +1 -1
  57. package/dist/cli/ui/utils/installedVersionCache.d.ts +33 -0
  58. package/dist/cli/ui/utils/installedVersionCache.d.ts.map +1 -0
  59. package/dist/cli/ui/utils/installedVersionCache.js +59 -0
  60. package/dist/cli/ui/utils/installedVersionCache.js.map +1 -0
  61. package/dist/cli/ui/utils/modelOptions.d.ts.map +1 -1
  62. package/dist/cli/ui/utils/modelOptions.js +16 -0
  63. package/dist/cli/ui/utils/modelOptions.js.map +1 -1
  64. package/dist/cli/ui/utils/versionCache.d.ts +37 -0
  65. package/dist/cli/ui/utils/versionCache.d.ts.map +1 -0
  66. package/dist/cli/ui/utils/versionCache.js +70 -0
  67. package/dist/cli/ui/utils/versionCache.js.map +1 -0
  68. package/dist/cli/ui/utils/versionFetcher.d.ts +41 -0
  69. package/dist/cli/ui/utils/versionFetcher.d.ts.map +1 -0
  70. package/dist/cli/ui/utils/versionFetcher.js +89 -0
  71. package/dist/cli/ui/utils/versionFetcher.js.map +1 -0
  72. package/dist/codex.d.ts +1 -0
  73. package/dist/codex.d.ts.map +1 -1
  74. package/dist/codex.js +95 -25
  75. package/dist/codex.js.map +1 -1
  76. package/dist/config/index.d.ts.map +1 -1
  77. package/dist/config/index.js +10 -1
  78. package/dist/config/index.js.map +1 -1
  79. package/dist/gemini.d.ts +1 -0
  80. package/dist/gemini.d.ts.map +1 -1
  81. package/dist/gemini.js +36 -3
  82. package/dist/gemini.js.map +1 -1
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +35 -2
  85. package/dist/index.js.map +1 -1
  86. package/dist/launcher.d.ts.map +1 -1
  87. package/dist/launcher.js +43 -8
  88. package/dist/launcher.js.map +1 -1
  89. package/dist/logging/agentOutput.d.ts +21 -0
  90. package/dist/logging/agentOutput.d.ts.map +1 -0
  91. package/dist/logging/agentOutput.js +164 -0
  92. package/dist/logging/agentOutput.js.map +1 -0
  93. package/dist/logging/formatter.d.ts.map +1 -1
  94. package/dist/logging/formatter.js +18 -4
  95. package/dist/logging/formatter.js.map +1 -1
  96. package/dist/logging/logger.d.ts.map +1 -1
  97. package/dist/logging/logger.js +2 -0
  98. package/dist/logging/logger.js.map +1 -1
  99. package/dist/logging/reader.d.ts +22 -0
  100. package/dist/logging/reader.d.ts.map +1 -1
  101. package/dist/logging/reader.js +116 -1
  102. package/dist/logging/reader.js.map +1 -1
  103. package/dist/opentui/index.solid.js +2575 -888
  104. package/dist/services/codingAgentResolver.d.ts.map +1 -1
  105. package/dist/services/codingAgentResolver.js +8 -6
  106. package/dist/services/codingAgentResolver.js.map +1 -1
  107. package/dist/services/dependency-installer.js +2 -2
  108. package/dist/services/dependency-installer.js.map +1 -1
  109. package/dist/shared/codingAgentConstants.d.ts +3 -0
  110. package/dist/shared/codingAgentConstants.d.ts.map +1 -1
  111. package/dist/shared/codingAgentConstants.js +66 -0
  112. package/dist/shared/codingAgentConstants.js.map +1 -1
  113. package/dist/utils/bun-runtime.d.ts +12 -0
  114. package/dist/utils/bun-runtime.d.ts.map +1 -0
  115. package/dist/utils/bun-runtime.js +13 -0
  116. package/dist/utils/bun-runtime.js.map +1 -0
  117. package/dist/utils/session/common.d.ts +8 -0
  118. package/dist/utils/session/common.d.ts.map +1 -1
  119. package/dist/utils/session/common.js +22 -0
  120. package/dist/utils/session/common.js.map +1 -1
  121. package/dist/utils/session/parsers/claude.d.ts +10 -4
  122. package/dist/utils/session/parsers/claude.d.ts.map +1 -1
  123. package/dist/utils/session/parsers/claude.js +64 -18
  124. package/dist/utils/session/parsers/claude.js.map +1 -1
  125. package/dist/utils/session/parsers/codex.d.ts.map +1 -1
  126. package/dist/utils/session/parsers/codex.js +47 -28
  127. package/dist/utils/session/parsers/codex.js.map +1 -1
  128. package/dist/utils/session/parsers/gemini.d.ts.map +1 -1
  129. package/dist/utils/session/parsers/gemini.js +43 -6
  130. package/dist/utils/session/parsers/gemini.js.map +1 -1
  131. package/dist/utils/session/parsers/opencode.d.ts.map +1 -1
  132. package/dist/utils/session/parsers/opencode.js +43 -6
  133. package/dist/utils/session/parsers/opencode.js.map +1 -1
  134. package/dist/utils/session/types.d.ts +7 -0
  135. package/dist/utils/session/types.d.ts.map +1 -1
  136. package/dist/web/client/src/components/ui/alert.d.ts +1 -1
  137. package/dist/worktree.d.ts +4 -1
  138. package/dist/worktree.d.ts.map +1 -1
  139. package/dist/worktree.js +21 -15
  140. package/dist/worktree.js.map +1 -1
  141. package/package.json +2 -1
  142. package/src/claude.ts +99 -28
  143. package/src/cli/ui/App.solid.tsx +373 -51
  144. package/src/cli/ui/__tests__/solid/AppSolid.cleanup.test.tsx +921 -1
  145. package/src/cli/ui/__tests__/solid/BranchListScreen.test.tsx +105 -5
  146. package/src/cli/ui/__tests__/solid/ConfirmScreen.test.tsx +77 -0
  147. package/src/cli/ui/__tests__/solid/LogScreen.test.tsx +351 -0
  148. package/src/cli/ui/__tests__/solid/components/QuickStartStep.test.tsx +73 -2
  149. package/src/cli/ui/__tests__/solid/components/WizardController.test.tsx +71 -0
  150. package/src/cli/ui/__tests__/solid/components/WizardSteps.test.tsx +95 -2
  151. package/src/cli/ui/__tests__/utils/branchFormatter.test.ts +72 -45
  152. package/src/cli/ui/components/solid/QuickStartStep.tsx +35 -23
  153. package/src/cli/ui/components/solid/SearchInput.tsx +1 -1
  154. package/src/cli/ui/components/solid/SelectInput.tsx +4 -0
  155. package/src/cli/ui/components/solid/WizardController.tsx +85 -12
  156. package/src/cli/ui/components/solid/WizardSteps.tsx +78 -90
  157. package/src/cli/ui/core/theme.ts +32 -0
  158. package/src/cli/ui/hooks/solid/useAsyncOperation.ts +8 -6
  159. package/src/cli/ui/hooks/solid/useGitOperations.ts +6 -5
  160. package/src/cli/ui/screens/solid/BranchListScreen.tsx +135 -32
  161. package/src/cli/ui/screens/solid/ConfirmScreen.tsx +20 -8
  162. package/src/cli/ui/screens/solid/EnvironmentScreen.tsx +22 -20
  163. package/src/cli/ui/screens/solid/LogScreen.tsx +364 -35
  164. package/src/cli/ui/screens/solid/ProfileEnvScreen.tsx +19 -15
  165. package/src/cli/ui/screens/solid/SelectorScreen.tsx +25 -14
  166. package/src/cli/ui/screens/solid/SettingsScreen.tsx +5 -3
  167. package/src/cli/ui/types.ts +1 -0
  168. package/src/cli/ui/utils/__tests__/branchFormatter.test.ts +53 -6
  169. package/src/cli/ui/utils/__tests__/installedVersionCache.test.ts +46 -0
  170. package/src/cli/ui/utils/branchFormatter.ts +35 -7
  171. package/src/cli/ui/utils/continueSession.ts +90 -3
  172. package/src/cli/ui/utils/installedVersionCache.ts +84 -0
  173. package/src/cli/ui/utils/modelOptions.test.ts +6 -0
  174. package/src/cli/ui/utils/modelOptions.ts +16 -0
  175. package/src/cli/ui/utils/versionCache.ts +93 -0
  176. package/src/cli/ui/utils/versionFetcher.ts +120 -0
  177. package/src/codex.ts +124 -26
  178. package/src/config/__tests__/saveSession.test.ts +2 -2
  179. package/src/config/index.ts +11 -1
  180. package/src/gemini.ts +50 -4
  181. package/src/index.test.ts +16 -10
  182. package/src/index.ts +41 -1
  183. package/src/launcher.ts +49 -8
  184. package/src/logging/agentOutput.ts +216 -0
  185. package/src/logging/formatter.ts +23 -4
  186. package/src/logging/logger.ts +2 -0
  187. package/src/logging/reader.ts +165 -1
  188. package/src/services/__tests__/BatchMergeService.test.ts +34 -14
  189. package/src/services/codingAgentResolver.ts +12 -5
  190. package/src/services/dependency-installer.ts +2 -2
  191. package/src/shared/codingAgentConstants.ts +73 -0
  192. package/src/utils/bun-runtime.ts +29 -0
  193. package/src/utils/session/common.ts +28 -0
  194. package/src/utils/session/parsers/claude.ts +79 -29
  195. package/src/utils/session/parsers/codex.ts +49 -26
  196. package/src/utils/session/parsers/gemini.ts +46 -5
  197. package/src/utils/session/parsers/opencode.ts +46 -5
  198. package/src/utils/session/types.ts +4 -0
  199. package/src/worktree.ts +28 -15
@@ -0,0 +1,71 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import { describe, expect, it, mock } from "bun:test";
3
+ import { testRender } from "@opentui/solid";
4
+ import type { ToolSessionEntry } from "../../../../config/index.js";
5
+
6
+ mock.module("../../../utils/versionFetcher.js", () => ({
7
+ fetchInstalledVersionForAgent: mock(async () => null),
8
+ createInstalledOption: (version: string) => ({
9
+ label: `installed (${version})`,
10
+ value: "installed",
11
+ }),
12
+ versionInfoToSelectItem: (v: { version: string }) => ({
13
+ label: v.version,
14
+ value: v.version,
15
+ }),
16
+ }));
17
+
18
+ describe("WizardController", () => {
19
+ it("keeps version selection visible after agent select", async () => {
20
+ const history: ToolSessionEntry[] = [];
21
+
22
+ const { WizardController } =
23
+ await import("../../../components/solid/WizardController.js");
24
+
25
+ const testSetup = await testRender(
26
+ () => (
27
+ <WizardController
28
+ visible
29
+ selectedBranchName="feature/test"
30
+ history={history}
31
+ onClose={() => {}}
32
+ onComplete={() => {}}
33
+ onResume={() => {}}
34
+ onStartNew={() => {}}
35
+ />
36
+ ),
37
+ { width: 80, height: 24 },
38
+ );
39
+ await testSetup.renderOnce();
40
+
41
+ try {
42
+ let frame = testSetup.captureCharFrame();
43
+ expect(frame).toContain("What would you like to do?");
44
+
45
+ await new Promise((resolve) => setTimeout(resolve, 60));
46
+ await testSetup.renderOnce();
47
+
48
+ // Open existing worktree
49
+ testSetup.mockInput.pressEnter();
50
+ await testSetup.renderOnce();
51
+
52
+ frame = testSetup.captureCharFrame();
53
+ expect(frame).toContain("Select coding agent:");
54
+
55
+ await new Promise((resolve) => setTimeout(resolve, 60));
56
+ await testSetup.renderOnce();
57
+
58
+ // Select default agent (Enter)
59
+ testSetup.mockInput.pressEnter();
60
+ await testSetup.renderOnce();
61
+ await new Promise((resolve) => setTimeout(resolve, 60));
62
+ await testSetup.renderOnce();
63
+
64
+ frame = testSetup.captureCharFrame();
65
+ expect(frame).toContain("Select version:");
66
+ expect(frame).not.toContain("Select Model:");
67
+ } finally {
68
+ testSetup.renderer.destroy();
69
+ }
70
+ });
71
+ });
@@ -1,15 +1,24 @@
1
1
  /** @jsxImportSource @opentui/solid */
2
- import { describe, expect, it } from "bun:test";
2
+ import { beforeEach, describe, expect, it } from "bun:test";
3
3
  import { testRender } from "@opentui/solid";
4
4
  import {
5
5
  BranchTypeStep,
6
6
  BranchNameStep,
7
7
  AgentSelectStep,
8
+ VersionSelectStep,
8
9
  ModelSelectStep,
9
10
  ReasoningLevelStep,
10
11
  ExecutionModeStep,
11
12
  SkipPermissionsStep,
12
13
  } from "../../../components/solid/WizardSteps.js";
14
+ import {
15
+ clearInstalledVersionCache,
16
+ setInstalledVersionCache,
17
+ } from "../../../utils/installedVersionCache.js";
18
+ import {
19
+ clearVersionCache,
20
+ setVersionCache,
21
+ } from "../../../utils/versionCache.js";
13
22
 
14
23
  // T405: ブランチタイプ選択ステップのテスト
15
24
  describe("BranchTypeStep", () => {
@@ -128,6 +137,65 @@ describe("AgentSelectStep", () => {
128
137
  });
129
138
  });
130
139
 
140
+ // T407a: バージョン選択ステップのテスト
141
+ describe("VersionSelectStep", () => {
142
+ beforeEach(() => {
143
+ clearInstalledVersionCache();
144
+ clearVersionCache();
145
+ });
146
+
147
+ it("renders installed option from cache", async () => {
148
+ setInstalledVersionCache("claude-code", {
149
+ version: "1.2.3",
150
+ path: "/usr/local/bin/claude",
151
+ });
152
+ setVersionCache("claude-code", []);
153
+
154
+ const testSetup = await testRender(
155
+ () => (
156
+ <VersionSelectStep
157
+ agentId="claude-code"
158
+ onSelect={() => {}}
159
+ onBack={() => {}}
160
+ />
161
+ ),
162
+ { width: 60, height: 20 },
163
+ );
164
+ await testSetup.renderOnce();
165
+
166
+ try {
167
+ const frame = testSetup.captureCharFrame();
168
+ expect(frame).toContain("Select version");
169
+ expect(frame).toContain("installed@1.2.3");
170
+ expect(frame).toContain("latest");
171
+ } finally {
172
+ testSetup.renderer.destroy();
173
+ }
174
+ });
175
+
176
+ it("does not render installed option when cache is empty", async () => {
177
+ const testSetup = await testRender(
178
+ () => (
179
+ <VersionSelectStep
180
+ agentId="claude-code"
181
+ onSelect={() => {}}
182
+ onBack={() => {}}
183
+ />
184
+ ),
185
+ { width: 60, height: 20 },
186
+ );
187
+ await testSetup.renderOnce();
188
+
189
+ try {
190
+ const frame = testSetup.captureCharFrame();
191
+ expect(frame).toContain("latest");
192
+ expect(frame).not.toContain("installed@");
193
+ } finally {
194
+ testSetup.renderer.destroy();
195
+ }
196
+ });
197
+ });
198
+
131
199
  // T408: モデル選択ステップのテスト
132
200
  describe("ModelSelectStep", () => {
133
201
  it("renders model options for Claude Code", async () => {
@@ -150,6 +218,28 @@ describe("ModelSelectStep", () => {
150
218
  testSetup.renderer.destroy();
151
219
  }
152
220
  });
221
+
222
+ it("renders model options for OpenCode", async () => {
223
+ const testSetup = await testRender(
224
+ () => (
225
+ <ModelSelectStep
226
+ agentId="opencode"
227
+ onSelect={() => {}}
228
+ onBack={() => {}}
229
+ />
230
+ ),
231
+ { width: 60, height: 20 },
232
+ );
233
+ await testSetup.renderOnce();
234
+
235
+ try {
236
+ const frame = testSetup.captureCharFrame();
237
+ expect(frame).toContain("Default (Auto)");
238
+ expect(frame).toContain("Custom");
239
+ } finally {
240
+ testSetup.renderer.destroy();
241
+ }
242
+ });
153
243
  });
154
244
 
155
245
  // T409: 推論レベル選択ステップ(Codexのみ)のテスト
@@ -230,7 +320,10 @@ describe("SkipPermissionsStep", () => {
230
320
  // Enterキーで選択(デフォルトはYes)
231
321
  testSetup.mockInput.pressEnter();
232
322
  await testSetup.renderOnce();
233
- expect(selected).toBe(true);
323
+ if (selected === null) {
324
+ throw new Error("Expected selection");
325
+ }
326
+ expect(selected === true).toBe(true);
234
327
  } finally {
235
328
  testSetup.renderer.destroy();
236
329
  }
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
1
2
  import { describe, it, expect } from "bun:test";
2
3
  import {
3
4
  formatBranchItem,
@@ -5,6 +6,31 @@ import {
5
6
  } from "../../utils/branchFormatter.js";
6
7
  import type { BranchInfo } from "../../types.js";
7
8
 
9
+ const LOCAL_DATE_TIME_FORMATTER = new Intl.DateTimeFormat(undefined, {
10
+ year: "numeric",
11
+ month: "2-digit",
12
+ day: "2-digit",
13
+ hour: "2-digit",
14
+ minute: "2-digit",
15
+ hour12: false,
16
+ });
17
+
18
+ const formatLocalDateTime = (timestampMs: number): string => {
19
+ const date = new Date(timestampMs);
20
+ const parts = LOCAL_DATE_TIME_FORMATTER.formatToParts(date);
21
+ const get = (type: Intl.DateTimeFormatPartTypes) =>
22
+ parts.find((part) => part.type === type)?.value;
23
+ const year = get("year");
24
+ const month = get("month");
25
+ const day = get("day");
26
+ const hour = get("hour");
27
+ const minute = get("minute");
28
+ if (!year || !month || !day || !hour || !minute) {
29
+ return LOCAL_DATE_TIME_FORMATTER.format(date);
30
+ }
31
+ return `${year}-${month}-${day} ${hour}:${minute}`;
32
+ };
33
+
8
34
  describe("branchFormatter", () => {
9
35
  describe("formatBranchItem", () => {
10
36
  it("should format a branch without icons", () => {
@@ -46,8 +72,9 @@ describe("branchFormatter", () => {
46
72
 
47
73
  const result = formatBranchItem(branchInfo);
48
74
 
49
- expect(result.lastToolUsageLabel).toContain("Codex");
50
- expect(result.lastToolUsageLabel).toContain("2025-11-26");
75
+ expect(result.lastToolUsageLabel).toBe(
76
+ `Codex@latest | ${formatLocalDateTime(branchInfo.lastToolUsage.timestamp)}`,
77
+ );
51
78
  });
52
79
 
53
80
  it("should set lastToolUsageLabel to null when no usage exists", () => {
@@ -156,13 +183,13 @@ describe("branchFormatter", () => {
156
183
 
157
184
  expect(results).toHaveLength(3);
158
185
  // Current branch (main) should be first
159
- expect(results[0].name).toBe("main");
160
- expect(results[0].isCurrent).toBe(true);
186
+ expect(results[0]!.name).toBe("main");
187
+ expect(results[0]!.isCurrent).toBe(true);
161
188
  // origin/main is also main branch, so it comes second
162
- expect(results[1].name).toBe("origin/main");
163
- expect(results[1].type).toBe("remote");
189
+ expect(results[1]!.name).toBe("origin/main");
190
+ expect(results[1]!.type).toBe("remote");
164
191
  // feature/test comes last
165
- expect(results[2].name).toBe("feature/test");
192
+ expect(results[2]!.name).toBe("feature/test");
166
193
  });
167
194
 
168
195
  it("should handle empty array", () => {
@@ -189,8 +216,8 @@ describe("branchFormatter", () => {
189
216
 
190
217
  const results = formatBranchItems(branches);
191
218
 
192
- expect(results[0].name).toBe("a-branch");
193
- expect(results[1].name).toBe("z-branch");
219
+ expect(results[0]!.name).toBe("a-branch");
220
+ expect(results[1]!.name).toBe("z-branch");
194
221
  });
195
222
  });
196
223
 
@@ -219,8 +246,8 @@ describe("branchFormatter", () => {
219
246
 
220
247
  const results = formatBranchItems(branches);
221
248
 
222
- expect(results[0].name).toBe("feature/current");
223
- expect(results[0].isCurrent).toBe(true);
249
+ expect(results[0]!.name).toBe("feature/current");
250
+ expect(results[0]!.isCurrent).toBe(true);
224
251
  });
225
252
 
226
253
  it("should prioritize main branch as second (after current)", () => {
@@ -247,8 +274,8 @@ describe("branchFormatter", () => {
247
274
 
248
275
  const results = formatBranchItems(branches);
249
276
 
250
- expect(results[0].name).toBe("main");
251
- expect(results[1].name).toBe("develop");
277
+ expect(results[0]!.name).toBe("main");
278
+ expect(results[1]!.name).toBe("develop");
252
279
  });
253
280
 
254
281
  it("should prioritize develop branch after main (when main exists)", () => {
@@ -275,9 +302,9 @@ describe("branchFormatter", () => {
275
302
 
276
303
  const results = formatBranchItems(branches);
277
304
 
278
- expect(results[0].name).toBe("main");
279
- expect(results[1].name).toBe("develop");
280
- expect(results[2].name).toBe("feature/test");
305
+ expect(results[0]!.name).toBe("main");
306
+ expect(results[1]!.name).toBe("develop");
307
+ expect(results[2]!.name).toBe("feature/test");
281
308
  });
282
309
 
283
310
  it("should NOT prioritize develop branch when main does not exist", () => {
@@ -305,9 +332,9 @@ describe("branchFormatter", () => {
305
332
  const results = formatBranchItems(branches);
306
333
 
307
334
  // develop should be sorted alphabetically, not prioritized
308
- expect(results[0].name).toBe("develop");
309
- expect(results[1].name).toBe("feature/a");
310
- expect(results[2].name).toBe("feature/z");
335
+ expect(results[0]!.name).toBe("develop");
336
+ expect(results[1]!.name).toBe("feature/a");
337
+ expect(results[2]!.name).toBe("feature/z");
311
338
  });
312
339
 
313
340
  it("should prioritize branches with worktree", () => {
@@ -346,8 +373,8 @@ describe("branchFormatter", () => {
346
373
 
347
374
  const results = formatBranchItems(branches, worktreeMap);
348
375
 
349
- expect(results[0].name).toBe("feature/with-worktree");
350
- expect(results[1].name).toBe("feature/no-worktree");
376
+ expect(results[0]!.name).toBe("feature/with-worktree");
377
+ expect(results[1]!.name).toBe("feature/no-worktree");
351
378
  });
352
379
 
353
380
  it("should sort branches with worktree by latest commit timestamp", () => {
@@ -403,8 +430,8 @@ describe("branchFormatter", () => {
403
430
 
404
431
  const results = formatBranchItems(branches, worktreeMap);
405
432
 
406
- expect(results[0].name).toBe("feature/recent");
407
- expect(results[1].name).toBe("feature/older");
433
+ expect(results[0]!.name).toBe("feature/recent");
434
+ expect(results[1]!.name).toBe("feature/older");
408
435
  });
409
436
 
410
437
  it("should prioritize local branches over remote branches", () => {
@@ -425,10 +452,10 @@ describe("branchFormatter", () => {
425
452
 
426
453
  const results = formatBranchItems(branches);
427
454
 
428
- expect(results[0].name).toBe("feature/local");
429
- expect(results[0].type).toBe("local");
430
- expect(results[1].name).toBe("origin/feature/remote");
431
- expect(results[1].type).toBe("remote");
455
+ expect(results[0]!.name).toBe("feature/local");
456
+ expect(results[0]!.type).toBe("local");
457
+ expect(results[1]!.name).toBe("origin/feature/remote");
458
+ expect(results[1]!.type).toBe("remote");
432
459
  });
433
460
 
434
461
  it("should sort by latest commit timestamp when worktree status matches", () => {
@@ -451,8 +478,8 @@ describe("branchFormatter", () => {
451
478
 
452
479
  const results = formatBranchItems(branches);
453
480
 
454
- expect(results[0].name).toBe("origin/feature/newer");
455
- expect(results[1].name).toBe("feature/local-older");
481
+ expect(results[0]!.name).toBe("origin/feature/newer");
482
+ expect(results[1]!.name).toBe("feature/local-older");
456
483
  });
457
484
 
458
485
  it("should apply all sorting rules in correct priority order", () => {
@@ -528,13 +555,13 @@ describe("branchFormatter", () => {
528
555
  // 4. Branches with worktree
529
556
  // 5. Local branches (alphabetically)
530
557
  // 6. Remote branches
531
- expect(results[0].name).toBe("feature/current");
532
- expect(results[1].name).toBe("main");
533
- expect(results[2].name).toBe("develop");
534
- expect(results[3].name).toBe("feature/with-worktree");
535
- expect(results[4].name).toBe("feature/a-local-no-worktree");
536
- expect(results[5].name).toBe("feature/z-local-no-worktree");
537
- expect(results[6].name).toBe("origin/feature/z-remote");
558
+ expect(results[0]!.name).toBe("feature/current");
559
+ expect(results[1]!.name).toBe("main");
560
+ expect(results[2]!.name).toBe("develop");
561
+ expect(results[3]!.name).toBe("feature/with-worktree");
562
+ expect(results[4]!.name).toBe("feature/a-local-no-worktree");
563
+ expect(results[5]!.name).toBe("feature/z-local-no-worktree");
564
+ expect(results[6]!.name).toBe("origin/feature/z-remote");
538
565
  });
539
566
 
540
567
  it("should handle release and hotfix branches without special priority", () => {
@@ -562,9 +589,9 @@ describe("branchFormatter", () => {
562
589
  const results = formatBranchItems(branches);
563
590
 
564
591
  // Should be sorted alphabetically (no special priority)
565
- expect(results[0].name).toBe("feature/test");
566
- expect(results[1].name).toBe("hotfix/urgent");
567
- expect(results[2].name).toBe("release/v1.0");
592
+ expect(results[0]!.name).toBe("feature/test");
593
+ expect(results[1]!.name).toBe("hotfix/urgent");
594
+ expect(results[2]!.name).toBe("release/v1.0");
568
595
  });
569
596
 
570
597
  it("should sort by latest activity time (max of git commit and tool usage)", () => {
@@ -604,8 +631,8 @@ describe("branchFormatter", () => {
604
631
  // Both have same latest activity time (1_800_000_000), so alphabetical
605
632
  // feature/git-newer: max(1_800_000_000, 1_700_000_000) = 1_800_000_000
606
633
  // feature/tool-newer: max(1_700_000_000, 1_800_000_000) = 1_800_000_000
607
- expect(results[0].name).toBe("feature/git-newer");
608
- expect(results[1].name).toBe("feature/tool-newer");
634
+ expect(results[0]!.name).toBe("feature/git-newer");
635
+ expect(results[1]!.name).toBe("feature/tool-newer");
609
636
  });
610
637
 
611
638
  it("should prioritize branch with tool usage over branch with only git commit when tool is newer", () => {
@@ -636,8 +663,8 @@ describe("branchFormatter", () => {
636
663
  const results = formatBranchItems(branches);
637
664
 
638
665
  // feature/with-tool has newer activity (tool usage at 1_800_000_000)
639
- expect(results[0].name).toBe("feature/with-tool");
640
- expect(results[1].name).toBe("feature/git-only");
666
+ expect(results[0]!.name).toBe("feature/with-tool");
667
+ expect(results[1]!.name).toBe("feature/git-only");
641
668
  });
642
669
 
643
670
  it("should prioritize branch with newer git commit over branch with older tool usage", () => {
@@ -668,8 +695,8 @@ describe("branchFormatter", () => {
668
695
  const results = formatBranchItems(branches);
669
696
 
670
697
  // feature/new-git has newer activity (git commit at 1_800_000_000)
671
- expect(results[0].name).toBe("feature/new-git");
672
- expect(results[1].name).toBe("feature/old-tool");
698
+ expect(results[0]!.name).toBe("feature/new-git");
699
+ expect(results[1]!.name).toBe("feature/old-tool");
673
700
  });
674
701
  });
675
702
  });
@@ -42,40 +42,52 @@ export function QuickStartStep(props: QuickStartStepProps) {
42
42
  }
43
43
  });
44
44
 
45
- // Build selection items from history
46
- // 最新の履歴エントリのみを使用(重複排除済み)
47
- const latestEntry = createMemo(() => props.history[0] ?? null);
45
+ const buildSettingsDescription = (entry: ToolSessionEntry): string => {
46
+ const parts: string[] = [];
47
+ if (entry.toolLabel) {
48
+ parts.push(entry.toolLabel);
49
+ } else if (entry.toolId) {
50
+ parts.push(entry.toolId);
51
+ }
52
+ if (entry.model) {
53
+ parts.push(entry.model);
54
+ }
55
+ if (entry.toolId === "codex-cli" && entry.reasoningLevel) {
56
+ parts.push(entry.reasoningLevel);
57
+ }
58
+ return parts.join(", ");
59
+ };
48
60
 
49
61
  const items = createMemo<QuickStartItem[]>(() => {
50
62
  const result: QuickStartItem[] = [];
51
- const entry = latestEntry();
52
-
53
- if (entry) {
54
- const reasoningInfo = entry.reasoningLevel
55
- ? `, ${entry.reasoningLevel}`
56
- : "";
57
- const versionInfo = entry.toolVersion ? `@${entry.toolVersion}` : "";
58
- const settingsDesc = `${entry.toolLabel}${versionInfo}, ${entry.model}${reasoningInfo}`;
59
- const resumeDesc = entry.sessionId
60
- ? `${settingsDesc}, session ${entry.sessionId}`
61
- : settingsDesc;
62
63
 
63
- result.push({
64
- label: "Resume session (previous settings)",
65
- value: `resume-${entry.toolId}`,
66
- description: resumeDesc,
67
- action: "resume",
68
- entry,
69
- });
64
+ props.history.forEach((entry, index) => {
65
+ if (!entry) return;
66
+ const settingsDesc = buildSettingsDescription(entry);
67
+ const sessionId =
68
+ entry.sessionId && entry.sessionId.trim().length > 0
69
+ ? entry.sessionId.trim()
70
+ : null;
71
+ const suffix = `${entry.toolId ?? "tool"}-${index}`;
72
+
73
+ if (sessionId) {
74
+ result.push({
75
+ label: "Resume session (previous settings)",
76
+ value: `resume-${suffix}`,
77
+ description: `${settingsDesc} | Session: ${sessionId}`,
78
+ action: "resume",
79
+ entry,
80
+ });
81
+ }
70
82
 
71
83
  result.push({
72
84
  label: "Start new (previous settings)",
73
- value: `start-new-${entry.toolId}`,
85
+ value: `start-new-${suffix}`,
74
86
  description: settingsDesc,
75
87
  action: "start-new",
76
88
  entry,
77
89
  });
78
- }
90
+ });
79
91
 
80
92
  // Add "Choose different settings..." at the end
81
93
  result.push({
@@ -27,7 +27,7 @@ export function SearchInput({
27
27
  <input
28
28
  value={value}
29
29
  onChange={onChange}
30
- onSubmit={onSubmit}
30
+ {...(onSubmit ? { onSubmit } : {})}
31
31
  placeholder={placeholder}
32
32
  width={inputWidth}
33
33
  focused={isFocused}
@@ -1,6 +1,7 @@
1
1
  /** @jsxImportSource @opentui/solid */
2
2
  import type { SelectOption, SelectRenderable } from "@opentui/core";
3
3
  import type { Ref } from "solid-js";
4
+ import { selectionStyle } from "../../core/theme.js";
4
5
 
5
6
  export interface SelectInputItem {
6
7
  label: string;
@@ -69,6 +70,9 @@ export function SelectInput(props: SelectInputProps) {
69
70
  <select
70
71
  options={options()}
71
72
  height={computedHeight()}
73
+ selectedBackgroundColor={selectionStyle.bg}
74
+ selectedTextColor={selectionStyle.fg}
75
+ selectedDescriptionColor={selectionStyle.fg}
72
76
  {...(props.selectedIndex !== undefined && {
73
77
  selectedIndex: props.selectedIndex,
74
78
  })}