@akiojin/gwt 2.1.1 → 2.3.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 +4 -4
- package/README.md +4 -4
- package/dist/cli/ui/components/App.d.ts +4 -4
- package/dist/cli/ui/components/App.d.ts.map +1 -1
- package/dist/cli/ui/components/App.js +144 -105
- 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 +7 -2
- package/dist/cli/ui/components/common/Input.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Input.js +12 -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 +2 -2
- 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 +8 -4
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js +122 -48
- 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/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.map +1 -1
- package/dist/client/assets/{index-V6hDu9KS.js → index-Difv1Hwu.js} +2 -2
- package/dist/client/index.html +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 +12 -0
- package/dist/gemini.d.ts.map +1 -0
- package/dist/gemini.js +154 -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 +30 -0
- package/dist/index.js.map +1 -1
- package/dist/qwen.d.ts +12 -0
- package/dist/qwen.d.ts.map +1 -0
- package/dist/qwen.js +154 -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/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/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 +40 -34
- 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 +496 -75
- 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 +247 -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 +26 -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 +19 -13
- package/src/cli/ui/components/screens/BranchCreatorScreen.tsx +74 -54
- package/src/cli/ui/components/screens/BranchListScreen.tsx +187 -62
- package/src/cli/ui/components/screens/ExecutionModeSelectorScreen.tsx +35 -28
- 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 +8 -1
- 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 +202 -0
- package/src/git.ts +2 -1
- package/src/index.ts +30 -0
- package/src/qwen.ts +208 -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,18 +1,19 @@
|
|
|
1
|
-
import React, { useCallback } from
|
|
2
|
-
import { Box, Text, useInput } from
|
|
3
|
-
import { Header } from
|
|
4
|
-
import { Stats } from
|
|
5
|
-
import { Footer } from
|
|
6
|
-
import { Select } from
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
1
|
+
import React, { useCallback, useState, useMemo } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import { Header } from "../parts/Header.js";
|
|
4
|
+
import { Stats } from "../parts/Stats.js";
|
|
5
|
+
import { Footer } from "../parts/Footer.js";
|
|
6
|
+
import { Select } from "../common/Select.js";
|
|
7
|
+
import { Input } from "../common/Input.js";
|
|
8
|
+
import { LoadingIndicator } from "../common/LoadingIndicator.js";
|
|
9
|
+
import { useTerminalSize } from "../../hooks/useTerminalSize.js";
|
|
10
|
+
import type { BranchItem, Statistics } from "../../types.js";
|
|
11
|
+
import stringWidth from "string-width";
|
|
12
|
+
import chalk from "chalk";
|
|
12
13
|
|
|
13
14
|
const WIDTH_OVERRIDES: Record<string, number> = {
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
"⬆": 1,
|
|
16
|
+
"☁": 1,
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
const measureDisplayWidth = (value: string): number => {
|
|
@@ -28,7 +29,7 @@ const measureDisplayWidth = (value: string): number => {
|
|
|
28
29
|
return width;
|
|
29
30
|
};
|
|
30
31
|
|
|
31
|
-
type IndicatorColor =
|
|
32
|
+
type IndicatorColor = "cyan" | "green" | "yellow" | "red";
|
|
32
33
|
|
|
33
34
|
interface CleanupIndicator {
|
|
34
35
|
icon: string;
|
|
@@ -61,6 +62,11 @@ export interface BranchListScreenProps {
|
|
|
61
62
|
cleanupUI?: CleanupUIState;
|
|
62
63
|
version?: string | null;
|
|
63
64
|
workingDirectory?: string;
|
|
65
|
+
// Test support: allow external control of filter mode and query
|
|
66
|
+
testFilterMode?: boolean;
|
|
67
|
+
testOnFilterModeChange?: (mode: boolean) => void;
|
|
68
|
+
testFilterQuery?: string;
|
|
69
|
+
testOnFilterQueryChange?: (query: string) => void;
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
/**
|
|
@@ -81,79 +87,156 @@ export function BranchListScreen({
|
|
|
81
87
|
cleanupUI,
|
|
82
88
|
version,
|
|
83
89
|
workingDirectory,
|
|
90
|
+
testFilterMode,
|
|
91
|
+
testOnFilterModeChange,
|
|
92
|
+
testFilterQuery,
|
|
93
|
+
testOnFilterQueryChange,
|
|
84
94
|
}: BranchListScreenProps) {
|
|
85
95
|
const { rows } = useTerminalSize();
|
|
86
96
|
|
|
97
|
+
// Filter state - allow test control via props
|
|
98
|
+
const [internalFilterQuery, setInternalFilterQuery] = useState("");
|
|
99
|
+
const filterQuery =
|
|
100
|
+
testFilterQuery !== undefined ? testFilterQuery : internalFilterQuery;
|
|
101
|
+
const setFilterQuery = useCallback(
|
|
102
|
+
(query: string) => {
|
|
103
|
+
setInternalFilterQuery(query);
|
|
104
|
+
testOnFilterQueryChange?.(query);
|
|
105
|
+
},
|
|
106
|
+
[testOnFilterQueryChange],
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Focus management: true = filter mode, false = branch selection mode
|
|
110
|
+
// Allow test control via props
|
|
111
|
+
const [internalFilterMode, setInternalFilterMode] = useState(false);
|
|
112
|
+
const filterMode =
|
|
113
|
+
testFilterMode !== undefined ? testFilterMode : internalFilterMode;
|
|
114
|
+
const setFilterMode = useCallback(
|
|
115
|
+
(mode: boolean) => {
|
|
116
|
+
setInternalFilterMode(mode);
|
|
117
|
+
testOnFilterModeChange?.(mode);
|
|
118
|
+
},
|
|
119
|
+
[testOnFilterModeChange],
|
|
120
|
+
);
|
|
121
|
+
|
|
87
122
|
// Handle keyboard input
|
|
88
|
-
// Note:
|
|
89
|
-
|
|
123
|
+
// Note: Input component blocks specific keys (c/r/m/f) using blockKeys prop
|
|
124
|
+
// This prevents shortcuts from triggering while typing in the filter
|
|
125
|
+
useInput((input, key) => {
|
|
90
126
|
if (cleanupUI?.inputLocked) {
|
|
91
127
|
return;
|
|
92
128
|
}
|
|
93
129
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
130
|
+
// Escape key handling
|
|
131
|
+
if (key.escape) {
|
|
132
|
+
if (filterQuery) {
|
|
133
|
+
// Clear filter query first
|
|
134
|
+
setFilterQuery("");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (filterMode) {
|
|
138
|
+
// Exit filter mode if query is empty
|
|
139
|
+
setFilterMode(false);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Enter filter mode with 'f' key (only in branch selection mode)
|
|
145
|
+
if (input === "f" && !filterMode) {
|
|
146
|
+
setFilterMode(true);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Global shortcuts (blocked by Input component when typing in filter mode)
|
|
151
|
+
if (input === "m" && onNavigate) {
|
|
152
|
+
onNavigate("worktree-manager");
|
|
153
|
+
} else if (input === "c") {
|
|
97
154
|
onCleanupCommand?.();
|
|
98
|
-
} else if (input ===
|
|
155
|
+
} else if (input === "r" && onRefresh) {
|
|
99
156
|
onRefresh();
|
|
100
157
|
}
|
|
101
158
|
});
|
|
102
159
|
|
|
160
|
+
// Filter branches based on query
|
|
161
|
+
const filteredBranches = useMemo(() => {
|
|
162
|
+
if (!filterQuery.trim()) {
|
|
163
|
+
return branches;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const query = filterQuery.toLowerCase();
|
|
167
|
+
return branches.filter((branch) => {
|
|
168
|
+
// Search in branch name
|
|
169
|
+
if (branch.name.toLowerCase().includes(query)) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Search in PR title if available (only openPR has title)
|
|
174
|
+
if (branch.openPR?.title?.toLowerCase().includes(query)) {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return false;
|
|
179
|
+
});
|
|
180
|
+
}, [branches, filterQuery]);
|
|
181
|
+
|
|
103
182
|
// Calculate available space for branch list
|
|
104
183
|
// Header: 2 lines (title + divider)
|
|
184
|
+
// Filter input: 1 line
|
|
105
185
|
// Stats: 1 line
|
|
106
186
|
// Empty line: 1 line
|
|
107
187
|
// Footer: 1 line
|
|
108
|
-
// Total fixed:
|
|
188
|
+
// Total fixed: 6 lines
|
|
109
189
|
const headerLines = 2;
|
|
190
|
+
const filterLines = 1;
|
|
110
191
|
const statsLines = 1;
|
|
111
192
|
const emptyLine = 1;
|
|
112
193
|
const footerLines = 1;
|
|
113
|
-
const fixedLines =
|
|
194
|
+
const fixedLines =
|
|
195
|
+
headerLines + filterLines + statsLines + emptyLine + footerLines;
|
|
114
196
|
const contentHeight = rows - fixedLines;
|
|
115
197
|
const limit = Math.max(5, contentHeight); // Minimum 5 items visible
|
|
116
198
|
|
|
117
199
|
// Footer actions
|
|
118
200
|
const footerActions = [
|
|
119
|
-
{ key:
|
|
120
|
-
{ key:
|
|
121
|
-
{ key:
|
|
122
|
-
{ key:
|
|
201
|
+
{ key: "enter", description: "Select" },
|
|
202
|
+
{ key: "f", description: "Filter" },
|
|
203
|
+
{ key: "r", description: "Refresh" },
|
|
204
|
+
{ key: "m", description: "Manage worktrees" },
|
|
205
|
+
{ key: "c", description: "Cleanup branches" },
|
|
123
206
|
];
|
|
124
207
|
|
|
125
208
|
const formatLatestCommit = useCallback((timestamp?: number) => {
|
|
126
209
|
if (!timestamp || Number.isNaN(timestamp)) {
|
|
127
|
-
return
|
|
210
|
+
return "---";
|
|
128
211
|
}
|
|
129
212
|
|
|
130
213
|
const date = new Date(timestamp * 1000);
|
|
131
214
|
const year = date.getFullYear();
|
|
132
|
-
const month = String(date.getMonth() + 1).padStart(2,
|
|
133
|
-
const day = String(date.getDate()).padStart(2,
|
|
134
|
-
const hours = String(date.getHours()).padStart(2,
|
|
135
|
-
const minutes = String(date.getMinutes()).padStart(2,
|
|
215
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
216
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
217
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
218
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
136
219
|
|
|
137
220
|
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
|
138
221
|
}, []);
|
|
139
222
|
|
|
140
223
|
const truncateToWidth = useCallback((value: string, maxWidth: number) => {
|
|
141
224
|
if (maxWidth <= 0) {
|
|
142
|
-
return
|
|
225
|
+
return "";
|
|
143
226
|
}
|
|
144
227
|
|
|
145
228
|
if (stringWidth(value) <= maxWidth) {
|
|
146
229
|
return value;
|
|
147
230
|
}
|
|
148
231
|
|
|
149
|
-
const ellipsis =
|
|
232
|
+
const ellipsis = "…";
|
|
150
233
|
const ellipsisWidth = stringWidth(ellipsis);
|
|
151
234
|
if (ellipsisWidth >= maxWidth) {
|
|
152
235
|
return ellipsis;
|
|
153
236
|
}
|
|
154
237
|
|
|
155
238
|
let currentWidth = 0;
|
|
156
|
-
let result =
|
|
239
|
+
let result = "";
|
|
157
240
|
|
|
158
241
|
for (const char of value) {
|
|
159
242
|
const charWidth = stringWidth(char);
|
|
@@ -170,60 +253,67 @@ export function BranchListScreen({
|
|
|
170
253
|
const renderBranchRow = useCallback(
|
|
171
254
|
(item: BranchItem, isSelected: boolean, context: { columns: number }) => {
|
|
172
255
|
const columns = Math.max(20, context.columns);
|
|
173
|
-
const arrow = isSelected ?
|
|
256
|
+
const arrow = isSelected ? ">" : " ";
|
|
174
257
|
const timestampText = formatLatestCommit(item.latestCommitTimestamp);
|
|
175
258
|
const timestampWidth = stringWidth(timestampText);
|
|
176
259
|
|
|
177
260
|
const indicatorInfo = cleanupUI?.indicators?.[item.name];
|
|
178
|
-
let indicatorIcon = indicatorInfo?.icon ??
|
|
261
|
+
let indicatorIcon = indicatorInfo?.icon ?? "";
|
|
179
262
|
if (indicatorIcon && indicatorInfo?.color && !isSelected) {
|
|
180
263
|
switch (indicatorInfo.color) {
|
|
181
|
-
case
|
|
264
|
+
case "cyan":
|
|
182
265
|
indicatorIcon = chalk.cyan(indicatorIcon);
|
|
183
266
|
break;
|
|
184
|
-
case
|
|
267
|
+
case "green":
|
|
185
268
|
indicatorIcon = chalk.green(indicatorIcon);
|
|
186
269
|
break;
|
|
187
|
-
case
|
|
270
|
+
case "yellow":
|
|
188
271
|
indicatorIcon = chalk.yellow(indicatorIcon);
|
|
189
272
|
break;
|
|
190
|
-
case
|
|
273
|
+
case "red":
|
|
191
274
|
indicatorIcon = chalk.red(indicatorIcon);
|
|
192
275
|
break;
|
|
193
276
|
default:
|
|
194
277
|
break;
|
|
195
278
|
}
|
|
196
279
|
}
|
|
197
|
-
const indicatorPrefix = indicatorIcon ? `${indicatorIcon} ` :
|
|
280
|
+
const indicatorPrefix = indicatorIcon ? `${indicatorIcon} ` : "";
|
|
198
281
|
const staticPrefix = `${arrow} ${indicatorPrefix}`;
|
|
199
282
|
const staticPrefixWidth = stringWidth(staticPrefix);
|
|
200
283
|
|
|
201
|
-
const availableLeftWidth = Math.max(
|
|
284
|
+
const availableLeftWidth = Math.max(
|
|
285
|
+
staticPrefixWidth,
|
|
286
|
+
columns - timestampWidth - 1,
|
|
287
|
+
);
|
|
202
288
|
const maxLabelWidth = Math.max(0, availableLeftWidth - staticPrefixWidth);
|
|
203
289
|
const truncatedLabel = truncateToWidth(item.label, maxLabelWidth);
|
|
204
290
|
const leftText = `${staticPrefix}${truncatedLabel}`;
|
|
205
291
|
|
|
206
292
|
const leftMeasuredWidth = stringWidth(leftText);
|
|
207
293
|
const leftDisplayWidth = measureDisplayWidth(leftText);
|
|
208
|
-
const baseGapWidth = Math.max(
|
|
209
|
-
|
|
294
|
+
const baseGapWidth = Math.max(
|
|
295
|
+
1,
|
|
296
|
+
columns - leftMeasuredWidth - timestampWidth,
|
|
297
|
+
);
|
|
298
|
+
const displayGapWidth = Math.max(
|
|
299
|
+
1,
|
|
300
|
+
columns - leftDisplayWidth - timestampWidth,
|
|
301
|
+
);
|
|
210
302
|
const cursorShift = Math.max(0, displayGapWidth - baseGapWidth);
|
|
211
303
|
|
|
212
|
-
const gap =
|
|
213
|
-
const cursorAdjust = cursorShift > 0 ? `\u001b[${cursorShift}C` :
|
|
304
|
+
const gap = " ".repeat(baseGapWidth);
|
|
305
|
+
const cursorAdjust = cursorShift > 0 ? `\u001b[${cursorShift}C` : "";
|
|
214
306
|
|
|
215
307
|
let line = `${leftText}${gap}${cursorAdjust}${timestampText}`;
|
|
216
308
|
const paddingWidth = Math.max(0, columns - stringWidth(line));
|
|
217
309
|
if (paddingWidth > 0) {
|
|
218
|
-
line +=
|
|
310
|
+
line += " ".repeat(paddingWidth);
|
|
219
311
|
}
|
|
220
312
|
|
|
221
|
-
const output = isSelected
|
|
222
|
-
? `[46m[30m${line}[0m`
|
|
223
|
-
: line;
|
|
313
|
+
const output = isSelected ? `\u001b[46m\u001b[30m${line}\u001b[0m` : line;
|
|
224
314
|
return <Text>{output}</Text>;
|
|
225
315
|
},
|
|
226
|
-
[cleanupUI, formatLatestCommit, truncateToWidth]
|
|
316
|
+
[cleanupUI, formatLatestCommit, truncateToWidth],
|
|
227
317
|
);
|
|
228
318
|
|
|
229
319
|
return (
|
|
@@ -236,8 +326,30 @@ export function BranchListScreen({
|
|
|
236
326
|
{...(workingDirectory !== undefined && { workingDirectory })}
|
|
237
327
|
/>
|
|
238
328
|
|
|
329
|
+
{/* Filter Input - Always visible */}
|
|
330
|
+
<Box>
|
|
331
|
+
<Text dimColor>Filter: </Text>
|
|
332
|
+
{filterMode ? (
|
|
333
|
+
<Input
|
|
334
|
+
value={filterQuery}
|
|
335
|
+
onChange={setFilterQuery}
|
|
336
|
+
onSubmit={() => {}} // No-op: filter is applied in real-time
|
|
337
|
+
placeholder="Type to search..."
|
|
338
|
+
blockKeys={["c", "r", "m", "f"]} // Block shortcuts while typing
|
|
339
|
+
/>
|
|
340
|
+
) : (
|
|
341
|
+
<Text dimColor>{filterQuery || "(press f to filter)"}</Text>
|
|
342
|
+
)}
|
|
343
|
+
{filterQuery && (
|
|
344
|
+
<Text dimColor>
|
|
345
|
+
{" "}
|
|
346
|
+
(Showing {filteredBranches.length} of {branches.length})
|
|
347
|
+
</Text>
|
|
348
|
+
)}
|
|
349
|
+
</Box>
|
|
350
|
+
|
|
239
351
|
{/* Stats */}
|
|
240
|
-
<Box
|
|
352
|
+
<Box>
|
|
241
353
|
<Stats stats={stats} lastUpdated={lastUpdated} />
|
|
242
354
|
</Box>
|
|
243
355
|
|
|
@@ -268,16 +380,29 @@ export function BranchListScreen({
|
|
|
268
380
|
</Box>
|
|
269
381
|
)}
|
|
270
382
|
|
|
271
|
-
{!loading &&
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
383
|
+
{!loading &&
|
|
384
|
+
!error &&
|
|
385
|
+
branches.length > 0 &&
|
|
386
|
+
filteredBranches.length === 0 &&
|
|
387
|
+
filterQuery && (
|
|
388
|
+
<Box>
|
|
389
|
+
<Text dimColor>No branches match your filter</Text>
|
|
390
|
+
</Box>
|
|
391
|
+
)}
|
|
392
|
+
|
|
393
|
+
{!loading &&
|
|
394
|
+
!error &&
|
|
395
|
+
branches.length > 0 &&
|
|
396
|
+
filteredBranches.length > 0 && (
|
|
397
|
+
<Select
|
|
398
|
+
items={filteredBranches}
|
|
399
|
+
onSelect={onSelect}
|
|
400
|
+
limit={limit}
|
|
401
|
+
disabled={Boolean(cleanupUI?.inputLocked)}
|
|
402
|
+
renderIndicator={() => null}
|
|
403
|
+
renderItem={renderBranchRow}
|
|
404
|
+
/>
|
|
405
|
+
)}
|
|
281
406
|
</Box>
|
|
282
407
|
|
|
283
408
|
{cleanupUI?.footerMessage && (
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import React, { useState } from
|
|
2
|
-
import { Box, Text, useInput } from
|
|
3
|
-
import { Header } from
|
|
4
|
-
import { Footer } from
|
|
5
|
-
import { Select } from
|
|
6
|
-
import { useTerminalSize } from
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import { Header } from "../parts/Header.js";
|
|
4
|
+
import { Footer } from "../parts/Footer.js";
|
|
5
|
+
import { Select } from "../common/Select.js";
|
|
6
|
+
import { useTerminalSize } from "../../hooks/useTerminalSize.js";
|
|
7
7
|
|
|
8
|
-
export type ExecutionMode =
|
|
8
|
+
export type ExecutionMode = "normal" | "continue" | "resume";
|
|
9
9
|
|
|
10
10
|
export interface ExecutionModeItem {
|
|
11
11
|
label: string;
|
|
@@ -62,33 +62,34 @@ export function ExecutionModeSelectorScreen({
|
|
|
62
62
|
// Execution mode options (Step 1)
|
|
63
63
|
const modeItems: ExecutionModeItem[] = [
|
|
64
64
|
{
|
|
65
|
-
label:
|
|
66
|
-
value:
|
|
67
|
-
description:
|
|
65
|
+
label: "Normal",
|
|
66
|
+
value: "normal",
|
|
67
|
+
description: "Start fresh session",
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
|
-
label:
|
|
71
|
-
value:
|
|
72
|
-
description:
|
|
70
|
+
label: "Continue",
|
|
71
|
+
value: "continue",
|
|
72
|
+
description: "Continue from last session",
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
|
-
label:
|
|
76
|
-
value:
|
|
77
|
-
description:
|
|
75
|
+
label: "Resume",
|
|
76
|
+
value: "resume",
|
|
77
|
+
description: "Resume specific session",
|
|
78
78
|
},
|
|
79
79
|
];
|
|
80
80
|
|
|
81
81
|
// Skip permissions options (Step 2)
|
|
82
82
|
const skipPermissionsItems: SkipPermissionsItem[] = [
|
|
83
83
|
{
|
|
84
|
-
label:
|
|
85
|
-
value:
|
|
86
|
-
description:
|
|
84
|
+
label: "No",
|
|
85
|
+
value: "no",
|
|
86
|
+
description: "Normal permission checks",
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
|
-
label:
|
|
90
|
-
value:
|
|
91
|
-
description:
|
|
89
|
+
label: "Yes",
|
|
90
|
+
value: "yes",
|
|
91
|
+
description:
|
|
92
|
+
"Skip permission checks (--dangerously-skip-permissions / --yolo)",
|
|
92
93
|
},
|
|
93
94
|
];
|
|
94
95
|
|
|
@@ -103,22 +104,22 @@ export function ExecutionModeSelectorScreen({
|
|
|
103
104
|
if (selectedMode) {
|
|
104
105
|
onSelect({
|
|
105
106
|
mode: selectedMode,
|
|
106
|
-
skipPermissions: item.value ===
|
|
107
|
+
skipPermissions: item.value === "yes",
|
|
107
108
|
});
|
|
108
109
|
}
|
|
109
110
|
};
|
|
110
111
|
|
|
111
112
|
// Footer actions
|
|
112
113
|
const footerActions = [
|
|
113
|
-
{ key:
|
|
114
|
-
{ key:
|
|
114
|
+
{ key: "enter", description: "Select" },
|
|
115
|
+
{ key: "esc", description: step === 2 ? "Back to mode selection" : "Back" },
|
|
115
116
|
];
|
|
116
117
|
|
|
117
118
|
return (
|
|
118
119
|
<Box flexDirection="column" height={rows}>
|
|
119
120
|
{/* Header */}
|
|
120
121
|
<Header
|
|
121
|
-
title={step === 1 ?
|
|
122
|
+
title={step === 1 ? "Execution Mode" : "Skip Permissions"}
|
|
122
123
|
titleColor="magenta"
|
|
123
124
|
version={version}
|
|
124
125
|
/>
|
|
@@ -135,9 +136,15 @@ export function ExecutionModeSelectorScreen({
|
|
|
135
136
|
) : (
|
|
136
137
|
<>
|
|
137
138
|
<Box marginBottom={1}>
|
|
138
|
-
<Text>
|
|
139
|
+
<Text>
|
|
140
|
+
Skip permission checks? (--dangerously-skip-permissions /
|
|
141
|
+
--yolo)
|
|
142
|
+
</Text>
|
|
139
143
|
</Box>
|
|
140
|
-
<Select
|
|
144
|
+
<Select
|
|
145
|
+
items={skipPermissionsItems}
|
|
146
|
+
onSelect={handleSkipPermissionsSelect}
|
|
147
|
+
/>
|
|
141
148
|
</>
|
|
142
149
|
)}
|
|
143
150
|
</Box>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { Box, Text, useInput } from
|
|
3
|
-
import { Header } from
|
|
4
|
-
import { Footer } from
|
|
5
|
-
import { Select } from
|
|
6
|
-
import { useTerminalSize } from
|
|
7
|
-
import type { CleanupTarget } from
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import { Header } from "../parts/Header.js";
|
|
4
|
+
import { Footer } from "../parts/Footer.js";
|
|
5
|
+
import { Select } from "../common/Select.js";
|
|
6
|
+
import { useTerminalSize } from "../../hooks/useTerminalSize.js";
|
|
7
|
+
import type { CleanupTarget } from "../../types.js";
|
|
8
8
|
|
|
9
9
|
export interface PRItem {
|
|
10
10
|
label: string;
|
|
@@ -44,7 +44,7 @@ export function PRCleanupScreen({
|
|
|
44
44
|
useInput((input, key) => {
|
|
45
45
|
if (key.escape) {
|
|
46
46
|
onBack();
|
|
47
|
-
} else if (input ===
|
|
47
|
+
} else if (input === "r") {
|
|
48
48
|
onRefresh();
|
|
49
49
|
}
|
|
50
50
|
});
|
|
@@ -53,28 +53,28 @@ export function PRCleanupScreen({
|
|
|
53
53
|
const prItems: PRItem[] = targets.map((target) => {
|
|
54
54
|
const pr = target.pullRequest;
|
|
55
55
|
const flags: string[] = [];
|
|
56
|
-
if (target.cleanupType ===
|
|
57
|
-
flags.push(
|
|
56
|
+
if (target.cleanupType === "worktree-and-branch") {
|
|
57
|
+
flags.push("worktree");
|
|
58
58
|
} else {
|
|
59
|
-
flags.push(
|
|
59
|
+
flags.push("branch");
|
|
60
60
|
}
|
|
61
|
-
if (target.reasons?.includes(
|
|
62
|
-
flags.push(
|
|
61
|
+
if (target.reasons?.includes("merged-pr")) {
|
|
62
|
+
flags.push("merged");
|
|
63
63
|
}
|
|
64
|
-
if (target.reasons?.includes(
|
|
65
|
-
flags.push(
|
|
64
|
+
if (target.reasons?.includes("no-diff-with-base")) {
|
|
65
|
+
flags.push("base");
|
|
66
66
|
}
|
|
67
67
|
if (target.hasUncommittedChanges) {
|
|
68
|
-
flags.push(
|
|
68
|
+
flags.push("changes");
|
|
69
69
|
}
|
|
70
70
|
if (target.hasUnpushedCommits) {
|
|
71
|
-
flags.push(
|
|
71
|
+
flags.push("unpushed");
|
|
72
72
|
}
|
|
73
73
|
if (target.isAccessible === false) {
|
|
74
|
-
flags.push(
|
|
74
|
+
flags.push("inaccessible");
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
const flagText = flags.length > 0 ? ` [${flags.join(
|
|
77
|
+
const flagText = flags.length > 0 ? ` [${flags.join(", ")}]` : "";
|
|
78
78
|
|
|
79
79
|
const label = pr
|
|
80
80
|
? `${target.branch} - #${pr.number} ${pr.title}${flagText}`
|
|
@@ -98,9 +98,9 @@ export function PRCleanupScreen({
|
|
|
98
98
|
|
|
99
99
|
// Footer actions
|
|
100
100
|
const footerActions = [
|
|
101
|
-
{ key:
|
|
102
|
-
{ key:
|
|
103
|
-
{ key:
|
|
101
|
+
{ key: "enter", description: "Cleanup" },
|
|
102
|
+
{ key: "r", description: "Refresh" },
|
|
103
|
+
{ key: "esc", description: "Back" },
|
|
104
104
|
];
|
|
105
105
|
|
|
106
106
|
return (
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { Box, Text, useInput } from
|
|
3
|
-
import { Header } from
|
|
4
|
-
import { Footer } from
|
|
5
|
-
import { Select } from
|
|
6
|
-
import { useTerminalSize } from
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import { Header } from "../parts/Header.js";
|
|
4
|
+
import { Footer } from "../parts/Footer.js";
|
|
5
|
+
import { Select } from "../common/Select.js";
|
|
6
|
+
import { useTerminalSize } from "../../hooks/useTerminalSize.js";
|
|
7
7
|
|
|
8
8
|
export interface SessionItem {
|
|
9
9
|
label: string;
|
|
@@ -59,8 +59,8 @@ export function SessionSelectorScreen({
|
|
|
59
59
|
|
|
60
60
|
// Footer actions
|
|
61
61
|
const footerActions = [
|
|
62
|
-
{ key:
|
|
63
|
-
{ key:
|
|
62
|
+
{ key: "enter", description: "Select" },
|
|
63
|
+
{ key: "esc", description: "Back" },
|
|
64
64
|
];
|
|
65
65
|
|
|
66
66
|
return (
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { Box, Text, useInput } from
|
|
3
|
-
import { Header } from
|
|
4
|
-
import { Footer } from
|
|
5
|
-
import { Select } from
|
|
6
|
-
import { useTerminalSize } from
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import { Header } from "../parts/Header.js";
|
|
4
|
+
import { Footer } from "../parts/Footer.js";
|
|
5
|
+
import { Select } from "../common/Select.js";
|
|
6
|
+
import { useTerminalSize } from "../../hooks/useTerminalSize.js";
|
|
7
7
|
|
|
8
8
|
export interface WorktreeItem {
|
|
9
9
|
branch: string;
|
|
@@ -64,8 +64,8 @@ export function WorktreeManagerScreen({
|
|
|
64
64
|
|
|
65
65
|
// Footer actions
|
|
66
66
|
const footerActions = [
|
|
67
|
-
{ key:
|
|
68
|
-
{ key:
|
|
67
|
+
{ key: "enter", description: "Select" },
|
|
68
|
+
{ key: "esc", description: "Back" },
|
|
69
69
|
];
|
|
70
70
|
|
|
71
71
|
return (
|
|
@@ -46,7 +46,9 @@ export function BranchActionSelectorScreen({
|
|
|
46
46
|
(mode === "protected" ? "Switch to root branch" : "Use existing branch");
|
|
47
47
|
const secondaryActionLabel =
|
|
48
48
|
secondaryLabel ??
|
|
49
|
-
(mode === "protected"
|
|
49
|
+
(mode === "protected"
|
|
50
|
+
? "Create new branch from this branch"
|
|
51
|
+
: "Create new branch");
|
|
50
52
|
|
|
51
53
|
const items: SelectItem[] = [
|
|
52
54
|
{
|
|
@@ -74,15 +76,18 @@ export function BranchActionSelectorScreen({
|
|
|
74
76
|
|
|
75
77
|
// Footer actions
|
|
76
78
|
const footerActions = [
|
|
77
|
-
{ key:
|
|
78
|
-
{ key:
|
|
79
|
+
{ key: "enter", description: "Select" },
|
|
80
|
+
{ key: "esc", description: "Back" },
|
|
79
81
|
];
|
|
80
82
|
|
|
81
83
|
return (
|
|
82
84
|
<Box flexDirection="column">
|
|
83
85
|
<Box marginBottom={1}>
|
|
84
86
|
<Text>
|
|
85
|
-
Selected branch:
|
|
87
|
+
Selected branch:{" "}
|
|
88
|
+
<Text bold color="cyan">
|
|
89
|
+
{selectedBranch}
|
|
90
|
+
</Text>
|
|
86
91
|
</Text>
|
|
87
92
|
</Box>
|
|
88
93
|
{infoMessage ? (
|