@akiojin/gwt 2.10.0 → 2.11.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.
- package/dist/cli/ui/components/App.d.ts.map +1 -1
- package/dist/cli/ui/components/App.js +5 -47
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/components/common/Input.d.ts +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts +1 -2
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js +19 -14
- package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/ModelSelectorScreen.js +6 -6
- package/dist/cli/ui/components/screens/ModelSelectorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/PRCleanupScreen.js +0 -3
- package/dist/cli/ui/components/screens/PRCleanupScreen.js.map +1 -1
- package/dist/cli/ui/hooks/useGitData.d.ts.map +1 -1
- package/dist/cli/ui/hooks/useGitData.js +43 -2
- package/dist/cli/ui/hooks/useGitData.js.map +1 -1
- package/dist/cli/ui/types.d.ts +16 -4
- package/dist/cli/ui/types.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.js +124 -28
- package/dist/cli/ui/utils/branchFormatter.js.map +1 -1
- package/dist/cli/ui/utils/modelOptions.d.ts.map +1 -1
- package/dist/cli/ui/utils/modelOptions.js.map +1 -1
- package/dist/client/assets/{index-CNWntAlF.js → index-Dl798X5w.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/git.d.ts +6 -0
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +40 -5
- package/dist/git.js.map +1 -1
- package/dist/web/client/src/components/BranchGraph.js +1 -1
- package/dist/web/client/src/components/BranchGraph.js.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.js +8 -3
- package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
- package/dist/web/server/routes/sessions.d.ts.map +1 -1
- package/dist/web/server/routes/sessions.js +4 -2
- package/dist/web/server/routes/sessions.js.map +1 -1
- package/dist/worktree.d.ts.map +1 -1
- package/dist/worktree.js +31 -44
- package/dist/worktree.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/ui/__tests__/acceptance/navigation.acceptance.test.tsx +9 -17
- package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +14 -20
- package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +14 -44
- package/src/cli/ui/__tests__/components/App.test.tsx +8 -15
- package/src/cli/ui/__tests__/components/ModelSelectorScreen.initial.test.tsx +12 -5
- package/src/cli/ui/__tests__/components/common/Confirm.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/common/ErrorBoundary.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/common/Input.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/common/LoadingIndicator.test.tsx +15 -14
- package/src/cli/ui/__tests__/components/common/Select.memo.test.tsx +3 -3
- package/src/cli/ui/__tests__/components/common/Select.test.tsx +1 -4
- package/src/cli/ui/__tests__/components/parts/Footer.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/parts/Header.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/parts/ScrollableList.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/parts/Stats.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/screens/AIToolSelectorScreen.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/screens/BranchCreatorScreen.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +33 -43
- package/src/cli/ui/__tests__/components/screens/ExecutionModeSelectorScreen.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/screens/PRCleanupScreen.test.tsx +3 -2
- package/src/cli/ui/__tests__/components/screens/SessionSelectorScreen.test.tsx +3 -2
- package/src/cli/ui/__tests__/hooks/useScreenState.test.ts +18 -17
- package/src/cli/ui/__tests__/hooks/useTerminalSize.test.ts +3 -2
- package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +5 -12
- package/src/cli/ui/__tests__/integration/navigation.test.tsx +15 -10
- package/src/cli/ui/__tests__/integration/realtimeUpdate.test.tsx +3 -2
- package/src/cli/ui/__tests__/performance/branchList.performance.test.tsx +0 -4
- package/src/cli/ui/__tests__/performance/useMemoOptimization.test.tsx +3 -5
- package/src/cli/ui/__tests__/utils/branchFormatter.test.ts +24 -23
- package/src/cli/ui/components/App.tsx +3 -74
- package/src/cli/ui/components/common/Input.tsx +1 -1
- package/src/cli/ui/components/screens/BranchListScreen.tsx +32 -22
- package/src/cli/ui/components/screens/ModelSelectorScreen.tsx +46 -49
- package/src/cli/ui/components/screens/PRCleanupScreen.tsx +0 -3
- package/src/cli/ui/hooks/useGitData.ts +59 -1
- package/src/cli/ui/screens/__tests__/BranchActionSelectorScreen.test.tsx +3 -2
- package/src/cli/ui/types.ts +24 -5
- package/src/cli/ui/utils/branchFormatter.ts +123 -28
- package/src/cli/ui/utils/modelOptions.test.ts +4 -6
- package/src/cli/ui/utils/modelOptions.ts +1 -2
- package/src/config/index.ts +2 -1
- package/src/git.ts +56 -16
- package/src/web/client/src/components/BranchGraph.tsx +1 -1
- package/src/web/client/src/pages/BranchDetailPage.tsx +8 -3
- package/src/web/server/routes/sessions.ts +12 -5
- package/src/worktree.ts +31 -59
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.d.ts +0 -20
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.d.ts.map +0 -1
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.js +0 -65
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.js.map +0 -1
- package/src/cli/ui/__tests__/components/screens/WorktreeManagerScreen.test.tsx +0 -151
- package/src/cli/ui/components/screens/WorktreeManagerScreen.tsx +0 -117
|
@@ -84,7 +84,6 @@ describe("BranchListScreen Performance", () => {
|
|
|
84
84
|
branches={branches}
|
|
85
85
|
stats={stats}
|
|
86
86
|
onSelect={() => {}}
|
|
87
|
-
onNavigate={() => {}}
|
|
88
87
|
onQuit={() => {}}
|
|
89
88
|
/>,
|
|
90
89
|
);
|
|
@@ -123,7 +122,6 @@ describe("BranchListScreen Performance", () => {
|
|
|
123
122
|
branches={branches}
|
|
124
123
|
stats={stats}
|
|
125
124
|
onSelect={() => {}}
|
|
126
|
-
onNavigate={() => {}}
|
|
127
125
|
onQuit={() => {}}
|
|
128
126
|
lastUpdated={new Date()}
|
|
129
127
|
/>,
|
|
@@ -137,7 +135,6 @@ describe("BranchListScreen Performance", () => {
|
|
|
137
135
|
branches={branches}
|
|
138
136
|
stats={{ ...stats, total: stats.total + 1 }}
|
|
139
137
|
onSelect={() => {}}
|
|
140
|
-
onNavigate={() => {}}
|
|
141
138
|
onQuit={() => {}}
|
|
142
139
|
lastUpdated={new Date()}
|
|
143
140
|
/>,
|
|
@@ -174,7 +171,6 @@ describe("BranchListScreen Performance", () => {
|
|
|
174
171
|
branches={branches}
|
|
175
172
|
stats={stats}
|
|
176
173
|
onSelect={() => {}}
|
|
177
|
-
onNavigate={() => {}}
|
|
178
174
|
onQuit={() => {}}
|
|
179
175
|
/>,
|
|
180
176
|
);
|
|
@@ -18,9 +18,6 @@ vi.mock("../../hooks/useGitData.js", () => ({
|
|
|
18
18
|
useGitData: vi.fn(),
|
|
19
19
|
}));
|
|
20
20
|
|
|
21
|
-
import { useGitData } from "../../hooks/useGitData.js";
|
|
22
|
-
// const mockUseGitData = useGitData as ReturnType<typeof vi.fn>;
|
|
23
|
-
|
|
24
21
|
// Helper function to create a stable hash of branch data
|
|
25
22
|
function createBranchHash(branches: BranchInfo[]): string {
|
|
26
23
|
return branches.map((b) => `${b.name}-${b.type}-${b.isCurrent}`).join(",");
|
|
@@ -63,8 +60,9 @@ function TestComponent({
|
|
|
63
60
|
describe("useMemo Optimization (T082-1)", () => {
|
|
64
61
|
beforeEach(() => {
|
|
65
62
|
const window = new Window();
|
|
66
|
-
globalThis.window = window as
|
|
67
|
-
globalThis.document =
|
|
63
|
+
globalThis.window = window as unknown as typeof globalThis.window;
|
|
64
|
+
globalThis.document =
|
|
65
|
+
window.document as unknown as typeof globalThis.document;
|
|
68
66
|
vi.clearAllMocks();
|
|
69
67
|
});
|
|
70
68
|
|
|
@@ -23,7 +23,7 @@ describe("branchFormatter", () => {
|
|
|
23
23
|
expect(result.branchType).toBe("main");
|
|
24
24
|
expect(result.isCurrent).toBe(true);
|
|
25
25
|
expect(result.icons).toContain("⚡"); // main icon
|
|
26
|
-
expect(result.icons).toContain("
|
|
26
|
+
expect(result.icons).toContain("👉"); // current icon
|
|
27
27
|
expect(result.label).toContain("main");
|
|
28
28
|
expect(result.value).toBe("main");
|
|
29
29
|
expect(result.hasChanges).toBe(false);
|
|
@@ -40,7 +40,7 @@ describe("branchFormatter", () => {
|
|
|
40
40
|
const result = formatBranchItem(branchInfo);
|
|
41
41
|
|
|
42
42
|
expect(result.icons).toContain("✨"); // feature icon
|
|
43
|
-
expect(result.icons).not.toContain("
|
|
43
|
+
expect(result.icons).not.toContain("👉"); // not current
|
|
44
44
|
expect(result.label).toContain("feature/new-ui");
|
|
45
45
|
expect(result.value).toBe("feature/new-ui");
|
|
46
46
|
});
|
|
@@ -65,7 +65,6 @@ describe("branchFormatter", () => {
|
|
|
65
65
|
const result = formatBranchItem(branchInfo);
|
|
66
66
|
|
|
67
67
|
expect(result.lastToolUsageLabel).toContain("Codex");
|
|
68
|
-
expect(result.lastToolUsageLabel).toContain("New");
|
|
69
68
|
expect(result.lastToolUsageLabel).toContain("2025-11-26");
|
|
70
69
|
});
|
|
71
70
|
|
|
@@ -157,12 +156,12 @@ describe("branchFormatter", () => {
|
|
|
157
156
|
const localResult = formatBranchItem(localBranch);
|
|
158
157
|
const remoteResult = formatBranchItem(remoteBranch);
|
|
159
158
|
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
// Both should have the branch name in the label
|
|
160
|
+
expect(localResult.label).toContain("feature/foo");
|
|
161
|
+
expect(remoteResult.label).toContain("origin/feature/foo");
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
expect(
|
|
165
|
-
expect(remoteResult.label).toMatch(/☁(?:️|︎)?\s+origin/);
|
|
163
|
+
// Remote branch should have ☁️ marker for remote-only status
|
|
164
|
+
expect(remoteResult.label).toMatch(/☁️/);
|
|
166
165
|
});
|
|
167
166
|
|
|
168
167
|
it("should keep icon columns fixed-width when using wide emoji icons", () => {
|
|
@@ -182,11 +181,13 @@ describe("branchFormatter", () => {
|
|
|
182
181
|
|
|
183
182
|
const result = formatBranchItem(branchInfo, { hasChanges: true });
|
|
184
183
|
|
|
185
|
-
//
|
|
184
|
+
// Icon columns: [Type][Worktree][Changes][Remote] = 4 * 2 = 8
|
|
185
|
+
// Sync column: 6 (fixed width for icon + up to 4 digits + space)
|
|
186
|
+
// Total: 14
|
|
186
187
|
const iconBlockWidth =
|
|
187
188
|
stringWidth(result.label) - stringWidth(branchInfo.name);
|
|
188
189
|
|
|
189
|
-
expect(iconBlockWidth).toBe(
|
|
190
|
+
expect(iconBlockWidth).toBe(14);
|
|
190
191
|
});
|
|
191
192
|
|
|
192
193
|
it("should include worktree status icon when provided", () => {
|
|
@@ -218,7 +219,7 @@ describe("branchFormatter", () => {
|
|
|
218
219
|
|
|
219
220
|
const result = formatBranchItem(branchInfo, { hasChanges: true });
|
|
220
221
|
|
|
221
|
-
expect(result.icons).toContain("
|
|
222
|
+
expect(result.icons).toContain("💾"); // changes icon
|
|
222
223
|
expect(result.hasChanges).toBe(true);
|
|
223
224
|
});
|
|
224
225
|
|
|
@@ -233,8 +234,8 @@ describe("branchFormatter", () => {
|
|
|
233
234
|
|
|
234
235
|
const result = formatBranchItem(branchInfo);
|
|
235
236
|
|
|
236
|
-
expect(result.icons).toContain("
|
|
237
|
-
expect(result.label).toContain("
|
|
237
|
+
expect(result.icons).toContain("📤"); // unpushed icon
|
|
238
|
+
expect(result.label).toContain("📤");
|
|
238
239
|
});
|
|
239
240
|
|
|
240
241
|
it("should show open PR icon", () => {
|
|
@@ -248,8 +249,8 @@ describe("branchFormatter", () => {
|
|
|
248
249
|
|
|
249
250
|
const result = formatBranchItem(branchInfo);
|
|
250
251
|
|
|
251
|
-
expect(result.icons).toContain("
|
|
252
|
-
expect(result.label).toContain("
|
|
252
|
+
expect(result.icons).toContain("🔃"); // open PR icon
|
|
253
|
+
expect(result.label).toContain("🔃");
|
|
253
254
|
});
|
|
254
255
|
|
|
255
256
|
it("should show merged PR icon", () => {
|
|
@@ -300,12 +301,12 @@ describe("branchFormatter", () => {
|
|
|
300
301
|
const resultWithChanges = formatBranchItem(branchInfo, {
|
|
301
302
|
hasChanges: true,
|
|
302
303
|
});
|
|
303
|
-
expect(resultWithChanges.icons).toContain("
|
|
304
|
-
expect(resultWithChanges.icons).not.toContain("
|
|
304
|
+
expect(resultWithChanges.icons).toContain("💾");
|
|
305
|
+
expect(resultWithChanges.icons).not.toContain("📤");
|
|
305
306
|
|
|
306
307
|
const resultWithoutChanges = formatBranchItem(branchInfo);
|
|
307
|
-
expect(resultWithoutChanges.icons).toContain("
|
|
308
|
-
expect(resultWithoutChanges.icons).not.toContain("
|
|
308
|
+
expect(resultWithoutChanges.icons).toContain("📤");
|
|
309
|
+
expect(resultWithoutChanges.icons).not.toContain("💾");
|
|
309
310
|
});
|
|
310
311
|
|
|
311
312
|
it("should prioritize unpushed over open PR", () => {
|
|
@@ -320,8 +321,8 @@ describe("branchFormatter", () => {
|
|
|
320
321
|
|
|
321
322
|
const result = formatBranchItem(branchInfo);
|
|
322
323
|
|
|
323
|
-
expect(result.icons).toContain("
|
|
324
|
-
expect(result.icons).not.toContain("
|
|
324
|
+
expect(result.icons).toContain("📤");
|
|
325
|
+
expect(result.icons).not.toContain("🔃");
|
|
325
326
|
});
|
|
326
327
|
|
|
327
328
|
it("should prioritize open PR over merged PR", () => {
|
|
@@ -336,7 +337,7 @@ describe("branchFormatter", () => {
|
|
|
336
337
|
|
|
337
338
|
const result = formatBranchItem(branchInfo);
|
|
338
339
|
|
|
339
|
-
expect(result.icons).toContain("
|
|
340
|
+
expect(result.icons).toContain("🔃");
|
|
340
341
|
expect(result.icons).not.toContain("✅");
|
|
341
342
|
});
|
|
342
343
|
|
|
@@ -378,7 +379,7 @@ describe("branchFormatter", () => {
|
|
|
378
379
|
const result = formatBranchItem(branchInfo);
|
|
379
380
|
|
|
380
381
|
expect(result.icons).toContain("⚠️");
|
|
381
|
-
expect(result.icons).not.toContain("
|
|
382
|
+
expect(result.icons).not.toContain("👉");
|
|
382
383
|
});
|
|
383
384
|
|
|
384
385
|
it("should handle develop branch", () => {
|
|
@@ -8,7 +8,6 @@ import React, {
|
|
|
8
8
|
import { useApp } from "ink";
|
|
9
9
|
import { ErrorBoundary } from "./common/ErrorBoundary.js";
|
|
10
10
|
import { BranchListScreen } from "./screens/BranchListScreen.js";
|
|
11
|
-
import { WorktreeManagerScreen } from "./screens/WorktreeManagerScreen.js";
|
|
12
11
|
import { BranchCreatorScreen } from "./screens/BranchCreatorScreen.js";
|
|
13
12
|
import { BranchActionSelectorScreen } from "../screens/BranchActionSelectorScreen.js";
|
|
14
13
|
import { AIToolSelectorScreen } from "./screens/AIToolSelectorScreen.js";
|
|
@@ -19,7 +18,6 @@ import {
|
|
|
19
18
|
ModelSelectorScreen,
|
|
20
19
|
type ModelSelectionResult,
|
|
21
20
|
} from "./screens/ModelSelectorScreen.js";
|
|
22
|
-
import type { WorktreeItem } from "./screens/WorktreeManagerScreen.js";
|
|
23
21
|
import { useGitData } from "../hooks/useGitData.js";
|
|
24
22
|
import { useScreenState } from "../hooks/useScreenState.js";
|
|
25
23
|
import { formatBranchItems } from "../utils/branchFormatter.js";
|
|
@@ -31,11 +29,7 @@ import type {
|
|
|
31
29
|
InferenceLevel,
|
|
32
30
|
SelectedBranchState,
|
|
33
31
|
} from "../types.js";
|
|
34
|
-
import {
|
|
35
|
-
getRepositoryRoot,
|
|
36
|
-
deleteBranch,
|
|
37
|
-
deleteRemoteBranch,
|
|
38
|
-
} from "../../../git.js";
|
|
32
|
+
import { getRepositoryRoot, deleteBranch } from "../../../git.js";
|
|
39
33
|
import {
|
|
40
34
|
createWorktree,
|
|
41
35
|
generateWorktreePath,
|
|
@@ -269,19 +263,6 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
269
263
|
[visibleBranches],
|
|
270
264
|
);
|
|
271
265
|
|
|
272
|
-
// Format worktrees to WorktreeItems
|
|
273
|
-
const worktreeItems: WorktreeItem[] = useMemo(
|
|
274
|
-
() =>
|
|
275
|
-
worktrees.map(
|
|
276
|
-
(wt): WorktreeItem => ({
|
|
277
|
-
branch: wt.branch,
|
|
278
|
-
path: wt.path,
|
|
279
|
-
isAccessible: wt.isAccessible ?? true,
|
|
280
|
-
}),
|
|
281
|
-
),
|
|
282
|
-
[worktrees],
|
|
283
|
-
);
|
|
284
|
-
|
|
285
266
|
const resolveBaseBranch = useCallback(() => {
|
|
286
267
|
const localMain = branches.find(
|
|
287
268
|
(branch) =>
|
|
@@ -428,40 +409,6 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
428
409
|
],
|
|
429
410
|
);
|
|
430
411
|
|
|
431
|
-
// Handle navigation
|
|
432
|
-
const handleNavigate = useCallback(
|
|
433
|
-
(screen: string) => {
|
|
434
|
-
navigateTo(screen as any);
|
|
435
|
-
},
|
|
436
|
-
[navigateTo],
|
|
437
|
-
);
|
|
438
|
-
|
|
439
|
-
const handleWorktreeSelect = useCallback(
|
|
440
|
-
(worktree: WorktreeItem) => {
|
|
441
|
-
const lastTool = branches.find((b) => b.name === worktree.branch)
|
|
442
|
-
?.lastToolUsage?.toolId;
|
|
443
|
-
setSelectedBranch({
|
|
444
|
-
name: worktree.branch,
|
|
445
|
-
displayName: worktree.branch,
|
|
446
|
-
branchType: "local",
|
|
447
|
-
branchCategory: inferBranchCategory(worktree.branch),
|
|
448
|
-
});
|
|
449
|
-
setSelectedTool(null);
|
|
450
|
-
setSelectedModel(null);
|
|
451
|
-
setCreationSourceBranch(null);
|
|
452
|
-
setPreferredToolId(lastTool ?? null);
|
|
453
|
-
setCleanupFooterMessage(null);
|
|
454
|
-
navigateTo("ai-tool-selector");
|
|
455
|
-
},
|
|
456
|
-
[
|
|
457
|
-
inferBranchCategory,
|
|
458
|
-
navigateTo,
|
|
459
|
-
setCleanupFooterMessage,
|
|
460
|
-
setCreationSourceBranch,
|
|
461
|
-
branches,
|
|
462
|
-
],
|
|
463
|
-
);
|
|
464
|
-
|
|
465
412
|
// Handle branch action selection
|
|
466
413
|
const handleProtectedBranchSwitch = useCallback(async () => {
|
|
467
414
|
if (!selectedBranch) {
|
|
@@ -733,14 +680,8 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
733
680
|
|
|
734
681
|
await deleteBranch(target.branch, true);
|
|
735
682
|
|
|
736
|
-
//
|
|
737
|
-
|
|
738
|
-
try {
|
|
739
|
-
await deleteRemoteBranch(target.branch);
|
|
740
|
-
} catch {
|
|
741
|
-
// リモート削除失敗はログのみ、処理は続行
|
|
742
|
-
}
|
|
743
|
-
}
|
|
683
|
+
// 自動クリーンアップではリモートブランチは削除しない
|
|
684
|
+
// リモートブランチはユーザーが明示的に削除する必要がある
|
|
744
685
|
|
|
745
686
|
succeededBranches.push(target.branch);
|
|
746
687
|
setCleanupIndicators((prev) => ({
|
|
@@ -864,7 +805,6 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
864
805
|
branches={branchItems}
|
|
865
806
|
stats={stats}
|
|
866
807
|
onSelect={handleSelect}
|
|
867
|
-
onNavigate={handleNavigate}
|
|
868
808
|
onQuit={handleQuit}
|
|
869
809
|
onCleanupCommand={handleCleanupCommand}
|
|
870
810
|
onRefresh={refresh}
|
|
@@ -882,16 +822,6 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
882
822
|
/>
|
|
883
823
|
);
|
|
884
824
|
|
|
885
|
-
case "worktree-manager":
|
|
886
|
-
return (
|
|
887
|
-
<WorktreeManagerScreen
|
|
888
|
-
worktrees={worktreeItems}
|
|
889
|
-
onBack={goBack}
|
|
890
|
-
onSelect={handleWorktreeSelect}
|
|
891
|
-
version={version}
|
|
892
|
-
/>
|
|
893
|
-
);
|
|
894
|
-
|
|
895
825
|
case "branch-creator":
|
|
896
826
|
return (
|
|
897
827
|
<BranchCreatorScreen
|
|
@@ -978,7 +908,6 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
978
908
|
branches={branchItems}
|
|
979
909
|
stats={stats}
|
|
980
910
|
onSelect={handleSelect}
|
|
981
|
-
onNavigate={handleNavigate}
|
|
982
911
|
onQuit={handleQuit}
|
|
983
912
|
onRefresh={refresh}
|
|
984
913
|
loading={loading}
|
|
@@ -11,7 +11,7 @@ export interface InputProps {
|
|
|
11
11
|
mask?: string;
|
|
12
12
|
/**
|
|
13
13
|
* Block specific key bindings to prevent parent handlers from processing them
|
|
14
|
-
* Useful for blocking shortcuts like 'c', 'r'
|
|
14
|
+
* Useful for blocking shortcuts like 'c', 'r' while typing
|
|
15
15
|
*/
|
|
16
16
|
blockKeys?: string[];
|
|
17
17
|
}
|
|
@@ -14,8 +14,6 @@ import chalk from "chalk";
|
|
|
14
14
|
const WIDTH_OVERRIDES: Record<string, number> = {
|
|
15
15
|
// Remote icon
|
|
16
16
|
"☁": 1,
|
|
17
|
-
// Unpushed icon
|
|
18
|
-
"⬆": 1,
|
|
19
17
|
// Branch type icons
|
|
20
18
|
"⚡": 1,
|
|
21
19
|
"✨": 1,
|
|
@@ -27,11 +25,16 @@ const WIDTH_OVERRIDES: Record<string, number> = {
|
|
|
27
25
|
"🟢": 1,
|
|
28
26
|
"🟠": 1,
|
|
29
27
|
// Change status icons
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
28
|
+
"👉": 1,
|
|
29
|
+
"💾": 1,
|
|
30
|
+
"📤": 1,
|
|
31
|
+
"🔃": 1,
|
|
33
32
|
"✅": 1,
|
|
34
33
|
"⚠️": 1,
|
|
34
|
+
// Remote markers
|
|
35
|
+
"🔗": 1,
|
|
36
|
+
"💻": 1,
|
|
37
|
+
"☁️": 1,
|
|
35
38
|
};
|
|
36
39
|
|
|
37
40
|
const getCharWidth = (char: string): number => {
|
|
@@ -70,7 +73,6 @@ export interface BranchListScreenProps {
|
|
|
70
73
|
branches: BranchItem[];
|
|
71
74
|
stats: Statistics;
|
|
72
75
|
onSelect: (branch: BranchItem) => void;
|
|
73
|
-
onNavigate?: (screen: string) => void;
|
|
74
76
|
onQuit?: () => void;
|
|
75
77
|
onCleanupCommand?: () => void;
|
|
76
78
|
onRefresh?: () => void;
|
|
@@ -96,7 +98,6 @@ export function BranchListScreen({
|
|
|
96
98
|
branches,
|
|
97
99
|
stats,
|
|
98
100
|
onSelect,
|
|
99
|
-
onNavigate,
|
|
100
101
|
onCleanupCommand,
|
|
101
102
|
onRefresh,
|
|
102
103
|
loading = false,
|
|
@@ -112,6 +113,9 @@ export function BranchListScreen({
|
|
|
112
113
|
testOnFilterQueryChange,
|
|
113
114
|
}: BranchListScreenProps) {
|
|
114
115
|
const { rows } = useTerminalSize();
|
|
116
|
+
const COLUMN_WIDTH = 2;
|
|
117
|
+
const SYNC_COLUMN_WIDTH = 6;
|
|
118
|
+
const headerText = ` ${"Ty".padEnd(COLUMN_WIDTH)}${"Wt".padEnd(COLUMN_WIDTH)}${"St".padEnd(COLUMN_WIDTH)}${"Rm".padEnd(COLUMN_WIDTH)}${"Sync".padEnd(SYNC_COLUMN_WIDTH)}Branch`;
|
|
115
119
|
|
|
116
120
|
// Filter state - allow test control via props
|
|
117
121
|
const [internalFilterQuery, setInternalFilterQuery] = useState("");
|
|
@@ -139,7 +143,7 @@ export function BranchListScreen({
|
|
|
139
143
|
);
|
|
140
144
|
|
|
141
145
|
// Handle keyboard input
|
|
142
|
-
// Note: Input component blocks specific keys (c/r/
|
|
146
|
+
// Note: Input component blocks specific keys (c/r/f) using blockKeys prop
|
|
143
147
|
// This prevents shortcuts from triggering while typing in the filter
|
|
144
148
|
useInput((input, key) => {
|
|
145
149
|
if (cleanupUI?.inputLocked) {
|
|
@@ -172,9 +176,7 @@ export function BranchListScreen({
|
|
|
172
176
|
}
|
|
173
177
|
|
|
174
178
|
// Global shortcuts (blocked by Input component when typing in filter mode)
|
|
175
|
-
if (input === "
|
|
176
|
-
onNavigate("worktree-manager");
|
|
177
|
-
} else if (input === "c") {
|
|
179
|
+
if (input === "c") {
|
|
178
180
|
onCleanupCommand?.();
|
|
179
181
|
} else if (input === "r" && onRefresh) {
|
|
180
182
|
onRefresh();
|
|
@@ -225,7 +227,6 @@ export function BranchListScreen({
|
|
|
225
227
|
{ key: "enter", description: "Select" },
|
|
226
228
|
{ key: "f", description: "Filter" },
|
|
227
229
|
{ key: "r", description: "Refresh" },
|
|
228
|
-
{ key: "m", description: "Manage worktrees" },
|
|
229
230
|
{ key: "c", description: "Cleanup branches" },
|
|
230
231
|
];
|
|
231
232
|
|
|
@@ -311,7 +312,10 @@ export function BranchListScreen({
|
|
|
311
312
|
const staticPrefix = `${arrow} ${indicatorPrefix}`;
|
|
312
313
|
const staticPrefixWidth = measureDisplayWidth(staticPrefix);
|
|
313
314
|
const maxLeftDisplayWidth = Math.max(0, columns - timestampWidth - 1);
|
|
314
|
-
const maxLabelWidth = Math.max(
|
|
315
|
+
const maxLabelWidth = Math.max(
|
|
316
|
+
0,
|
|
317
|
+
maxLeftDisplayWidth - staticPrefixWidth,
|
|
318
|
+
);
|
|
315
319
|
const truncatedLabel = truncateToWidth(item.label, maxLabelWidth);
|
|
316
320
|
const leftText = `${staticPrefix}${truncatedLabel}`;
|
|
317
321
|
|
|
@@ -350,7 +354,7 @@ export function BranchListScreen({
|
|
|
350
354
|
onChange={setFilterQuery}
|
|
351
355
|
onSubmit={() => {}} // No-op: filter is applied in real-time
|
|
352
356
|
placeholder="Type to search..."
|
|
353
|
-
blockKeys={["c", "r", "
|
|
357
|
+
blockKeys={["c", "r", "f"]} // Block shortcuts while typing
|
|
354
358
|
/>
|
|
355
359
|
) : (
|
|
356
360
|
<Text dimColor>{filterQuery || "(press f to filter)"}</Text>
|
|
@@ -409,14 +413,20 @@ export function BranchListScreen({
|
|
|
409
413
|
!error &&
|
|
410
414
|
branches.length > 0 &&
|
|
411
415
|
filteredBranches.length > 0 && (
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
416
|
+
<>
|
|
417
|
+
{/* Column labels */}
|
|
418
|
+
<Box>
|
|
419
|
+
<Text dimColor>{headerText}</Text>
|
|
420
|
+
</Box>
|
|
421
|
+
<Select
|
|
422
|
+
items={filteredBranches}
|
|
423
|
+
onSelect={onSelect}
|
|
424
|
+
limit={limit}
|
|
425
|
+
disabled={Boolean(cleanupUI?.inputLocked)}
|
|
426
|
+
renderIndicator={() => null}
|
|
427
|
+
renderItem={renderBranchRow}
|
|
428
|
+
/>
|
|
429
|
+
</>
|
|
420
430
|
)}
|
|
421
431
|
</Box>
|
|
422
432
|
|
|
@@ -106,56 +106,54 @@ export function ModelSelectorScreen({
|
|
|
106
106
|
[selectedModel],
|
|
107
107
|
);
|
|
108
108
|
|
|
109
|
-
const inferenceItems: InferenceSelectItem[] = useMemo(
|
|
110
|
-
() => {
|
|
111
|
-
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
if (level === "medium") {
|
|
121
|
-
return {
|
|
122
|
-
label: "Medium (default)",
|
|
123
|
-
value: level,
|
|
124
|
-
hint: "Balances speed and reasoning depth for everyday tasks",
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
if (level === "high") {
|
|
128
|
-
return {
|
|
129
|
-
label: "High",
|
|
130
|
-
value: level,
|
|
131
|
-
hint: "Maximizes reasoning depth for complex problems",
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
if (level === "xhigh") {
|
|
135
|
-
return {
|
|
136
|
-
label: "Extra high",
|
|
137
|
-
value: level,
|
|
138
|
-
hint:
|
|
139
|
-
"Extra high reasoning depth; may quickly consume Plus plan rate limits.",
|
|
140
|
-
};
|
|
141
|
-
}
|
|
109
|
+
const inferenceItems: InferenceSelectItem[] = useMemo(() => {
|
|
110
|
+
return inferenceOptions.map((level) => {
|
|
111
|
+
if (selectedModel?.id === "gpt-5.1-codex-max") {
|
|
112
|
+
if (level === "low") {
|
|
113
|
+
return {
|
|
114
|
+
label: "Low",
|
|
115
|
+
value: level,
|
|
116
|
+
hint: "Fast responses with lighter reasoning",
|
|
117
|
+
};
|
|
142
118
|
}
|
|
119
|
+
if (level === "medium") {
|
|
120
|
+
return {
|
|
121
|
+
label: "Medium (default)",
|
|
122
|
+
value: level,
|
|
123
|
+
hint: "Balances speed and reasoning depth for everyday tasks",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (level === "high") {
|
|
127
|
+
return {
|
|
128
|
+
label: "High",
|
|
129
|
+
value: level,
|
|
130
|
+
hint: "Maximizes reasoning depth for complex problems",
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (level === "xhigh") {
|
|
134
|
+
return {
|
|
135
|
+
label: "Extra high",
|
|
136
|
+
value: level,
|
|
137
|
+
hint: "Extra high reasoning depth; may quickly consume Plus plan rate limits.",
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
143
141
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
[inferenceOptions, selectedModel?.id],
|
|
151
|
-
);
|
|
142
|
+
return {
|
|
143
|
+
label: INFERENCE_LABELS[level],
|
|
144
|
+
value: level,
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
}, [inferenceOptions, selectedModel?.id]);
|
|
152
148
|
|
|
153
149
|
const defaultInferenceIndex = useMemo(() => {
|
|
154
150
|
const initialLevel = initialSelection?.inferenceLevel;
|
|
155
151
|
if (initialLevel && inferenceOptions.includes(initialLevel)) {
|
|
156
152
|
return inferenceOptions.findIndex((lvl) => lvl === initialLevel);
|
|
157
153
|
}
|
|
158
|
-
const defaultLevel = getDefaultInferenceForModel(
|
|
154
|
+
const defaultLevel = getDefaultInferenceForModel(
|
|
155
|
+
selectedModel ?? undefined,
|
|
156
|
+
);
|
|
159
157
|
if (!defaultLevel) return 0;
|
|
160
158
|
const index = inferenceOptions.findIndex((lvl) => lvl === defaultLevel);
|
|
161
159
|
return index >= 0 ? index : 0;
|
|
@@ -223,11 +221,9 @@ export function ModelSelectorScreen({
|
|
|
223
221
|
{isSelected ? (
|
|
224
222
|
<Text color="cyan">➤ {item.label}</Text>
|
|
225
223
|
) : (
|
|
226
|
-
<Text>
|
|
224
|
+
<Text> {item.label}</Text>
|
|
227
225
|
)}
|
|
228
|
-
{item.description ?
|
|
229
|
-
<Text color="gray"> {item.description}</Text>
|
|
230
|
-
) : null}
|
|
226
|
+
{item.description ? <Text color="gray"> {item.description}</Text> : null}
|
|
231
227
|
</Box>
|
|
232
228
|
);
|
|
233
229
|
|
|
@@ -271,7 +267,8 @@ export function ModelSelectorScreen({
|
|
|
271
267
|
<Select
|
|
272
268
|
items={[
|
|
273
269
|
{
|
|
274
|
-
label:
|
|
270
|
+
label:
|
|
271
|
+
"No model selection required. Press Enter to continue.",
|
|
275
272
|
value: "__continue__",
|
|
276
273
|
},
|
|
277
274
|
]}
|
|
@@ -302,10 +299,10 @@ export function ModelSelectorScreen({
|
|
|
302
299
|
{isSelected ? (
|
|
303
300
|
<Text color="cyan">➤ {item.label}</Text>
|
|
304
301
|
) : (
|
|
305
|
-
<Text>
|
|
302
|
+
<Text> {item.label}</Text>
|
|
306
303
|
)}
|
|
307
304
|
{"hint" in item && item.hint ? (
|
|
308
|
-
<Text color="gray">
|
|
305
|
+
<Text color="gray"> {item.hint}</Text>
|
|
309
306
|
) : null}
|
|
310
307
|
</Box>
|
|
311
308
|
)}
|