@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,30 +1,32 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useMemo, useRef, useState } from
|
|
2
|
-
import { useApp } from
|
|
3
|
-
import { ErrorBoundary } from
|
|
4
|
-
import { BranchListScreen } from
|
|
5
|
-
import { WorktreeManagerScreen } from
|
|
6
|
-
import { BranchCreatorScreen } from
|
|
7
|
-
import { BranchActionSelectorScreen } from
|
|
8
|
-
import { AIToolSelectorScreen } from
|
|
9
|
-
import { SessionSelectorScreen } from
|
|
10
|
-
import { ExecutionModeSelectorScreen } from
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
2
|
+
import { useApp } from "ink";
|
|
3
|
+
import { ErrorBoundary } from "./common/ErrorBoundary.js";
|
|
4
|
+
import { BranchListScreen } from "./screens/BranchListScreen.js";
|
|
5
|
+
import { WorktreeManagerScreen } from "./screens/WorktreeManagerScreen.js";
|
|
6
|
+
import { BranchCreatorScreen } from "./screens/BranchCreatorScreen.js";
|
|
7
|
+
import { BranchActionSelectorScreen } from "../screens/BranchActionSelectorScreen.js";
|
|
8
|
+
import { AIToolSelectorScreen } from "./screens/AIToolSelectorScreen.js";
|
|
9
|
+
import { SessionSelectorScreen } from "./screens/SessionSelectorScreen.js";
|
|
10
|
+
import { ExecutionModeSelectorScreen } from "./screens/ExecutionModeSelectorScreen.js";
|
|
11
|
+
import { ModelSelectorScreen, } from "./screens/ModelSelectorScreen.js";
|
|
12
|
+
import { useGitData } from "../hooks/useGitData.js";
|
|
13
|
+
import { useScreenState } from "../hooks/useScreenState.js";
|
|
14
|
+
import { formatBranchItems } from "../utils/branchFormatter.js";
|
|
15
|
+
import { calculateStatistics } from "../utils/statisticsCalculator.js";
|
|
16
|
+
import { getRepositoryRoot, deleteBranch } from "../../../git.js";
|
|
17
|
+
import { createWorktree, generateWorktreePath, getMergedPRWorktrees, isProtectedBranchName, removeWorktree, switchToProtectedBranch, } from "../../../worktree.js";
|
|
18
|
+
import { getPackageVersion } from "../../../utils.js";
|
|
19
|
+
import { resolveBaseBranchLabel, resolveBaseBranchRef, } from "../utils/baseBranch.js";
|
|
20
|
+
import { getDefaultInferenceForModel, getDefaultModelOption, } from "../utils/modelOptions.js";
|
|
21
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧"];
|
|
20
22
|
const COMPLETION_HOLD_DURATION_MS = 3000;
|
|
21
|
-
const PROTECTED_BRANCH_WARNING =
|
|
23
|
+
const PROTECTED_BRANCH_WARNING = "Root branches operate directly in the repository root. Create a new branch if you need a dedicated worktree.";
|
|
22
24
|
const getSpinnerFrame = (index) => {
|
|
23
25
|
const frame = SPINNER_FRAMES[index];
|
|
24
|
-
if (typeof frame ===
|
|
26
|
+
if (typeof frame === "string") {
|
|
25
27
|
return frame;
|
|
26
28
|
}
|
|
27
|
-
return SPINNER_FRAMES[0] ??
|
|
29
|
+
return SPINNER_FRAMES[0] ?? "⠋";
|
|
28
30
|
};
|
|
29
31
|
/**
|
|
30
32
|
* App - Top-level component for Ink.js UI
|
|
@@ -44,6 +46,8 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
44
46
|
const [selectedBranch, setSelectedBranch] = useState(null);
|
|
45
47
|
const [creationSourceBranch, setCreationSourceBranch] = useState(null);
|
|
46
48
|
const [selectedTool, setSelectedTool] = useState(null);
|
|
49
|
+
const [selectedModel, setSelectedModel] = useState(null);
|
|
50
|
+
const [lastModelByTool, setLastModelByTool] = useState({});
|
|
47
51
|
// PR cleanup feedback
|
|
48
52
|
const [cleanupIndicators, setCleanupIndicators] = useState({});
|
|
49
53
|
const [cleanupProcessingBranch, setCleanupProcessingBranch] = useState(null);
|
|
@@ -66,7 +70,8 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
66
70
|
return undefined;
|
|
67
71
|
}
|
|
68
72
|
const interval = setInterval(() => {
|
|
69
|
-
spinnerFrameIndexRef.current =
|
|
73
|
+
spinnerFrameIndexRef.current =
|
|
74
|
+
(spinnerFrameIndexRef.current + 1) % SPINNER_FRAMES.length;
|
|
70
75
|
setSpinnerFrameIndex(spinnerFrameIndexRef.current);
|
|
71
76
|
}, 120);
|
|
72
77
|
return () => {
|
|
@@ -83,17 +88,17 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
83
88
|
if (cleanupProcessingBranch) {
|
|
84
89
|
setCleanupIndicators((prev) => {
|
|
85
90
|
const current = prev[cleanupProcessingBranch];
|
|
86
|
-
if (current && current.icon === frame && current.color ===
|
|
91
|
+
if (current && current.icon === frame && current.color === "cyan") {
|
|
87
92
|
return prev;
|
|
88
93
|
}
|
|
89
94
|
const next = {
|
|
90
95
|
...prev,
|
|
91
|
-
[cleanupProcessingBranch]: { icon: frame, color:
|
|
96
|
+
[cleanupProcessingBranch]: { icon: frame, color: "cyan" },
|
|
92
97
|
};
|
|
93
98
|
return next;
|
|
94
99
|
});
|
|
95
100
|
}
|
|
96
|
-
setCleanupFooterMessage({ text: `Processing... ${frame}`, color:
|
|
101
|
+
setCleanupFooterMessage({ text: `Processing... ${frame}`, color: "cyan" });
|
|
97
102
|
}, [cleanupInputLocked, cleanupProcessingBranch, spinnerFrameIndex]);
|
|
98
103
|
useEffect(() => {
|
|
99
104
|
if (!hiddenBranches.length) {
|
|
@@ -113,9 +118,11 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
113
118
|
}, []);
|
|
114
119
|
const visibleBranches = useMemo(() => branches.filter((branch) => !hiddenBranches.includes(branch.name)), [branches, hiddenBranches]);
|
|
115
120
|
// Helper function to create content-based hash for branches
|
|
116
|
-
const branchHash = useMemo(() => visibleBranches
|
|
121
|
+
const branchHash = useMemo(() => visibleBranches
|
|
122
|
+
.map((b) => `${b.name}-${b.type}-${b.isCurrent}`)
|
|
123
|
+
.join(","), [visibleBranches]);
|
|
117
124
|
// Helper function to create content-based hash for worktrees
|
|
118
|
-
const worktreeHash = useMemo(() => worktrees.map((w) => `${w.branch}-${w.path}`).join(
|
|
125
|
+
const worktreeHash = useMemo(() => worktrees.map((w) => `${w.branch}-${w.path}`).join(","), [worktrees]);
|
|
119
126
|
// Format branches to BranchItems (memoized for performance with content-based dependencies)
|
|
120
127
|
const branchItems = useMemo(() => {
|
|
121
128
|
// Build worktreeMap for sorting
|
|
@@ -139,46 +146,48 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
139
146
|
isAccessible: wt.isAccessible ?? true,
|
|
140
147
|
})), [worktrees]);
|
|
141
148
|
const resolveBaseBranch = useCallback(() => {
|
|
142
|
-
const localMain = branches.find((branch) => branch.type ===
|
|
149
|
+
const localMain = branches.find((branch) => branch.type === "local" &&
|
|
150
|
+
(branch.name === "main" || branch.name === "master"));
|
|
143
151
|
if (localMain) {
|
|
144
152
|
return localMain.name;
|
|
145
153
|
}
|
|
146
|
-
const develop = branches.find((branch) => branch.type ===
|
|
154
|
+
const develop = branches.find((branch) => branch.type === "local" &&
|
|
155
|
+
(branch.name === "develop" || branch.name === "dev"));
|
|
147
156
|
if (develop) {
|
|
148
157
|
return develop.name;
|
|
149
158
|
}
|
|
150
|
-
return
|
|
159
|
+
return "main";
|
|
151
160
|
}, [branches]);
|
|
152
161
|
const baseBranchLabel = useMemo(() => resolveBaseBranchLabel(creationSourceBranch, selectedBranch, resolveBaseBranch), [creationSourceBranch, resolveBaseBranch, selectedBranch]);
|
|
153
162
|
// Handle branch selection
|
|
154
163
|
const toLocalBranchName = useCallback((remoteName) => {
|
|
155
|
-
const segments = remoteName.split(
|
|
164
|
+
const segments = remoteName.split("/");
|
|
156
165
|
if (segments.length <= 1) {
|
|
157
166
|
return remoteName;
|
|
158
167
|
}
|
|
159
|
-
return segments.slice(1).join(
|
|
168
|
+
return segments.slice(1).join("/");
|
|
160
169
|
}, []);
|
|
161
170
|
const inferBranchCategory = useCallback((branchName) => {
|
|
162
171
|
const matched = branches.find((branch) => branch.name === branchName);
|
|
163
172
|
if (matched) {
|
|
164
173
|
return matched.branchType;
|
|
165
174
|
}
|
|
166
|
-
if (branchName ===
|
|
167
|
-
return
|
|
175
|
+
if (branchName === "main" || branchName === "master") {
|
|
176
|
+
return "main";
|
|
168
177
|
}
|
|
169
|
-
if (branchName ===
|
|
170
|
-
return
|
|
178
|
+
if (branchName === "develop" || branchName === "dev") {
|
|
179
|
+
return "develop";
|
|
171
180
|
}
|
|
172
|
-
if (branchName.startsWith(
|
|
173
|
-
return
|
|
181
|
+
if (branchName.startsWith("feature/")) {
|
|
182
|
+
return "feature";
|
|
174
183
|
}
|
|
175
|
-
if (branchName.startsWith(
|
|
176
|
-
return
|
|
184
|
+
if (branchName.startsWith("hotfix/")) {
|
|
185
|
+
return "hotfix";
|
|
177
186
|
}
|
|
178
|
-
if (branchName.startsWith(
|
|
179
|
-
return
|
|
187
|
+
if (branchName.startsWith("release/")) {
|
|
188
|
+
return "release";
|
|
180
189
|
}
|
|
181
|
-
return
|
|
190
|
+
return "other";
|
|
182
191
|
}, [branches]);
|
|
183
192
|
const isProtectedSelection = useCallback((branch) => {
|
|
184
193
|
if (!branch) {
|
|
@@ -186,9 +195,11 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
186
195
|
}
|
|
187
196
|
return (isProtectedBranchName(branch.name) ||
|
|
188
197
|
isProtectedBranchName(branch.displayName) ||
|
|
189
|
-
(branch.remoteBranch
|
|
190
|
-
|
|
191
|
-
|
|
198
|
+
(branch.remoteBranch
|
|
199
|
+
? isProtectedBranchName(branch.remoteBranch)
|
|
200
|
+
: false) ||
|
|
201
|
+
branch.branchCategory === "main" ||
|
|
202
|
+
branch.branchCategory === "develop");
|
|
192
203
|
}, [isProtectedBranchName]);
|
|
193
204
|
const protectedBranchInfo = useMemo(() => {
|
|
194
205
|
if (!selectedBranch) {
|
|
@@ -204,35 +215,43 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
204
215
|
};
|
|
205
216
|
}, [selectedBranch, isProtectedSelection]);
|
|
206
217
|
const handleSelect = useCallback((item) => {
|
|
207
|
-
const selection = item.type ===
|
|
218
|
+
const selection = item.type === "remote"
|
|
208
219
|
? {
|
|
209
220
|
name: toLocalBranchName(item.name),
|
|
210
221
|
displayName: item.name,
|
|
211
|
-
branchType:
|
|
222
|
+
branchType: "remote",
|
|
212
223
|
branchCategory: item.branchType,
|
|
213
224
|
remoteBranch: item.name,
|
|
214
225
|
}
|
|
215
226
|
: {
|
|
216
227
|
name: item.name,
|
|
217
228
|
displayName: item.name,
|
|
218
|
-
branchType:
|
|
229
|
+
branchType: "local",
|
|
219
230
|
branchCategory: item.branchType,
|
|
220
231
|
};
|
|
221
232
|
const protectedSelected = isProtectedSelection(selection);
|
|
222
233
|
setSelectedBranch(selection);
|
|
223
234
|
setSelectedTool(null);
|
|
235
|
+
setSelectedModel(null);
|
|
224
236
|
setCreationSourceBranch(null);
|
|
225
237
|
if (protectedSelected) {
|
|
226
238
|
setCleanupFooterMessage({
|
|
227
239
|
text: PROTECTED_BRANCH_WARNING,
|
|
228
|
-
color:
|
|
240
|
+
color: "yellow",
|
|
229
241
|
});
|
|
230
242
|
}
|
|
231
243
|
else {
|
|
232
244
|
setCleanupFooterMessage(null);
|
|
233
245
|
}
|
|
234
|
-
navigateTo(
|
|
235
|
-
}, [
|
|
246
|
+
navigateTo("branch-action-selector");
|
|
247
|
+
}, [
|
|
248
|
+
isProtectedSelection,
|
|
249
|
+
navigateTo,
|
|
250
|
+
setCleanupFooterMessage,
|
|
251
|
+
setCreationSourceBranch,
|
|
252
|
+
setSelectedTool,
|
|
253
|
+
toLocalBranchName,
|
|
254
|
+
]);
|
|
236
255
|
// Handle navigation
|
|
237
256
|
const handleNavigate = useCallback((screen) => {
|
|
238
257
|
navigateTo(screen);
|
|
@@ -241,14 +260,20 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
241
260
|
setSelectedBranch({
|
|
242
261
|
name: worktree.branch,
|
|
243
262
|
displayName: worktree.branch,
|
|
244
|
-
branchType:
|
|
263
|
+
branchType: "local",
|
|
245
264
|
branchCategory: inferBranchCategory(worktree.branch),
|
|
246
265
|
});
|
|
247
266
|
setSelectedTool(null);
|
|
267
|
+
setSelectedModel(null);
|
|
248
268
|
setCreationSourceBranch(null);
|
|
249
269
|
setCleanupFooterMessage(null);
|
|
250
|
-
navigateTo(
|
|
251
|
-
}, [
|
|
270
|
+
navigateTo("ai-tool-selector");
|
|
271
|
+
}, [
|
|
272
|
+
inferBranchCategory,
|
|
273
|
+
navigateTo,
|
|
274
|
+
setCleanupFooterMessage,
|
|
275
|
+
setCreationSourceBranch,
|
|
276
|
+
]);
|
|
252
277
|
// Handle branch action selection
|
|
253
278
|
const handleProtectedBranchSwitch = useCallback(async () => {
|
|
254
279
|
if (!selectedBranch) {
|
|
@@ -257,12 +282,12 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
257
282
|
try {
|
|
258
283
|
setCleanupFooterMessage({
|
|
259
284
|
text: `Preparing root branch '${selectedBranch.displayName ?? selectedBranch.name}'...`,
|
|
260
|
-
color:
|
|
285
|
+
color: "cyan",
|
|
261
286
|
});
|
|
262
287
|
const repoRoot = await getRepositoryRoot();
|
|
263
288
|
const remoteRef = selectedBranch.remoteBranch ??
|
|
264
|
-
(selectedBranch.branchType ===
|
|
265
|
-
? selectedBranch.displayName ?? selectedBranch.name
|
|
289
|
+
(selectedBranch.branchType === "remote"
|
|
290
|
+
? (selectedBranch.displayName ?? selectedBranch.name)
|
|
266
291
|
: null);
|
|
267
292
|
const result = await switchToProtectedBranch({
|
|
268
293
|
branchName: selectedBranch.name,
|
|
@@ -270,43 +295,43 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
270
295
|
remoteRef: remoteRef ?? null,
|
|
271
296
|
});
|
|
272
297
|
let successMessage = `'${selectedBranch.displayName ?? selectedBranch.name}' will use the repository root.`;
|
|
273
|
-
if (result ===
|
|
298
|
+
if (result === "remote") {
|
|
274
299
|
successMessage = `Created a local tracking branch for '${selectedBranch.displayName ?? selectedBranch.name}' and switched to the protected branch.`;
|
|
275
300
|
}
|
|
276
|
-
else if (result ===
|
|
301
|
+
else if (result === "local") {
|
|
277
302
|
successMessage = `Checked out '${selectedBranch.displayName ?? selectedBranch.name}' in the repository root.`;
|
|
278
303
|
}
|
|
279
304
|
setCleanupFooterMessage({
|
|
280
305
|
text: successMessage,
|
|
281
|
-
color:
|
|
306
|
+
color: "green",
|
|
282
307
|
});
|
|
283
308
|
refresh();
|
|
284
|
-
navigateTo(
|
|
309
|
+
navigateTo("ai-tool-selector");
|
|
285
310
|
}
|
|
286
311
|
catch (error) {
|
|
287
312
|
const message = error instanceof Error ? error.message : String(error);
|
|
288
313
|
setCleanupFooterMessage({
|
|
289
314
|
text: `Failed to switch root branch: ${message}`,
|
|
290
|
-
color:
|
|
315
|
+
color: "red",
|
|
291
316
|
});
|
|
292
|
-
console.error(
|
|
317
|
+
console.error("Failed to switch protected branch:", error);
|
|
293
318
|
}
|
|
294
|
-
}, [
|
|
295
|
-
navigateTo,
|
|
296
|
-
refresh,
|
|
297
|
-
selectedBranch,
|
|
298
|
-
setCleanupFooterMessage,
|
|
299
|
-
]);
|
|
319
|
+
}, [navigateTo, refresh, selectedBranch, setCleanupFooterMessage]);
|
|
300
320
|
const handleUseExistingBranch = useCallback(() => {
|
|
301
321
|
if (selectedBranch && isProtectedSelection(selectedBranch)) {
|
|
302
322
|
void handleProtectedBranchSwitch();
|
|
303
323
|
return;
|
|
304
324
|
}
|
|
305
|
-
navigateTo(
|
|
306
|
-
}, [
|
|
325
|
+
navigateTo("ai-tool-selector");
|
|
326
|
+
}, [
|
|
327
|
+
handleProtectedBranchSwitch,
|
|
328
|
+
isProtectedSelection,
|
|
329
|
+
navigateTo,
|
|
330
|
+
selectedBranch,
|
|
331
|
+
]);
|
|
307
332
|
const handleCreateNewBranch = useCallback(() => {
|
|
308
333
|
setCreationSourceBranch(selectedBranch);
|
|
309
|
-
navigateTo(
|
|
334
|
+
navigateTo("branch-creator");
|
|
310
335
|
}, [navigateTo, selectedBranch]);
|
|
311
336
|
// Handle quit
|
|
312
337
|
const handleQuit = useCallback(() => {
|
|
@@ -332,16 +357,17 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
332
357
|
setSelectedBranch({
|
|
333
358
|
name: branchName,
|
|
334
359
|
displayName: branchName,
|
|
335
|
-
branchType:
|
|
360
|
+
branchType: "local",
|
|
336
361
|
branchCategory: inferBranchCategory(branchName),
|
|
337
362
|
});
|
|
338
363
|
setSelectedTool(null);
|
|
364
|
+
setSelectedModel(null);
|
|
339
365
|
setCleanupFooterMessage(null);
|
|
340
|
-
navigateTo(
|
|
366
|
+
navigateTo("ai-tool-selector");
|
|
341
367
|
}
|
|
342
368
|
catch (error) {
|
|
343
369
|
// On error, go back to branch list
|
|
344
|
-
console.error(
|
|
370
|
+
console.error("Failed to create branch:", error);
|
|
345
371
|
goBack();
|
|
346
372
|
refresh();
|
|
347
373
|
}
|
|
@@ -382,7 +408,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
382
408
|
setCleanupInputLocked(true);
|
|
383
409
|
setCleanupIndicators({});
|
|
384
410
|
const initialFrame = getSpinnerFrame(0);
|
|
385
|
-
setCleanupFooterMessage({
|
|
411
|
+
setCleanupFooterMessage({
|
|
412
|
+
text: `Processing... ${initialFrame}`,
|
|
413
|
+
color: "cyan",
|
|
414
|
+
});
|
|
386
415
|
setCleanupProcessingBranch(null);
|
|
387
416
|
spinnerFrameIndexRef.current = 0;
|
|
388
417
|
setSpinnerFrameIndex(0);
|
|
@@ -393,7 +422,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
393
422
|
catch (error) {
|
|
394
423
|
const message = error instanceof Error ? error.message : String(error);
|
|
395
424
|
setCleanupIndicators({});
|
|
396
|
-
setCleanupFooterMessage({ text: `❌ ${message}`, color:
|
|
425
|
+
setCleanupFooterMessage({ text: `❌ ${message}`, color: "red" });
|
|
397
426
|
setCleanupInputLocked(false);
|
|
398
427
|
completionTimerRef.current = setTimeout(() => {
|
|
399
428
|
setCleanupFooterMessage(null);
|
|
@@ -403,7 +432,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
403
432
|
}
|
|
404
433
|
if (targets.length === 0) {
|
|
405
434
|
setCleanupIndicators({});
|
|
406
|
-
setCleanupFooterMessage({
|
|
435
|
+
setCleanupFooterMessage({
|
|
436
|
+
text: "✅ Nothing to clean up.",
|
|
437
|
+
color: "green",
|
|
438
|
+
});
|
|
407
439
|
setCleanupInputLocked(false);
|
|
408
440
|
completionTimerRef.current = setTimeout(() => {
|
|
409
441
|
setCleanupFooterMessage(null);
|
|
@@ -414,8 +446,8 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
414
446
|
// Reset hidden branches that may already be gone
|
|
415
447
|
setHiddenBranches((prev) => prev.filter((name) => targets.find((t) => t.branch === name) === undefined));
|
|
416
448
|
const initialIndicators = targets.reduce((acc, target, index) => {
|
|
417
|
-
const icon = index === 0 ? getSpinnerFrame(0) :
|
|
418
|
-
const color = index === 0 ?
|
|
449
|
+
const icon = index === 0 ? getSpinnerFrame(0) : "⏳";
|
|
450
|
+
const color = index === 0 ? "cyan" : "yellow";
|
|
419
451
|
acc[target.branch] = { icon, color };
|
|
420
452
|
return acc;
|
|
421
453
|
}, {});
|
|
@@ -424,7 +456,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
424
456
|
setCleanupProcessingBranch(firstTarget ? firstTarget.branch : null);
|
|
425
457
|
spinnerFrameIndexRef.current = 0;
|
|
426
458
|
setSpinnerFrameIndex(0);
|
|
427
|
-
setCleanupFooterMessage({
|
|
459
|
+
setCleanupFooterMessage({
|
|
460
|
+
text: `Processing... ${getSpinnerFrame(0)}`,
|
|
461
|
+
color: "cyan",
|
|
462
|
+
});
|
|
428
463
|
for (let index = 0; index < targets.length; index += 1) {
|
|
429
464
|
const currentTarget = targets[index];
|
|
430
465
|
if (!currentTarget) {
|
|
@@ -436,59 +471,79 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
436
471
|
setSpinnerFrameIndex(0);
|
|
437
472
|
setCleanupIndicators((prev) => {
|
|
438
473
|
const updated = { ...prev };
|
|
439
|
-
updated[target.branch] = { icon: getSpinnerFrame(0), color:
|
|
474
|
+
updated[target.branch] = { icon: getSpinnerFrame(0), color: "cyan" };
|
|
440
475
|
for (const pending of targets.slice(index + 1)) {
|
|
441
476
|
const current = updated[pending.branch];
|
|
442
|
-
if (!current || current.icon !==
|
|
443
|
-
updated[pending.branch] = { icon:
|
|
477
|
+
if (!current || current.icon !== "⏳") {
|
|
478
|
+
updated[pending.branch] = { icon: "⏳", color: "yellow" };
|
|
444
479
|
}
|
|
445
480
|
}
|
|
446
481
|
return updated;
|
|
447
482
|
});
|
|
448
483
|
const shouldSkip = target.hasUncommittedChanges ||
|
|
449
484
|
target.hasUnpushedCommits ||
|
|
450
|
-
(target.cleanupType ===
|
|
485
|
+
(target.cleanupType === "worktree-and-branch" &&
|
|
486
|
+
(!target.worktreePath || target.isAccessible === false));
|
|
451
487
|
if (shouldSkip) {
|
|
452
488
|
setCleanupIndicators((prev) => ({
|
|
453
489
|
...prev,
|
|
454
|
-
[target.branch]: { icon:
|
|
490
|
+
[target.branch]: { icon: "⏭️", color: "yellow" },
|
|
455
491
|
}));
|
|
456
492
|
setCleanupProcessingBranch(null);
|
|
457
493
|
continue;
|
|
458
494
|
}
|
|
459
495
|
try {
|
|
460
|
-
if (target.cleanupType ===
|
|
496
|
+
if (target.cleanupType === "worktree-and-branch" &&
|
|
497
|
+
target.worktreePath) {
|
|
461
498
|
await removeWorktree(target.worktreePath, true);
|
|
462
499
|
}
|
|
463
500
|
await deleteBranch(target.branch, true);
|
|
464
501
|
succeededBranches.push(target.branch);
|
|
465
502
|
setCleanupIndicators((prev) => ({
|
|
466
503
|
...prev,
|
|
467
|
-
[target.branch]: { icon:
|
|
504
|
+
[target.branch]: { icon: "✅", color: "green" },
|
|
468
505
|
}));
|
|
469
506
|
}
|
|
470
507
|
catch {
|
|
471
|
-
const icon =
|
|
508
|
+
const icon = "❌";
|
|
472
509
|
setCleanupIndicators((prev) => ({
|
|
473
510
|
...prev,
|
|
474
|
-
[target.branch]: { icon, color:
|
|
511
|
+
[target.branch]: { icon, color: "red" },
|
|
475
512
|
}));
|
|
476
513
|
}
|
|
477
514
|
setCleanupProcessingBranch(null);
|
|
478
515
|
}
|
|
479
516
|
setCleanupProcessingBranch(null);
|
|
480
517
|
setCleanupInputLocked(false);
|
|
481
|
-
setCleanupFooterMessage({
|
|
482
|
-
|
|
518
|
+
setCleanupFooterMessage({
|
|
519
|
+
text: "Cleanup completed. Finalizing...",
|
|
520
|
+
color: "green",
|
|
521
|
+
});
|
|
522
|
+
const holdDuration = typeof process !== "undefined" && process.env?.NODE_ENV === "test"
|
|
483
523
|
? 0
|
|
484
524
|
: COMPLETION_HOLD_DURATION_MS;
|
|
485
525
|
completionTimerRef.current = setTimeout(resetAfterWait, holdDuration);
|
|
486
|
-
}, [
|
|
526
|
+
}, [
|
|
527
|
+
cleanupInputLocked,
|
|
528
|
+
deleteBranch,
|
|
529
|
+
getMergedPRWorktrees,
|
|
530
|
+
refresh,
|
|
531
|
+
removeWorktree,
|
|
532
|
+
]);
|
|
487
533
|
// Handle AI tool selection
|
|
488
534
|
const handleToolSelect = useCallback((tool) => {
|
|
489
535
|
setSelectedTool(tool);
|
|
490
|
-
|
|
491
|
-
|
|
536
|
+
setSelectedModel(lastModelByTool[tool] ?? null);
|
|
537
|
+
navigateTo("model-selector");
|
|
538
|
+
}, [lastModelByTool, navigateTo]);
|
|
539
|
+
const handleModelSelect = useCallback((selection) => {
|
|
540
|
+
setSelectedModel(selection);
|
|
541
|
+
setLastModelByTool((prev) => ({
|
|
542
|
+
...prev,
|
|
543
|
+
...(selectedTool ? { [selectedTool]: selection } : {}),
|
|
544
|
+
}));
|
|
545
|
+
navigateTo("execution-mode-selector");
|
|
546
|
+
}, [navigateTo, selectedTool]);
|
|
492
547
|
// Handle session selection
|
|
493
548
|
const handleSessionSelect = useCallback((_session) => {
|
|
494
549
|
// TODO: Load selected session and navigate to next screen
|
|
@@ -499,6 +554,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
499
554
|
const handleModeSelect = useCallback((result) => {
|
|
500
555
|
// All selections complete - exit with result
|
|
501
556
|
if (selectedBranch && selectedTool) {
|
|
557
|
+
const defaultModel = getDefaultModelOption(selectedTool);
|
|
558
|
+
const resolvedModel = selectedModel?.model ?? defaultModel?.id ?? null;
|
|
559
|
+
const resolvedInference = selectedModel?.inferenceLevel ??
|
|
560
|
+
getDefaultInferenceForModel(defaultModel ?? undefined);
|
|
502
561
|
const payload = {
|
|
503
562
|
branch: selectedBranch.name,
|
|
504
563
|
displayName: selectedBranch.displayName,
|
|
@@ -506,6 +565,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
506
565
|
tool: selectedTool,
|
|
507
566
|
mode: result.mode,
|
|
508
567
|
skipPermissions: result.skipPermissions,
|
|
568
|
+
...(resolvedModel !== undefined ? { model: resolvedModel } : {}),
|
|
569
|
+
...(resolvedInference !== undefined
|
|
570
|
+
? { inferenceLevel: resolvedInference }
|
|
571
|
+
: {}),
|
|
509
572
|
...(selectedBranch.remoteBranch
|
|
510
573
|
? { remoteBranch: selectedBranch.remoteBranch }
|
|
511
574
|
: {}),
|
|
@@ -513,24 +576,32 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
513
576
|
onExit(payload);
|
|
514
577
|
exit();
|
|
515
578
|
}
|
|
516
|
-
}, [
|
|
579
|
+
}, [
|
|
580
|
+
selectedBranch,
|
|
581
|
+
selectedTool,
|
|
582
|
+
selectedModel,
|
|
583
|
+
onExit,
|
|
584
|
+
exit,
|
|
585
|
+
getDefaultModelOption,
|
|
586
|
+
getDefaultInferenceForModel,
|
|
587
|
+
]);
|
|
517
588
|
// Render screen based on currentScreen
|
|
518
589
|
const renderScreen = () => {
|
|
519
590
|
switch (currentScreen) {
|
|
520
|
-
case
|
|
591
|
+
case "branch-list":
|
|
521
592
|
return (React.createElement(BranchListScreen, { branches: branchItems, stats: stats, onSelect: handleSelect, onNavigate: handleNavigate, onQuit: handleQuit, onCleanupCommand: handleCleanupCommand, onRefresh: refresh, loading: loading, error: error, lastUpdated: lastUpdated, loadingIndicatorDelay: loadingIndicatorDelay, cleanupUI: {
|
|
522
593
|
indicators: cleanupIndicators,
|
|
523
594
|
footerMessage: cleanupFooterMessage,
|
|
524
595
|
inputLocked: cleanupInputLocked,
|
|
525
596
|
}, version: version, workingDirectory: workingDirectory }));
|
|
526
|
-
case
|
|
597
|
+
case "worktree-manager":
|
|
527
598
|
return (React.createElement(WorktreeManagerScreen, { worktrees: worktreeItems, onBack: goBack, onSelect: handleWorktreeSelect, version: version }));
|
|
528
|
-
case
|
|
599
|
+
case "branch-creator":
|
|
529
600
|
return (React.createElement(BranchCreatorScreen, { onBack: goBack, onCreate: handleCreate, baseBranch: baseBranchLabel, version: version }));
|
|
530
|
-
case
|
|
601
|
+
case "branch-action-selector": {
|
|
531
602
|
const isProtected = Boolean(protectedBranchInfo);
|
|
532
603
|
const baseProps = {
|
|
533
|
-
selectedBranch: selectedBranch?.displayName ??
|
|
604
|
+
selectedBranch: selectedBranch?.displayName ?? "",
|
|
534
605
|
onUseExisting: handleUseExistingBranch,
|
|
535
606
|
onCreateNew: handleCreateNewBranch,
|
|
536
607
|
onBack: goBack,
|
|
@@ -541,12 +612,18 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
|
|
|
541
612
|
}
|
|
542
613
|
return React.createElement(BranchActionSelectorScreen, { ...baseProps });
|
|
543
614
|
}
|
|
544
|
-
case
|
|
545
|
-
return React.createElement(AIToolSelectorScreen, { onBack: goBack, onSelect: handleToolSelect, version: version });
|
|
546
|
-
case
|
|
615
|
+
case "ai-tool-selector":
|
|
616
|
+
return (React.createElement(AIToolSelectorScreen, { onBack: goBack, onSelect: handleToolSelect, version: version }));
|
|
617
|
+
case "model-selector":
|
|
618
|
+
if (!selectedTool) {
|
|
619
|
+
goBack();
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
return (React.createElement(ModelSelectorScreen, { tool: selectedTool, onBack: goBack, onSelect: handleModelSelect, version: version, initialSelection: selectedModel }));
|
|
623
|
+
case "session-selector":
|
|
547
624
|
// TODO: Implement session data fetching
|
|
548
625
|
return (React.createElement(SessionSelectorScreen, { sessions: [], onBack: goBack, onSelect: handleSessionSelect, version: version }));
|
|
549
|
-
case
|
|
626
|
+
case "execution-mode-selector":
|
|
550
627
|
return (React.createElement(ExecutionModeSelectorScreen, { onBack: goBack, onSelect: handleModeSelect, version: version }));
|
|
551
628
|
default:
|
|
552
629
|
return (React.createElement(BranchListScreen, { branches: branchItems, stats: stats, onSelect: handleSelect, onNavigate: handleNavigate, onQuit: handleQuit, onRefresh: refresh, loading: loading, error: error, lastUpdated: lastUpdated, loadingIndicatorDelay: loadingIndicatorDelay, version: version, workingDirectory: workingDirectory }));
|