@akiojin/gwt 2.2.0 → 2.4.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.
- package/README.ja.md +6 -4
- package/README.md +6 -4
- package/dist/claude.d.ts +1 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +6 -3
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/components/App.d.ts +6 -4
- package/dist/cli/ui/components/App.d.ts.map +1 -1
- package/dist/cli/ui/components/App.js +184 -107
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/components/common/Confirm.d.ts +1 -1
- package/dist/cli/ui/components/common/Confirm.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Confirm.js +7 -7
- package/dist/cli/ui/components/common/Confirm.js.map +1 -1
- package/dist/cli/ui/components/common/ErrorBoundary.d.ts +1 -1
- package/dist/cli/ui/components/common/ErrorBoundary.d.ts.map +1 -1
- package/dist/cli/ui/components/common/ErrorBoundary.js +4 -4
- package/dist/cli/ui/components/common/ErrorBoundary.js.map +1 -1
- package/dist/cli/ui/components/common/Input.d.ts +2 -2
- package/dist/cli/ui/components/common/Input.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Input.js +4 -4
- package/dist/cli/ui/components/common/Input.js.map +1 -1
- package/dist/cli/ui/components/common/LoadingIndicator.d.ts +1 -1
- package/dist/cli/ui/components/common/LoadingIndicator.d.ts.map +1 -1
- package/dist/cli/ui/components/common/LoadingIndicator.js +4 -4
- package/dist/cli/ui/components/common/LoadingIndicator.js.map +1 -1
- package/dist/cli/ui/components/common/Select.d.ts +1 -1
- package/dist/cli/ui/components/common/Select.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Select.js +11 -12
- package/dist/cli/ui/components/common/Select.js.map +1 -1
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts +3 -3
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.js +11 -11
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts +1 -1
- package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchCreatorScreen.js +39 -36
- package/dist/cli/ui/components/screens/BranchCreatorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts +3 -3
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js +55 -50
- package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts +2 -2
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js +25 -25
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts +18 -0
- package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts.map +1 -0
- package/dist/cli/ui/components/screens/ModelSelectorScreen.js +201 -0
- package/dist/cli/ui/components/screens/ModelSelectorScreen.js.map +1 -0
- package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts +2 -2
- package/dist/cli/ui/components/screens/PRCleanupScreen.js +21 -21
- package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts +1 -1
- package/dist/cli/ui/components/screens/SessionSelectorScreen.js +8 -8
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.d.ts +1 -1
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.js +8 -8
- package/dist/cli/ui/screens/BranchActionSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/BranchActionSelectorScreen.js +7 -4
- package/dist/cli/ui/screens/BranchActionSelectorScreen.js.map +1 -1
- package/dist/cli/ui/types.d.ts +11 -1
- package/dist/cli/ui/types.d.ts.map +1 -1
- package/dist/cli/ui/utils/modelOptions.d.ts +6 -0
- package/dist/cli/ui/utils/modelOptions.d.ts.map +1 -0
- package/dist/cli/ui/utils/modelOptions.js +111 -0
- package/dist/cli/ui/utils/modelOptions.js.map +1 -0
- package/dist/client/assets/{index-V6hDu9KS.js → index-Difv1Hwu.js} +2 -2
- package/dist/client/index.html +1 -1
- package/dist/codex.d.ts +6 -0
- package/dist/codex.d.ts.map +1 -1
- package/dist/codex.js +11 -4
- package/dist/codex.js.map +1 -1
- package/dist/config/builtin-tools.d.ts +10 -2
- package/dist/config/builtin-tools.d.ts.map +1 -1
- package/dist/config/builtin-tools.js +40 -4
- package/dist/config/builtin-tools.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/tools.d.ts.map +1 -1
- package/dist/config/tools.js +4 -3
- package/dist/config/tools.js.map +1 -1
- package/dist/gemini.d.ts +13 -0
- package/dist/gemini.d.ts.map +1 -0
- package/dist/gemini.js +157 -0
- package/dist/gemini.js.map +1 -0
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +59 -7
- package/dist/index.js.map +1 -1
- package/dist/qwen.d.ts +13 -0
- package/dist/qwen.d.ts.map +1 -0
- package/dist/qwen.js +157 -0
- package/dist/qwen.js.map +1 -0
- package/dist/services/git.service.d.ts.map +1 -1
- package/dist/services/git.service.js.map +1 -1
- package/dist/web/client/src/components/BranchGraph.d.ts.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/components/EnvEditor.d.ts.map +1 -1
- package/dist/web/client/src/components/EnvEditor.js +7 -4
- package/dist/web/client/src/components/EnvEditor.js.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.js +55 -18
- package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
- package/dist/web/client/src/pages/BranchListPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/BranchListPage.js +10 -4
- package/dist/web/client/src/pages/BranchListPage.js.map +1 -1
- package/dist/web/client/src/pages/ConfigManagementPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/ConfigManagementPage.js +4 -2
- package/dist/web/client/src/pages/ConfigManagementPage.js.map +1 -1
- package/package.json +2 -1
- package/src/claude.ts +8 -3
- package/src/cli/ui/__tests__/acceptance/navigation.acceptance.test.tsx +69 -50
- package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +67 -45
- package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +117 -75
- package/src/cli/ui/__tests__/components/App.test.tsx +45 -37
- package/src/cli/ui/__tests__/components/ModelSelectorScreen.initial.test.tsx +81 -0
- package/src/cli/ui/__tests__/components/common/Confirm.test.tsx +35 -22
- package/src/cli/ui/__tests__/components/common/ErrorBoundary.test.tsx +22 -22
- package/src/cli/ui/__tests__/components/common/Input.test.tsx +29 -22
- package/src/cli/ui/__tests__/components/common/LoadingIndicator.test.tsx +63 -43
- package/src/cli/ui/__tests__/components/common/Select.memo.test.tsx +57 -66
- package/src/cli/ui/__tests__/components/common/Select.test.tsx +121 -91
- package/src/cli/ui/__tests__/components/parts/Footer.test.tsx +18 -16
- package/src/cli/ui/__tests__/components/parts/Header.test.tsx +13 -13
- package/src/cli/ui/__tests__/components/parts/ScrollableList.test.tsx +20 -20
- package/src/cli/ui/__tests__/components/parts/Stats.test.tsx +38 -26
- package/src/cli/ui/__tests__/components/screens/AIToolSelectorScreen.test.tsx +31 -31
- package/src/cli/ui/__tests__/components/screens/BranchCreatorScreen.test.tsx +73 -37
- package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +261 -153
- package/src/cli/ui/__tests__/components/screens/ExecutionModeSelectorScreen.test.tsx +38 -32
- package/src/cli/ui/__tests__/components/screens/PRCleanupScreen.test.tsx +39 -39
- package/src/cli/ui/__tests__/components/screens/SessionSelectorScreen.test.tsx +49 -21
- package/src/cli/ui/__tests__/components/screens/WorktreeManagerScreen.test.tsx +52 -28
- package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +84 -48
- package/src/cli/ui/__tests__/integration/navigation.test.tsx +111 -83
- package/src/cli/ui/__tests__/integration/realtimeUpdate.test.tsx +111 -108
- package/src/cli/ui/__tests__/performance/branchList.performance.test.tsx +50 -37
- package/src/cli/ui/__tests__/performance/useMemoOptimization.test.tsx +75 -76
- package/src/cli/ui/components/App.tsx +317 -150
- package/src/cli/ui/components/common/Confirm.tsx +13 -9
- package/src/cli/ui/components/common/ErrorBoundary.tsx +8 -5
- package/src/cli/ui/components/common/Input.tsx +12 -4
- package/src/cli/ui/components/common/LoadingIndicator.tsx +8 -5
- package/src/cli/ui/components/common/Select.tsx +28 -17
- package/src/cli/ui/components/parts/Header.test.tsx +5 -15
- package/src/cli/ui/components/screens/AIToolSelectorScreen.tsx +20 -15
- package/src/cli/ui/components/screens/BranchCreatorScreen.tsx +74 -54
- package/src/cli/ui/components/screens/BranchListScreen.tsx +92 -75
- package/src/cli/ui/components/screens/ExecutionModeSelectorScreen.tsx +35 -28
- package/src/cli/ui/components/screens/ModelSelectorScreen.tsx +320 -0
- package/src/cli/ui/components/screens/PRCleanupScreen.tsx +22 -22
- package/src/cli/ui/components/screens/SessionSelectorScreen.tsx +8 -8
- package/src/cli/ui/components/screens/WorktreeManagerScreen.tsx +8 -8
- package/src/cli/ui/screens/BranchActionSelectorScreen.tsx +9 -4
- package/src/cli/ui/types.ts +21 -1
- package/src/cli/ui/utils/modelOptions.test.ts +36 -0
- package/src/cli/ui/utils/modelOptions.ts +122 -0
- package/src/codex.ts +23 -4
- package/src/config/builtin-tools.ts +42 -4
- package/src/config/index.ts +2 -12
- package/src/config/tools.ts +16 -6
- package/src/gemini.ts +207 -0
- package/src/git.ts +2 -1
- package/src/index.ts +86 -6
- package/src/qwen.ts +213 -0
- package/src/services/git.service.ts +2 -1
- package/src/web/client/src/components/BranchGraph.tsx +3 -2
- package/src/web/client/src/components/EnvEditor.tsx +44 -11
- package/src/web/client/src/pages/BranchDetailPage.tsx +165 -54
- package/src/web/client/src/pages/BranchListPage.tsx +37 -13
- package/src/web/client/src/pages/ConfigManagementPage.tsx +28 -9
|
@@ -1,22 +1,37 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
19
|
-
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from "react";
|
|
8
|
+
import { useApp } from "ink";
|
|
9
|
+
import { ErrorBoundary } from "./common/ErrorBoundary.js";
|
|
10
|
+
import { BranchListScreen } from "./screens/BranchListScreen.js";
|
|
11
|
+
import { WorktreeManagerScreen } from "./screens/WorktreeManagerScreen.js";
|
|
12
|
+
import { BranchCreatorScreen } from "./screens/BranchCreatorScreen.js";
|
|
13
|
+
import { BranchActionSelectorScreen } from "../screens/BranchActionSelectorScreen.js";
|
|
14
|
+
import { AIToolSelectorScreen } from "./screens/AIToolSelectorScreen.js";
|
|
15
|
+
import { SessionSelectorScreen } from "./screens/SessionSelectorScreen.js";
|
|
16
|
+
import { ExecutionModeSelectorScreen } from "./screens/ExecutionModeSelectorScreen.js";
|
|
17
|
+
import type { ExecutionMode } from "./screens/ExecutionModeSelectorScreen.js";
|
|
18
|
+
import {
|
|
19
|
+
ModelSelectorScreen,
|
|
20
|
+
type ModelSelectionResult,
|
|
21
|
+
} from "./screens/ModelSelectorScreen.js";
|
|
22
|
+
import type { WorktreeItem } from "./screens/WorktreeManagerScreen.js";
|
|
23
|
+
import { useGitData } from "../hooks/useGitData.js";
|
|
24
|
+
import { useScreenState } from "../hooks/useScreenState.js";
|
|
25
|
+
import { formatBranchItems } from "../utils/branchFormatter.js";
|
|
26
|
+
import { calculateStatistics } from "../utils/statisticsCalculator.js";
|
|
27
|
+
import type {
|
|
28
|
+
AITool,
|
|
29
|
+
BranchInfo,
|
|
30
|
+
BranchItem,
|
|
31
|
+
InferenceLevel,
|
|
32
|
+
SelectedBranchState,
|
|
33
|
+
} from "../types.js";
|
|
34
|
+
import { getRepositoryRoot, deleteBranch } from "../../../git.js";
|
|
20
35
|
import {
|
|
21
36
|
createWorktree,
|
|
22
37
|
generateWorktreePath,
|
|
@@ -24,34 +39,40 @@ import {
|
|
|
24
39
|
isProtectedBranchName,
|
|
25
40
|
removeWorktree,
|
|
26
41
|
switchToProtectedBranch,
|
|
27
|
-
} from
|
|
28
|
-
import { getPackageVersion } from
|
|
42
|
+
} from "../../../worktree.js";
|
|
43
|
+
import { getPackageVersion } from "../../../utils.js";
|
|
29
44
|
import {
|
|
30
45
|
resolveBaseBranchLabel,
|
|
31
46
|
resolveBaseBranchRef,
|
|
32
|
-
} from
|
|
47
|
+
} from "../utils/baseBranch.js";
|
|
48
|
+
import {
|
|
49
|
+
getDefaultInferenceForModel,
|
|
50
|
+
getDefaultModelOption,
|
|
51
|
+
} from "../utils/modelOptions.js";
|
|
33
52
|
|
|
34
|
-
const SPINNER_FRAMES = [
|
|
53
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧"];
|
|
35
54
|
const COMPLETION_HOLD_DURATION_MS = 3000;
|
|
36
55
|
const PROTECTED_BRANCH_WARNING =
|
|
37
|
-
|
|
56
|
+
"Root branches operate directly in the repository root. Create a new branch if you need a dedicated worktree.";
|
|
38
57
|
|
|
39
58
|
const getSpinnerFrame = (index: number): string => {
|
|
40
59
|
const frame = SPINNER_FRAMES[index];
|
|
41
|
-
if (typeof frame ===
|
|
60
|
+
if (typeof frame === "string") {
|
|
42
61
|
return frame;
|
|
43
62
|
}
|
|
44
|
-
return SPINNER_FRAMES[0] ??
|
|
63
|
+
return SPINNER_FRAMES[0] ?? "⠋";
|
|
45
64
|
};
|
|
46
65
|
|
|
47
66
|
export interface SelectionResult {
|
|
48
67
|
branch: string; // Local branch name (without remote prefix)
|
|
49
68
|
displayName: string; // Name that was selected in the UI (may include remote prefix)
|
|
50
|
-
branchType:
|
|
69
|
+
branchType: "local" | "remote";
|
|
51
70
|
remoteBranch?: string; // Full remote ref when branchType === 'remote'
|
|
52
71
|
tool: AITool;
|
|
53
72
|
mode: ExecutionMode;
|
|
54
73
|
skipPermissions: boolean;
|
|
74
|
+
model?: string | null;
|
|
75
|
+
inferenceLevel?: InferenceLevel;
|
|
55
76
|
}
|
|
56
77
|
|
|
57
78
|
export interface AppProps {
|
|
@@ -69,24 +90,42 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
69
90
|
// 起動ディレクトリの取得
|
|
70
91
|
const workingDirectory = process.cwd();
|
|
71
92
|
|
|
72
|
-
const { branches, worktrees, loading, error, refresh, lastUpdated } =
|
|
73
|
-
|
|
74
|
-
|
|
93
|
+
const { branches, worktrees, loading, error, refresh, lastUpdated } =
|
|
94
|
+
useGitData({
|
|
95
|
+
enableAutoRefresh: false, // Manual refresh with 'r' key
|
|
96
|
+
});
|
|
75
97
|
const { currentScreen, navigateTo, goBack } = useScreenState();
|
|
76
98
|
|
|
77
99
|
// Version state
|
|
78
100
|
const [version, setVersion] = useState<string | null>(null);
|
|
79
101
|
|
|
80
102
|
// Selection state (for branch → tool → mode flow)
|
|
81
|
-
const [selectedBranch, setSelectedBranch] =
|
|
82
|
-
|
|
103
|
+
const [selectedBranch, setSelectedBranch] =
|
|
104
|
+
useState<SelectedBranchState | null>(null);
|
|
105
|
+
const [creationSourceBranch, setCreationSourceBranch] =
|
|
106
|
+
useState<SelectedBranchState | null>(null);
|
|
83
107
|
const [selectedTool, setSelectedTool] = useState<AITool | null>(null);
|
|
108
|
+
const [selectedModel, setSelectedModel] =
|
|
109
|
+
useState<ModelSelectionResult | null>(null);
|
|
110
|
+
const [lastModelByTool, setLastModelByTool] = useState<
|
|
111
|
+
Record<AITool, ModelSelectionResult | undefined>
|
|
112
|
+
>({});
|
|
84
113
|
|
|
85
114
|
// PR cleanup feedback
|
|
86
|
-
const [cleanupIndicators, setCleanupIndicators] = useState<
|
|
87
|
-
|
|
115
|
+
const [cleanupIndicators, setCleanupIndicators] = useState<
|
|
116
|
+
Record<
|
|
117
|
+
string,
|
|
118
|
+
{ icon: string; color?: "cyan" | "green" | "yellow" | "red" }
|
|
119
|
+
>
|
|
120
|
+
>({});
|
|
121
|
+
const [cleanupProcessingBranch, setCleanupProcessingBranch] = useState<
|
|
122
|
+
string | null
|
|
123
|
+
>(null);
|
|
88
124
|
const [cleanupInputLocked, setCleanupInputLocked] = useState(false);
|
|
89
|
-
const [cleanupFooterMessage, setCleanupFooterMessage] = useState<{
|
|
125
|
+
const [cleanupFooterMessage, setCleanupFooterMessage] = useState<{
|
|
126
|
+
text: string;
|
|
127
|
+
color?: "cyan" | "green" | "yellow" | "red";
|
|
128
|
+
} | null>(null);
|
|
90
129
|
const [hiddenBranches, setHiddenBranches] = useState<string[]>([]);
|
|
91
130
|
const spinnerFrameIndexRef = useRef(0);
|
|
92
131
|
const [spinnerFrameIndex, setSpinnerFrameIndex] = useState(0);
|
|
@@ -107,7 +146,8 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
107
146
|
}
|
|
108
147
|
|
|
109
148
|
const interval = setInterval(() => {
|
|
110
|
-
spinnerFrameIndexRef.current =
|
|
149
|
+
spinnerFrameIndexRef.current =
|
|
150
|
+
(spinnerFrameIndexRef.current + 1) % SPINNER_FRAMES.length;
|
|
111
151
|
setSpinnerFrameIndex(spinnerFrameIndexRef.current);
|
|
112
152
|
}, 120);
|
|
113
153
|
|
|
@@ -128,20 +168,23 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
128
168
|
if (cleanupProcessingBranch) {
|
|
129
169
|
setCleanupIndicators((prev) => {
|
|
130
170
|
const current = prev[cleanupProcessingBranch];
|
|
131
|
-
if (current && current.icon === frame && current.color ===
|
|
171
|
+
if (current && current.icon === frame && current.color === "cyan") {
|
|
132
172
|
return prev;
|
|
133
173
|
}
|
|
134
174
|
|
|
135
|
-
const next: Record<
|
|
175
|
+
const next: Record<
|
|
176
|
+
string,
|
|
177
|
+
{ icon: string; color?: "cyan" | "green" | "yellow" | "red" }
|
|
178
|
+
> = {
|
|
136
179
|
...prev,
|
|
137
|
-
[cleanupProcessingBranch]: { icon: frame, color:
|
|
180
|
+
[cleanupProcessingBranch]: { icon: frame, color: "cyan" },
|
|
138
181
|
};
|
|
139
182
|
|
|
140
183
|
return next;
|
|
141
184
|
});
|
|
142
185
|
}
|
|
143
186
|
|
|
144
|
-
setCleanupFooterMessage({ text: `Processing... ${frame}`, color:
|
|
187
|
+
setCleanupFooterMessage({ text: `Processing... ${frame}`, color: "cyan" });
|
|
145
188
|
}, [cleanupInputLocked, cleanupProcessingBranch, spinnerFrameIndex]);
|
|
146
189
|
|
|
147
190
|
useEffect(() => {
|
|
@@ -157,28 +200,34 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
157
200
|
}
|
|
158
201
|
}, [branches, hiddenBranches]);
|
|
159
202
|
|
|
160
|
-
useEffect(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
203
|
+
useEffect(
|
|
204
|
+
() => () => {
|
|
205
|
+
if (completionTimerRef.current) {
|
|
206
|
+
clearTimeout(completionTimerRef.current);
|
|
207
|
+
completionTimerRef.current = null;
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
[],
|
|
211
|
+
);
|
|
166
212
|
|
|
167
213
|
const visibleBranches = useMemo(
|
|
168
214
|
() => branches.filter((branch) => !hiddenBranches.includes(branch.name)),
|
|
169
|
-
[branches, hiddenBranches]
|
|
215
|
+
[branches, hiddenBranches],
|
|
170
216
|
);
|
|
171
217
|
|
|
172
218
|
// Helper function to create content-based hash for branches
|
|
173
219
|
const branchHash = useMemo(
|
|
174
|
-
() =>
|
|
175
|
-
|
|
220
|
+
() =>
|
|
221
|
+
visibleBranches
|
|
222
|
+
.map((b) => `${b.name}-${b.type}-${b.isCurrent}`)
|
|
223
|
+
.join(","),
|
|
224
|
+
[visibleBranches],
|
|
176
225
|
);
|
|
177
226
|
|
|
178
227
|
// Helper function to create content-based hash for worktrees
|
|
179
228
|
const worktreeHash = useMemo(
|
|
180
|
-
() => worktrees.map((w) => `${w.branch}-${w.path}`).join(
|
|
181
|
-
[worktrees]
|
|
229
|
+
() => worktrees.map((w) => `${w.branch}-${w.path}`).join(","),
|
|
230
|
+
[worktrees],
|
|
182
231
|
);
|
|
183
232
|
|
|
184
233
|
// Format branches to BranchItems (memoized for performance with content-based dependencies)
|
|
@@ -197,76 +246,89 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
197
246
|
}, [branchHash, worktreeHash, visibleBranches, worktrees]);
|
|
198
247
|
|
|
199
248
|
// Calculate statistics (memoized for performance)
|
|
200
|
-
const stats = useMemo(
|
|
249
|
+
const stats = useMemo(
|
|
250
|
+
() => calculateStatistics(visibleBranches),
|
|
251
|
+
[visibleBranches],
|
|
252
|
+
);
|
|
201
253
|
|
|
202
254
|
// Format worktrees to WorktreeItems
|
|
203
255
|
const worktreeItems: WorktreeItem[] = useMemo(
|
|
204
256
|
() =>
|
|
205
|
-
worktrees.map(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
257
|
+
worktrees.map(
|
|
258
|
+
(wt): WorktreeItem => ({
|
|
259
|
+
branch: wt.branch,
|
|
260
|
+
path: wt.path,
|
|
261
|
+
isAccessible: wt.isAccessible ?? true,
|
|
262
|
+
}),
|
|
263
|
+
),
|
|
264
|
+
[worktrees],
|
|
211
265
|
);
|
|
212
266
|
|
|
213
267
|
const resolveBaseBranch = useCallback(() => {
|
|
214
268
|
const localMain = branches.find(
|
|
215
269
|
(branch) =>
|
|
216
|
-
branch.type ===
|
|
270
|
+
branch.type === "local" &&
|
|
271
|
+
(branch.name === "main" || branch.name === "master"),
|
|
217
272
|
);
|
|
218
273
|
if (localMain) {
|
|
219
274
|
return localMain.name;
|
|
220
275
|
}
|
|
221
276
|
|
|
222
277
|
const develop = branches.find(
|
|
223
|
-
(branch) =>
|
|
278
|
+
(branch) =>
|
|
279
|
+
branch.type === "local" &&
|
|
280
|
+
(branch.name === "develop" || branch.name === "dev"),
|
|
224
281
|
);
|
|
225
282
|
if (develop) {
|
|
226
283
|
return develop.name;
|
|
227
284
|
}
|
|
228
285
|
|
|
229
|
-
return
|
|
286
|
+
return "main";
|
|
230
287
|
}, [branches]);
|
|
231
288
|
|
|
232
289
|
const baseBranchLabel = useMemo(
|
|
233
|
-
() =>
|
|
234
|
-
|
|
290
|
+
() =>
|
|
291
|
+
resolveBaseBranchLabel(
|
|
292
|
+
creationSourceBranch,
|
|
293
|
+
selectedBranch,
|
|
294
|
+
resolveBaseBranch,
|
|
295
|
+
),
|
|
296
|
+
[creationSourceBranch, resolveBaseBranch, selectedBranch],
|
|
235
297
|
);
|
|
236
298
|
|
|
237
299
|
// Handle branch selection
|
|
238
300
|
const toLocalBranchName = useCallback((remoteName: string) => {
|
|
239
|
-
const segments = remoteName.split(
|
|
301
|
+
const segments = remoteName.split("/");
|
|
240
302
|
if (segments.length <= 1) {
|
|
241
303
|
return remoteName;
|
|
242
304
|
}
|
|
243
|
-
return segments.slice(1).join(
|
|
305
|
+
return segments.slice(1).join("/");
|
|
244
306
|
}, []);
|
|
245
307
|
|
|
246
308
|
const inferBranchCategory = useCallback(
|
|
247
|
-
(branchName: string): BranchInfo[
|
|
309
|
+
(branchName: string): BranchInfo["branchType"] => {
|
|
248
310
|
const matched = branches.find((branch) => branch.name === branchName);
|
|
249
311
|
if (matched) {
|
|
250
312
|
return matched.branchType;
|
|
251
313
|
}
|
|
252
|
-
if (branchName ===
|
|
253
|
-
return
|
|
314
|
+
if (branchName === "main" || branchName === "master") {
|
|
315
|
+
return "main";
|
|
254
316
|
}
|
|
255
|
-
if (branchName ===
|
|
256
|
-
return
|
|
317
|
+
if (branchName === "develop" || branchName === "dev") {
|
|
318
|
+
return "develop";
|
|
257
319
|
}
|
|
258
|
-
if (branchName.startsWith(
|
|
259
|
-
return
|
|
320
|
+
if (branchName.startsWith("feature/")) {
|
|
321
|
+
return "feature";
|
|
260
322
|
}
|
|
261
|
-
if (branchName.startsWith(
|
|
262
|
-
return
|
|
323
|
+
if (branchName.startsWith("hotfix/")) {
|
|
324
|
+
return "hotfix";
|
|
263
325
|
}
|
|
264
|
-
if (branchName.startsWith(
|
|
265
|
-
return
|
|
326
|
+
if (branchName.startsWith("release/")) {
|
|
327
|
+
return "release";
|
|
266
328
|
}
|
|
267
|
-
return
|
|
329
|
+
return "other";
|
|
268
330
|
},
|
|
269
|
-
[branches]
|
|
331
|
+
[branches],
|
|
270
332
|
);
|
|
271
333
|
|
|
272
334
|
const isProtectedSelection = useCallback(
|
|
@@ -277,12 +339,14 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
277
339
|
return (
|
|
278
340
|
isProtectedBranchName(branch.name) ||
|
|
279
341
|
isProtectedBranchName(branch.displayName) ||
|
|
280
|
-
(branch.remoteBranch
|
|
281
|
-
|
|
282
|
-
|
|
342
|
+
(branch.remoteBranch
|
|
343
|
+
? isProtectedBranchName(branch.remoteBranch)
|
|
344
|
+
: false) ||
|
|
345
|
+
branch.branchCategory === "main" ||
|
|
346
|
+
branch.branchCategory === "develop"
|
|
283
347
|
);
|
|
284
348
|
},
|
|
285
|
-
[isProtectedBranchName]
|
|
349
|
+
[isProtectedBranchName],
|
|
286
350
|
);
|
|
287
351
|
|
|
288
352
|
const protectedBranchInfo = useMemo(() => {
|
|
@@ -302,18 +366,18 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
302
366
|
const handleSelect = useCallback(
|
|
303
367
|
(item: BranchItem) => {
|
|
304
368
|
const selection: SelectedBranchState =
|
|
305
|
-
item.type ===
|
|
369
|
+
item.type === "remote"
|
|
306
370
|
? {
|
|
307
371
|
name: toLocalBranchName(item.name),
|
|
308
372
|
displayName: item.name,
|
|
309
|
-
branchType:
|
|
373
|
+
branchType: "remote",
|
|
310
374
|
branchCategory: item.branchType,
|
|
311
375
|
remoteBranch: item.name,
|
|
312
376
|
}
|
|
313
377
|
: {
|
|
314
378
|
name: item.name,
|
|
315
379
|
displayName: item.name,
|
|
316
|
-
branchType:
|
|
380
|
+
branchType: "local",
|
|
317
381
|
branchCategory: item.branchType,
|
|
318
382
|
};
|
|
319
383
|
|
|
@@ -321,20 +385,28 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
321
385
|
|
|
322
386
|
setSelectedBranch(selection);
|
|
323
387
|
setSelectedTool(null);
|
|
388
|
+
setSelectedModel(null);
|
|
324
389
|
setCreationSourceBranch(null);
|
|
325
390
|
|
|
326
391
|
if (protectedSelected) {
|
|
327
392
|
setCleanupFooterMessage({
|
|
328
393
|
text: PROTECTED_BRANCH_WARNING,
|
|
329
|
-
color:
|
|
394
|
+
color: "yellow",
|
|
330
395
|
});
|
|
331
396
|
} else {
|
|
332
397
|
setCleanupFooterMessage(null);
|
|
333
398
|
}
|
|
334
399
|
|
|
335
|
-
navigateTo(
|
|
400
|
+
navigateTo("branch-action-selector");
|
|
336
401
|
},
|
|
337
|
-
[
|
|
402
|
+
[
|
|
403
|
+
isProtectedSelection,
|
|
404
|
+
navigateTo,
|
|
405
|
+
setCleanupFooterMessage,
|
|
406
|
+
setCreationSourceBranch,
|
|
407
|
+
setSelectedTool,
|
|
408
|
+
toLocalBranchName,
|
|
409
|
+
],
|
|
338
410
|
);
|
|
339
411
|
|
|
340
412
|
// Handle navigation
|
|
@@ -342,7 +414,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
342
414
|
(screen: string) => {
|
|
343
415
|
navigateTo(screen as any);
|
|
344
416
|
},
|
|
345
|
-
[navigateTo]
|
|
417
|
+
[navigateTo],
|
|
346
418
|
);
|
|
347
419
|
|
|
348
420
|
const handleWorktreeSelect = useCallback(
|
|
@@ -350,15 +422,21 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
350
422
|
setSelectedBranch({
|
|
351
423
|
name: worktree.branch,
|
|
352
424
|
displayName: worktree.branch,
|
|
353
|
-
branchType:
|
|
425
|
+
branchType: "local",
|
|
354
426
|
branchCategory: inferBranchCategory(worktree.branch),
|
|
355
427
|
});
|
|
356
428
|
setSelectedTool(null);
|
|
429
|
+
setSelectedModel(null);
|
|
357
430
|
setCreationSourceBranch(null);
|
|
358
431
|
setCleanupFooterMessage(null);
|
|
359
|
-
navigateTo(
|
|
432
|
+
navigateTo("ai-tool-selector");
|
|
360
433
|
},
|
|
361
|
-
[
|
|
434
|
+
[
|
|
435
|
+
inferBranchCategory,
|
|
436
|
+
navigateTo,
|
|
437
|
+
setCleanupFooterMessage,
|
|
438
|
+
setCreationSourceBranch,
|
|
439
|
+
],
|
|
362
440
|
);
|
|
363
441
|
|
|
364
442
|
// Handle branch action selection
|
|
@@ -370,13 +448,13 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
370
448
|
try {
|
|
371
449
|
setCleanupFooterMessage({
|
|
372
450
|
text: `Preparing root branch '${selectedBranch.displayName ?? selectedBranch.name}'...`,
|
|
373
|
-
color:
|
|
451
|
+
color: "cyan",
|
|
374
452
|
});
|
|
375
453
|
const repoRoot = await getRepositoryRoot();
|
|
376
454
|
const remoteRef =
|
|
377
455
|
selectedBranch.remoteBranch ??
|
|
378
|
-
(selectedBranch.branchType ===
|
|
379
|
-
? selectedBranch.displayName ?? selectedBranch.name
|
|
456
|
+
(selectedBranch.branchType === "remote"
|
|
457
|
+
? (selectedBranch.displayName ?? selectedBranch.name)
|
|
380
458
|
: null);
|
|
381
459
|
|
|
382
460
|
const result = await switchToProtectedBranch({
|
|
@@ -386,45 +464,44 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
386
464
|
});
|
|
387
465
|
|
|
388
466
|
let successMessage = `'${selectedBranch.displayName ?? selectedBranch.name}' will use the repository root.`;
|
|
389
|
-
if (result ===
|
|
467
|
+
if (result === "remote") {
|
|
390
468
|
successMessage = `Created a local tracking branch for '${selectedBranch.displayName ?? selectedBranch.name}' and switched to the protected branch.`;
|
|
391
|
-
} else if (result ===
|
|
469
|
+
} else if (result === "local") {
|
|
392
470
|
successMessage = `Checked out '${selectedBranch.displayName ?? selectedBranch.name}' in the repository root.`;
|
|
393
471
|
}
|
|
394
472
|
|
|
395
473
|
setCleanupFooterMessage({
|
|
396
474
|
text: successMessage,
|
|
397
|
-
color:
|
|
475
|
+
color: "green",
|
|
398
476
|
});
|
|
399
477
|
refresh();
|
|
400
|
-
navigateTo(
|
|
478
|
+
navigateTo("ai-tool-selector");
|
|
401
479
|
} catch (error) {
|
|
402
|
-
const message =
|
|
403
|
-
error instanceof Error ? error.message : String(error);
|
|
480
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
404
481
|
setCleanupFooterMessage({
|
|
405
482
|
text: `Failed to switch root branch: ${message}`,
|
|
406
|
-
color:
|
|
483
|
+
color: "red",
|
|
407
484
|
});
|
|
408
|
-
console.error(
|
|
485
|
+
console.error("Failed to switch protected branch:", error);
|
|
409
486
|
}
|
|
410
|
-
}, [
|
|
411
|
-
navigateTo,
|
|
412
|
-
refresh,
|
|
413
|
-
selectedBranch,
|
|
414
|
-
setCleanupFooterMessage,
|
|
415
|
-
]);
|
|
487
|
+
}, [navigateTo, refresh, selectedBranch, setCleanupFooterMessage]);
|
|
416
488
|
|
|
417
489
|
const handleUseExistingBranch = useCallback(() => {
|
|
418
490
|
if (selectedBranch && isProtectedSelection(selectedBranch)) {
|
|
419
491
|
void handleProtectedBranchSwitch();
|
|
420
492
|
return;
|
|
421
493
|
}
|
|
422
|
-
navigateTo(
|
|
423
|
-
}, [
|
|
494
|
+
navigateTo("ai-tool-selector");
|
|
495
|
+
}, [
|
|
496
|
+
handleProtectedBranchSwitch,
|
|
497
|
+
isProtectedSelection,
|
|
498
|
+
navigateTo,
|
|
499
|
+
selectedBranch,
|
|
500
|
+
]);
|
|
424
501
|
|
|
425
502
|
const handleCreateNewBranch = useCallback(() => {
|
|
426
503
|
setCreationSourceBranch(selectedBranch);
|
|
427
|
-
navigateTo(
|
|
504
|
+
navigateTo("branch-creator");
|
|
428
505
|
}, [navigateTo, selectedBranch]);
|
|
429
506
|
|
|
430
507
|
// Handle quit
|
|
@@ -459,16 +536,17 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
459
536
|
setSelectedBranch({
|
|
460
537
|
name: branchName,
|
|
461
538
|
displayName: branchName,
|
|
462
|
-
branchType:
|
|
539
|
+
branchType: "local",
|
|
463
540
|
branchCategory: inferBranchCategory(branchName),
|
|
464
541
|
});
|
|
465
542
|
setSelectedTool(null);
|
|
543
|
+
setSelectedModel(null);
|
|
466
544
|
setCleanupFooterMessage(null);
|
|
467
545
|
|
|
468
|
-
navigateTo(
|
|
546
|
+
navigateTo("ai-tool-selector");
|
|
469
547
|
} catch (error) {
|
|
470
548
|
// On error, go back to branch list
|
|
471
|
-
console.error(
|
|
549
|
+
console.error("Failed to create branch:", error);
|
|
472
550
|
goBack();
|
|
473
551
|
refresh();
|
|
474
552
|
}
|
|
@@ -482,7 +560,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
482
560
|
creationSourceBranch,
|
|
483
561
|
inferBranchCategory,
|
|
484
562
|
setCleanupFooterMessage,
|
|
485
|
-
]
|
|
563
|
+
],
|
|
486
564
|
);
|
|
487
565
|
|
|
488
566
|
const handleCleanupCommand = useCallback(async () => {
|
|
@@ -516,7 +594,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
516
594
|
setCleanupInputLocked(true);
|
|
517
595
|
setCleanupIndicators({});
|
|
518
596
|
const initialFrame = getSpinnerFrame(0);
|
|
519
|
-
setCleanupFooterMessage({
|
|
597
|
+
setCleanupFooterMessage({
|
|
598
|
+
text: `Processing... ${initialFrame}`,
|
|
599
|
+
color: "cyan",
|
|
600
|
+
});
|
|
520
601
|
setCleanupProcessingBranch(null);
|
|
521
602
|
spinnerFrameIndexRef.current = 0;
|
|
522
603
|
setSpinnerFrameIndex(0);
|
|
@@ -527,7 +608,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
527
608
|
} catch (error) {
|
|
528
609
|
const message = error instanceof Error ? error.message : String(error);
|
|
529
610
|
setCleanupIndicators({});
|
|
530
|
-
setCleanupFooterMessage({ text: `❌ ${message}`, color:
|
|
611
|
+
setCleanupFooterMessage({ text: `❌ ${message}`, color: "red" });
|
|
531
612
|
setCleanupInputLocked(false);
|
|
532
613
|
completionTimerRef.current = setTimeout(() => {
|
|
533
614
|
setCleanupFooterMessage(null);
|
|
@@ -538,7 +619,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
538
619
|
|
|
539
620
|
if (targets.length === 0) {
|
|
540
621
|
setCleanupIndicators({});
|
|
541
|
-
setCleanupFooterMessage({
|
|
622
|
+
setCleanupFooterMessage({
|
|
623
|
+
text: "✅ Nothing to clean up.",
|
|
624
|
+
color: "green",
|
|
625
|
+
});
|
|
542
626
|
setCleanupInputLocked(false);
|
|
543
627
|
completionTimerRef.current = setTimeout(() => {
|
|
544
628
|
setCleanupFooterMessage(null);
|
|
@@ -548,11 +632,21 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
548
632
|
}
|
|
549
633
|
|
|
550
634
|
// Reset hidden branches that may already be gone
|
|
551
|
-
setHiddenBranches((prev) =>
|
|
635
|
+
setHiddenBranches((prev) =>
|
|
636
|
+
prev.filter(
|
|
637
|
+
(name) => targets.find((t) => t.branch === name) === undefined,
|
|
638
|
+
),
|
|
639
|
+
);
|
|
552
640
|
|
|
553
|
-
const initialIndicators = targets.reduce<
|
|
554
|
-
|
|
555
|
-
|
|
641
|
+
const initialIndicators = targets.reduce<
|
|
642
|
+
Record<
|
|
643
|
+
string,
|
|
644
|
+
{ icon: string; color?: "cyan" | "green" | "yellow" | "red" }
|
|
645
|
+
>
|
|
646
|
+
>((acc, target, index) => {
|
|
647
|
+
const icon = index === 0 ? getSpinnerFrame(0) : "⏳";
|
|
648
|
+
const color: "cyan" | "green" | "yellow" | "red" =
|
|
649
|
+
index === 0 ? "cyan" : "yellow";
|
|
556
650
|
acc[target.branch] = { icon, color };
|
|
557
651
|
return acc;
|
|
558
652
|
}, {});
|
|
@@ -562,7 +656,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
562
656
|
setCleanupProcessingBranch(firstTarget ? firstTarget.branch : null);
|
|
563
657
|
spinnerFrameIndexRef.current = 0;
|
|
564
658
|
setSpinnerFrameIndex(0);
|
|
565
|
-
setCleanupFooterMessage({
|
|
659
|
+
setCleanupFooterMessage({
|
|
660
|
+
text: `Processing... ${getSpinnerFrame(0)}`,
|
|
661
|
+
color: "cyan",
|
|
662
|
+
});
|
|
566
663
|
|
|
567
664
|
for (let index = 0; index < targets.length; index += 1) {
|
|
568
665
|
const currentTarget = targets[index];
|
|
@@ -577,11 +674,11 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
577
674
|
|
|
578
675
|
setCleanupIndicators((prev) => {
|
|
579
676
|
const updated = { ...prev };
|
|
580
|
-
updated[target.branch] = { icon: getSpinnerFrame(0), color:
|
|
677
|
+
updated[target.branch] = { icon: getSpinnerFrame(0), color: "cyan" };
|
|
581
678
|
for (const pending of targets.slice(index + 1)) {
|
|
582
679
|
const current = updated[pending.branch];
|
|
583
|
-
if (!current || current.icon !==
|
|
584
|
-
updated[pending.branch] = { icon:
|
|
680
|
+
if (!current || current.icon !== "⏳") {
|
|
681
|
+
updated[pending.branch] = { icon: "⏳", color: "yellow" };
|
|
585
682
|
}
|
|
586
683
|
}
|
|
587
684
|
return updated;
|
|
@@ -590,19 +687,23 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
590
687
|
const shouldSkip =
|
|
591
688
|
target.hasUncommittedChanges ||
|
|
592
689
|
target.hasUnpushedCommits ||
|
|
593
|
-
(target.cleanupType ===
|
|
690
|
+
(target.cleanupType === "worktree-and-branch" &&
|
|
691
|
+
(!target.worktreePath || target.isAccessible === false));
|
|
594
692
|
|
|
595
693
|
if (shouldSkip) {
|
|
596
694
|
setCleanupIndicators((prev) => ({
|
|
597
695
|
...prev,
|
|
598
|
-
[target.branch]: { icon:
|
|
696
|
+
[target.branch]: { icon: "⏭️", color: "yellow" },
|
|
599
697
|
}));
|
|
600
698
|
setCleanupProcessingBranch(null);
|
|
601
699
|
continue;
|
|
602
700
|
}
|
|
603
701
|
|
|
604
702
|
try {
|
|
605
|
-
if (
|
|
703
|
+
if (
|
|
704
|
+
target.cleanupType === "worktree-and-branch" &&
|
|
705
|
+
target.worktreePath
|
|
706
|
+
) {
|
|
606
707
|
await removeWorktree(target.worktreePath, true);
|
|
607
708
|
}
|
|
608
709
|
|
|
@@ -610,13 +711,13 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
610
711
|
succeededBranches.push(target.branch);
|
|
611
712
|
setCleanupIndicators((prev) => ({
|
|
612
713
|
...prev,
|
|
613
|
-
[target.branch]: { icon:
|
|
714
|
+
[target.branch]: { icon: "✅", color: "green" },
|
|
614
715
|
}));
|
|
615
716
|
} catch {
|
|
616
|
-
const icon =
|
|
717
|
+
const icon = "❌";
|
|
617
718
|
setCleanupIndicators((prev) => ({
|
|
618
719
|
...prev,
|
|
619
|
-
[target.branch]: { icon, color:
|
|
720
|
+
[target.branch]: { icon, color: "red" },
|
|
620
721
|
}));
|
|
621
722
|
}
|
|
622
723
|
|
|
@@ -625,23 +726,45 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
625
726
|
|
|
626
727
|
setCleanupProcessingBranch(null);
|
|
627
728
|
setCleanupInputLocked(false);
|
|
628
|
-
setCleanupFooterMessage({
|
|
729
|
+
setCleanupFooterMessage({
|
|
730
|
+
text: "Cleanup completed. Finalizing...",
|
|
731
|
+
color: "green",
|
|
732
|
+
});
|
|
629
733
|
|
|
630
734
|
const holdDuration =
|
|
631
|
-
typeof process !==
|
|
735
|
+
typeof process !== "undefined" && process.env?.NODE_ENV === "test"
|
|
632
736
|
? 0
|
|
633
737
|
: COMPLETION_HOLD_DURATION_MS;
|
|
634
738
|
|
|
635
739
|
completionTimerRef.current = setTimeout(resetAfterWait, holdDuration);
|
|
636
|
-
}, [
|
|
740
|
+
}, [
|
|
741
|
+
cleanupInputLocked,
|
|
742
|
+
deleteBranch,
|
|
743
|
+
getMergedPRWorktrees,
|
|
744
|
+
refresh,
|
|
745
|
+
removeWorktree,
|
|
746
|
+
]);
|
|
637
747
|
|
|
638
748
|
// Handle AI tool selection
|
|
639
749
|
const handleToolSelect = useCallback(
|
|
640
750
|
(tool: AITool) => {
|
|
641
751
|
setSelectedTool(tool);
|
|
642
|
-
|
|
752
|
+
setSelectedModel(lastModelByTool[tool] ?? null);
|
|
753
|
+
navigateTo("model-selector");
|
|
754
|
+
},
|
|
755
|
+
[lastModelByTool, navigateTo],
|
|
756
|
+
);
|
|
757
|
+
|
|
758
|
+
const handleModelSelect = useCallback(
|
|
759
|
+
(selection: ModelSelectionResult) => {
|
|
760
|
+
setSelectedModel(selection);
|
|
761
|
+
setLastModelByTool((prev) => ({
|
|
762
|
+
...prev,
|
|
763
|
+
...(selectedTool ? { [selectedTool]: selection } : {}),
|
|
764
|
+
}));
|
|
765
|
+
navigateTo("execution-mode-selector");
|
|
643
766
|
},
|
|
644
|
-
[navigateTo]
|
|
767
|
+
[navigateTo, selectedTool],
|
|
645
768
|
);
|
|
646
769
|
|
|
647
770
|
// Handle session selection
|
|
@@ -651,7 +774,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
651
774
|
// For now, just go back to branch list
|
|
652
775
|
goBack();
|
|
653
776
|
},
|
|
654
|
-
[goBack]
|
|
777
|
+
[goBack],
|
|
655
778
|
);
|
|
656
779
|
|
|
657
780
|
// Handle execution mode and skipPermissions selection
|
|
@@ -659,6 +782,13 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
659
782
|
(result: { mode: ExecutionMode; skipPermissions: boolean }) => {
|
|
660
783
|
// All selections complete - exit with result
|
|
661
784
|
if (selectedBranch && selectedTool) {
|
|
785
|
+
const defaultModel = getDefaultModelOption(selectedTool);
|
|
786
|
+
const resolvedModel =
|
|
787
|
+
selectedModel?.model ?? defaultModel?.id ?? null;
|
|
788
|
+
const resolvedInference =
|
|
789
|
+
selectedModel?.inferenceLevel ??
|
|
790
|
+
getDefaultInferenceForModel(defaultModel ?? undefined);
|
|
791
|
+
|
|
662
792
|
const payload: SelectionResult = {
|
|
663
793
|
branch: selectedBranch.name,
|
|
664
794
|
displayName: selectedBranch.displayName,
|
|
@@ -666,6 +796,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
666
796
|
tool: selectedTool,
|
|
667
797
|
mode: result.mode,
|
|
668
798
|
skipPermissions: result.skipPermissions,
|
|
799
|
+
...(resolvedModel !== undefined ? { model: resolvedModel } : {}),
|
|
800
|
+
...(resolvedInference !== undefined
|
|
801
|
+
? { inferenceLevel: resolvedInference }
|
|
802
|
+
: {}),
|
|
669
803
|
...(selectedBranch.remoteBranch
|
|
670
804
|
? { remoteBranch: selectedBranch.remoteBranch }
|
|
671
805
|
: {}),
|
|
@@ -675,13 +809,21 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
675
809
|
exit();
|
|
676
810
|
}
|
|
677
811
|
},
|
|
678
|
-
[
|
|
812
|
+
[
|
|
813
|
+
selectedBranch,
|
|
814
|
+
selectedTool,
|
|
815
|
+
selectedModel,
|
|
816
|
+
onExit,
|
|
817
|
+
exit,
|
|
818
|
+
getDefaultModelOption,
|
|
819
|
+
getDefaultInferenceForModel,
|
|
820
|
+
],
|
|
679
821
|
);
|
|
680
822
|
|
|
681
823
|
// Render screen based on currentScreen
|
|
682
824
|
const renderScreen = () => {
|
|
683
825
|
switch (currentScreen) {
|
|
684
|
-
case
|
|
826
|
+
case "branch-list":
|
|
685
827
|
return (
|
|
686
828
|
<BranchListScreen
|
|
687
829
|
branches={branchItems}
|
|
@@ -705,7 +847,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
705
847
|
/>
|
|
706
848
|
);
|
|
707
849
|
|
|
708
|
-
case
|
|
850
|
+
case "worktree-manager":
|
|
709
851
|
return (
|
|
710
852
|
<WorktreeManagerScreen
|
|
711
853
|
worktrees={worktreeItems}
|
|
@@ -715,7 +857,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
715
857
|
/>
|
|
716
858
|
);
|
|
717
859
|
|
|
718
|
-
case
|
|
860
|
+
case "branch-creator":
|
|
719
861
|
return (
|
|
720
862
|
<BranchCreatorScreen
|
|
721
863
|
onBack={goBack}
|
|
@@ -725,10 +867,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
725
867
|
/>
|
|
726
868
|
);
|
|
727
869
|
|
|
728
|
-
case
|
|
870
|
+
case "branch-action-selector": {
|
|
729
871
|
const isProtected = Boolean(protectedBranchInfo);
|
|
730
872
|
const baseProps = {
|
|
731
|
-
selectedBranch: selectedBranch?.displayName ??
|
|
873
|
+
selectedBranch: selectedBranch?.displayName ?? "",
|
|
732
874
|
onUseExisting: handleUseExistingBranch,
|
|
733
875
|
onCreateNew: handleCreateNewBranch,
|
|
734
876
|
onBack: goBack,
|
|
@@ -750,10 +892,31 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
750
892
|
return <BranchActionSelectorScreen {...baseProps} />;
|
|
751
893
|
}
|
|
752
894
|
|
|
753
|
-
case
|
|
754
|
-
return
|
|
895
|
+
case "ai-tool-selector":
|
|
896
|
+
return (
|
|
897
|
+
<AIToolSelectorScreen
|
|
898
|
+
onBack={goBack}
|
|
899
|
+
onSelect={handleToolSelect}
|
|
900
|
+
version={version}
|
|
901
|
+
/>
|
|
902
|
+
);
|
|
755
903
|
|
|
756
|
-
case
|
|
904
|
+
case "model-selector":
|
|
905
|
+
if (!selectedTool) {
|
|
906
|
+
goBack();
|
|
907
|
+
return null;
|
|
908
|
+
}
|
|
909
|
+
return (
|
|
910
|
+
<ModelSelectorScreen
|
|
911
|
+
tool={selectedTool}
|
|
912
|
+
onBack={goBack}
|
|
913
|
+
onSelect={handleModelSelect}
|
|
914
|
+
version={version}
|
|
915
|
+
initialSelection={selectedModel}
|
|
916
|
+
/>
|
|
917
|
+
);
|
|
918
|
+
|
|
919
|
+
case "session-selector":
|
|
757
920
|
// TODO: Implement session data fetching
|
|
758
921
|
return (
|
|
759
922
|
<SessionSelectorScreen
|
|
@@ -764,9 +927,13 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
764
927
|
/>
|
|
765
928
|
);
|
|
766
929
|
|
|
767
|
-
case
|
|
930
|
+
case "execution-mode-selector":
|
|
768
931
|
return (
|
|
769
|
-
<ExecutionModeSelectorScreen
|
|
932
|
+
<ExecutionModeSelectorScreen
|
|
933
|
+
onBack={goBack}
|
|
934
|
+
onSelect={handleModeSelect}
|
|
935
|
+
version={version}
|
|
936
|
+
/>
|
|
770
937
|
);
|
|
771
938
|
|
|
772
939
|
default:
|