@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,6 +1,6 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { Box, Text } from
|
|
3
|
-
import { Select, type SelectItem } from
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { Select, type SelectItem } from "./Select.js";
|
|
4
4
|
|
|
5
5
|
export interface ConfirmProps {
|
|
6
6
|
message: string;
|
|
@@ -16,17 +16,17 @@ export interface ConfirmProps {
|
|
|
16
16
|
export function Confirm({
|
|
17
17
|
message,
|
|
18
18
|
onConfirm,
|
|
19
|
-
yesLabel =
|
|
20
|
-
noLabel =
|
|
19
|
+
yesLabel = "Yes",
|
|
20
|
+
noLabel = "No",
|
|
21
21
|
defaultNo = false,
|
|
22
22
|
}: ConfirmProps) {
|
|
23
23
|
const items: SelectItem[] = [
|
|
24
|
-
{ label: yesLabel, value:
|
|
25
|
-
{ label: noLabel, value:
|
|
24
|
+
{ label: yesLabel, value: "yes" },
|
|
25
|
+
{ label: noLabel, value: "no" },
|
|
26
26
|
];
|
|
27
27
|
|
|
28
28
|
const handleSelect = (item: SelectItem) => {
|
|
29
|
-
onConfirm(item.value ===
|
|
29
|
+
onConfirm(item.value === "yes");
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
return (
|
|
@@ -34,7 +34,11 @@ export function Confirm({
|
|
|
34
34
|
<Box marginBottom={1}>
|
|
35
35
|
<Text>{message}</Text>
|
|
36
36
|
</Box>
|
|
37
|
-
<Select
|
|
37
|
+
<Select
|
|
38
|
+
items={items}
|
|
39
|
+
onSelect={handleSelect}
|
|
40
|
+
initialIndex={defaultNo ? 1 : 0}
|
|
41
|
+
/>
|
|
38
42
|
</Box>
|
|
39
43
|
);
|
|
40
44
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { Component, type ReactNode } from
|
|
2
|
-
import { Box, Text } from
|
|
1
|
+
import React, { Component, type ReactNode } from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
3
|
|
|
4
4
|
interface ErrorBoundaryProps {
|
|
5
5
|
children: ReactNode;
|
|
@@ -14,7 +14,10 @@ interface ErrorBoundaryState {
|
|
|
14
14
|
/**
|
|
15
15
|
* Error Boundary component to catch and display errors
|
|
16
16
|
*/
|
|
17
|
-
export class ErrorBoundary extends Component<
|
|
17
|
+
export class ErrorBoundary extends Component<
|
|
18
|
+
ErrorBoundaryProps,
|
|
19
|
+
ErrorBoundaryState
|
|
20
|
+
> {
|
|
18
21
|
constructor(props: ErrorBoundaryProps) {
|
|
19
22
|
super(props);
|
|
20
23
|
this.state = { hasError: false, error: null };
|
|
@@ -25,7 +28,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
|
|
28
|
-
console.error(
|
|
31
|
+
console.error("ErrorBoundary caught an error:", error, errorInfo);
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
componentDidUpdate(prevProps: ErrorBoundaryProps): void {
|
|
@@ -46,7 +49,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|
|
46
49
|
return (
|
|
47
50
|
<Box flexDirection="column" padding={1}>
|
|
48
51
|
<Text color="red" bold>
|
|
49
|
-
Error: {this.state.error.message ||
|
|
52
|
+
Error: {this.state.error.message || "Unknown error"}
|
|
50
53
|
</Text>
|
|
51
54
|
</Box>
|
|
52
55
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { Box, Text } from
|
|
3
|
-
import TextInput from
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import TextInput from "ink-text-input";
|
|
4
4
|
|
|
5
5
|
export interface InputProps {
|
|
6
6
|
value: string;
|
|
@@ -9,12 +9,34 @@ export interface InputProps {
|
|
|
9
9
|
placeholder?: string;
|
|
10
10
|
label?: string;
|
|
11
11
|
mask?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Block specific key bindings to prevent parent handlers from processing them
|
|
14
|
+
* Useful for blocking shortcuts like 'c', 'r', 'm' while typing
|
|
15
|
+
*/
|
|
16
|
+
blockKeys?: string[];
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
/**
|
|
15
20
|
* Input component - wrapper around ink-text-input with optional label
|
|
16
21
|
*/
|
|
17
|
-
export function Input({
|
|
22
|
+
export function Input({
|
|
23
|
+
value,
|
|
24
|
+
onChange,
|
|
25
|
+
onSubmit,
|
|
26
|
+
placeholder,
|
|
27
|
+
label,
|
|
28
|
+
mask,
|
|
29
|
+
blockKeys,
|
|
30
|
+
}: InputProps) {
|
|
31
|
+
// Block specific keys from being processed by parent useInput handlers
|
|
32
|
+
// This prevents shortcuts (c/r/m) from triggering while typing in the input
|
|
33
|
+
useInput((input) => {
|
|
34
|
+
if (blockKeys && blockKeys.includes(input)) {
|
|
35
|
+
// Consume the key - don't let it propagate to parent handlers
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
18
40
|
return (
|
|
19
41
|
<Box flexDirection="column">
|
|
20
42
|
{label && (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useRef, useState } from
|
|
2
|
-
import { Box, Text } from
|
|
1
|
+
import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
3
|
|
|
4
4
|
export interface LoadingIndicatorProps {
|
|
5
5
|
/** true にするとローディング表示を開始する */
|
|
@@ -14,7 +14,7 @@ export interface LoadingIndicatorProps {
|
|
|
14
14
|
frames?: string[];
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const DEFAULT_FRAMES = [
|
|
17
|
+
const DEFAULT_FRAMES = ["|", "/", "-", "\\"];
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* ローディング中に簡易スピナーとメッセージを表示するコンポーネント。
|
|
@@ -23,7 +23,7 @@ const DEFAULT_FRAMES = ['|', '/', '-', '\\'];
|
|
|
23
23
|
export function LoadingIndicator({
|
|
24
24
|
isLoading,
|
|
25
25
|
delay = 300,
|
|
26
|
-
message =
|
|
26
|
+
message = "Loading... please wait",
|
|
27
27
|
interval = 80,
|
|
28
28
|
frames = DEFAULT_FRAMES,
|
|
29
29
|
}: LoadingIndicatorProps) {
|
|
@@ -33,7 +33,10 @@ export function LoadingIndicator({
|
|
|
33
33
|
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
34
34
|
|
|
35
35
|
// スピナーに使用するフレームをキャッシュ
|
|
36
|
-
const safeFrames = useMemo(
|
|
36
|
+
const safeFrames = useMemo(
|
|
37
|
+
() => (frames.length > 0 ? frames : DEFAULT_FRAMES),
|
|
38
|
+
[frames],
|
|
39
|
+
);
|
|
37
40
|
|
|
38
41
|
useEffect(() => {
|
|
39
42
|
// ローディングが開始したら、delay後に表示を有効化
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useEffect, useState } from
|
|
2
|
-
import { Box, Text, useInput, useStdout } from
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { Box, Text, useInput, useStdout } from "ink";
|
|
3
3
|
|
|
4
4
|
export interface SelectItem {
|
|
5
5
|
label: string;
|
|
@@ -16,7 +16,7 @@ export interface SelectProps<T extends SelectItem = SelectItem> {
|
|
|
16
16
|
renderItem?: (
|
|
17
17
|
item: T,
|
|
18
18
|
isSelected: boolean,
|
|
19
|
-
context: { columns: number }
|
|
19
|
+
context: { columns: number },
|
|
20
20
|
) => React.ReactNode;
|
|
21
21
|
// Optional controlled component props for cursor position
|
|
22
22
|
selectedIndex?: number;
|
|
@@ -29,7 +29,7 @@ export interface SelectProps<T extends SelectItem = SelectItem> {
|
|
|
29
29
|
*/
|
|
30
30
|
function arePropsEqual<T extends SelectItem = SelectItem>(
|
|
31
31
|
prevProps: SelectProps<T>,
|
|
32
|
-
nextProps: SelectProps<T
|
|
32
|
+
nextProps: SelectProps<T>,
|
|
33
33
|
): boolean {
|
|
34
34
|
// Check if non-array props are the same
|
|
35
35
|
if (
|
|
@@ -59,7 +59,10 @@ function arePropsEqual<T extends SelectItem = SelectItem>(
|
|
|
59
59
|
return false;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
if (
|
|
62
|
+
if (
|
|
63
|
+
prevItem.value !== nextItem.value ||
|
|
64
|
+
prevItem.label !== nextItem.label
|
|
65
|
+
) {
|
|
63
66
|
return false;
|
|
64
67
|
}
|
|
65
68
|
}
|
|
@@ -73,7 +76,7 @@ function arePropsEqual<T extends SelectItem = SelectItem>(
|
|
|
73
76
|
* Cursor stops at top and bottom instead of wrapping around
|
|
74
77
|
* Wrapped with React.memo for performance optimization
|
|
75
78
|
*/
|
|
76
|
-
const SelectComponent = <T extends SelectItem = SelectItem
|
|
79
|
+
const SelectComponent = <T extends SelectItem = SelectItem>({
|
|
77
80
|
items,
|
|
78
81
|
onSelect,
|
|
79
82
|
limit,
|
|
@@ -85,15 +88,18 @@ const SelectComponent = <T extends SelectItem = SelectItem,>({
|
|
|
85
88
|
onSelectedIndexChange,
|
|
86
89
|
}: SelectProps<T>) => {
|
|
87
90
|
// Support both controlled and uncontrolled modes
|
|
88
|
-
const [internalSelectedIndex, setInternalSelectedIndex] =
|
|
91
|
+
const [internalSelectedIndex, setInternalSelectedIndex] =
|
|
92
|
+
useState(initialIndex);
|
|
89
93
|
const [offset, setOffset] = useState(0);
|
|
90
94
|
|
|
91
95
|
// Use external selectedIndex if provided (controlled mode), otherwise use internal state
|
|
92
96
|
const isControlled = externalSelectedIndex !== undefined;
|
|
93
|
-
const selectedIndex = isControlled
|
|
97
|
+
const selectedIndex = isControlled
|
|
98
|
+
? externalSelectedIndex
|
|
99
|
+
: internalSelectedIndex;
|
|
94
100
|
|
|
95
101
|
const updateSelectedIndex = (value: number | ((prev: number) => number)) => {
|
|
96
|
-
const newIndex = typeof value ===
|
|
102
|
+
const newIndex = typeof value === "function" ? value(selectedIndex) : value;
|
|
97
103
|
|
|
98
104
|
if (!isControlled) {
|
|
99
105
|
setInternalSelectedIndex(newIndex);
|
|
@@ -134,7 +140,7 @@ const SelectComponent = <T extends SelectItem = SelectItem,>({
|
|
|
134
140
|
|
|
135
141
|
// Only handle navigation and selection keys
|
|
136
142
|
// Let other keys (q, m, n, c, etc.) propagate to parent components
|
|
137
|
-
if (key.upArrow || input ===
|
|
143
|
+
if (key.upArrow || input === "k") {
|
|
138
144
|
// Move up but don't loop - stop at 0
|
|
139
145
|
updateSelectedIndex((current) => {
|
|
140
146
|
const newIndex = Math.max(0, current - 1);
|
|
@@ -146,7 +152,7 @@ const SelectComponent = <T extends SelectItem = SelectItem,>({
|
|
|
146
152
|
|
|
147
153
|
return newIndex;
|
|
148
154
|
});
|
|
149
|
-
} else if (key.downArrow || input ===
|
|
155
|
+
} else if (key.downArrow || input === "j") {
|
|
150
156
|
// Move down but don't loop - stop at last item
|
|
151
157
|
updateSelectedIndex((current) => {
|
|
152
158
|
const newIndex = Math.min(items.length - 1, current + 1);
|
|
@@ -189,11 +195,13 @@ const SelectComponent = <T extends SelectItem = SelectItem,>({
|
|
|
189
195
|
);
|
|
190
196
|
}
|
|
191
197
|
|
|
192
|
-
const indicatorElement = renderIndicator
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
198
|
+
const indicatorElement = renderIndicator ? (
|
|
199
|
+
renderIndicator(item, isSelected)
|
|
200
|
+
) : isSelected ? (
|
|
201
|
+
<Text color="cyan">›</Text>
|
|
202
|
+
) : (
|
|
203
|
+
<Text> </Text>
|
|
204
|
+
);
|
|
197
205
|
|
|
198
206
|
return (
|
|
199
207
|
<Box key={item.value} flexDirection="row">
|
|
@@ -213,4 +221,7 @@ const SelectComponent = <T extends SelectItem = SelectItem,>({
|
|
|
213
221
|
/**
|
|
214
222
|
* Export memoized Select component
|
|
215
223
|
*/
|
|
216
|
-
export const Select = React.memo(
|
|
224
|
+
export const Select = React.memo(
|
|
225
|
+
SelectComponent,
|
|
226
|
+
arePropsEqual,
|
|
227
|
+
) as typeof SelectComponent;
|
|
@@ -5,9 +5,7 @@ import { Header } from "./Header.js";
|
|
|
5
5
|
|
|
6
6
|
describe("Header Component", () => {
|
|
7
7
|
it("正常系: versionプロップありの場合、タイトルとバージョンを表示する", () => {
|
|
8
|
-
const { lastFrame } = render(
|
|
9
|
-
<Header title="gwt" version="1.12.3" />
|
|
10
|
-
);
|
|
8
|
+
const { lastFrame } = render(<Header title="gwt" version="1.12.3" />);
|
|
11
9
|
|
|
12
10
|
const output = lastFrame();
|
|
13
11
|
|
|
@@ -27,9 +25,7 @@ describe("Header Component", () => {
|
|
|
27
25
|
});
|
|
28
26
|
|
|
29
27
|
it("正常系: version={null}の場合、タイトルのみ表示する", () => {
|
|
30
|
-
const { lastFrame } = render(
|
|
31
|
-
<Header title="gwt" version={null} />
|
|
32
|
-
);
|
|
28
|
+
const { lastFrame } = render(<Header title="gwt" version={null} />);
|
|
33
29
|
|
|
34
30
|
const output = lastFrame();
|
|
35
31
|
|
|
@@ -46,7 +42,7 @@ describe("Header Component", () => {
|
|
|
46
42
|
version="1.12.3"
|
|
47
43
|
showDivider={true}
|
|
48
44
|
dividerChar="─"
|
|
49
|
-
|
|
45
|
+
/>,
|
|
50
46
|
);
|
|
51
47
|
|
|
52
48
|
const output = lastFrame();
|
|
@@ -57,11 +53,7 @@ describe("Header Component", () => {
|
|
|
57
53
|
|
|
58
54
|
it("正常系: showDivider=falseの場合、区切り線が表示されない", () => {
|
|
59
55
|
const { lastFrame } = render(
|
|
60
|
-
<Header
|
|
61
|
-
title="gwt"
|
|
62
|
-
version="1.12.3"
|
|
63
|
-
showDivider={false}
|
|
64
|
-
/>
|
|
56
|
+
<Header title="gwt" version="1.12.3" showDivider={false} />,
|
|
65
57
|
);
|
|
66
58
|
|
|
67
59
|
const output = lastFrame();
|
|
@@ -73,9 +65,7 @@ describe("Header Component", () => {
|
|
|
73
65
|
});
|
|
74
66
|
|
|
75
67
|
it("正常系: プレリリースバージョンも正しく表示される", () => {
|
|
76
|
-
const { lastFrame } = render(
|
|
77
|
-
<Header title="gwt" version="2.0.0-beta.1" />
|
|
78
|
-
);
|
|
68
|
+
const { lastFrame } = render(<Header title="gwt" version="2.0.0-beta.1" />);
|
|
79
69
|
|
|
80
70
|
const output = lastFrame();
|
|
81
71
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import React, { useState, useEffect } 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 { getAllTools } from
|
|
8
|
-
import type { AIToolConfig } from
|
|
1
|
+
import React, { useState, useEffect } 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 { getAllTools } from "../../../../config/tools.js";
|
|
8
|
+
import type { AIToolConfig } from "../../../../types/tools.js";
|
|
9
9
|
|
|
10
10
|
export type AITool = string;
|
|
11
11
|
|
|
@@ -27,7 +27,11 @@ export interface AIToolSelectorScreenProps {
|
|
|
27
27
|
*
|
|
28
28
|
* This screen dynamically loads available tools from the configuration (builtin + custom).
|
|
29
29
|
*/
|
|
30
|
-
export function AIToolSelectorScreen({
|
|
30
|
+
export function AIToolSelectorScreen({
|
|
31
|
+
onBack,
|
|
32
|
+
onSelect,
|
|
33
|
+
version,
|
|
34
|
+
}: AIToolSelectorScreenProps) {
|
|
31
35
|
const { rows } = useTerminalSize();
|
|
32
36
|
const [toolItems, setToolItems] = useState<AIToolItem[]>([]);
|
|
33
37
|
const [isLoading, setIsLoading] = useState(true);
|
|
@@ -60,7 +64,7 @@ export function AIToolSelectorScreen({ onBack, onSelect, version }: AIToolSelect
|
|
|
60
64
|
setToolItems(items);
|
|
61
65
|
} catch (error) {
|
|
62
66
|
// If loading fails, show error in console but don't crash
|
|
63
|
-
console.error(
|
|
67
|
+
console.error("Failed to load tools:", error);
|
|
64
68
|
// Fall back to empty array
|
|
65
69
|
setToolItems([]);
|
|
66
70
|
} finally {
|
|
@@ -86,8 +90,8 @@ export function AIToolSelectorScreen({ onBack, onSelect, version }: AIToolSelect
|
|
|
86
90
|
|
|
87
91
|
// Footer actions
|
|
88
92
|
const footerActions = [
|
|
89
|
-
{ key:
|
|
90
|
-
{ key:
|
|
93
|
+
{ key: "enter", description: "Select" },
|
|
94
|
+
{ key: "esc", description: "Back" },
|
|
91
95
|
];
|
|
92
96
|
|
|
93
97
|
return (
|
|
@@ -103,7 +107,9 @@ export function AIToolSelectorScreen({ onBack, onSelect, version }: AIToolSelect
|
|
|
103
107
|
{isLoading ? (
|
|
104
108
|
<Text>Loading tools...</Text>
|
|
105
109
|
) : toolItems.length === 0 ? (
|
|
106
|
-
<Text color="yellow">
|
|
110
|
+
<Text color="yellow">
|
|
111
|
+
No tools available. Please check your configuration.
|
|
112
|
+
</Text>
|
|
107
113
|
) : (
|
|
108
114
|
<Select items={toolItems} onSelect={handleSelect} />
|
|
109
115
|
)}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import React, { useState, useCallback, useEffect, useRef } from
|
|
2
|
-
import { Box, Text, useInput } from
|
|
3
|
-
import { Header } from
|
|
4
|
-
import { Footer } from
|
|
5
|
-
import { Select } from
|
|
6
|
-
import { Input } from
|
|
7
|
-
import { useTerminalSize } from
|
|
8
|
-
import { BRANCH_PREFIXES } from
|
|
1
|
+
import React, { useState, useCallback, useEffect, useRef } 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 { Input } from "../common/Input.js";
|
|
7
|
+
import { useTerminalSize } from "../../hooks/useTerminalSize.js";
|
|
8
|
+
import { BRANCH_PREFIXES } from "../../../../config/constants.js";
|
|
9
9
|
|
|
10
|
-
type BranchType =
|
|
11
|
-
type Step =
|
|
10
|
+
type BranchType = "feature" | "bugfix" | "hotfix" | "release";
|
|
11
|
+
type Step = "type-selection" | "name-input";
|
|
12
12
|
|
|
13
|
-
const SPINNER_FRAMES = [
|
|
13
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧"];
|
|
14
14
|
|
|
15
15
|
export interface BranchCreatorScreenProps {
|
|
16
16
|
onBack: () => void;
|
|
@@ -39,11 +39,13 @@ export function BranchCreatorScreen({
|
|
|
39
39
|
disableAnimation = false,
|
|
40
40
|
}: BranchCreatorScreenProps) {
|
|
41
41
|
const { rows } = useTerminalSize();
|
|
42
|
-
const [step, setStep] = useState<Step>(
|
|
43
|
-
const [selectedType, setSelectedType] = useState<BranchType>(
|
|
44
|
-
const [branchName, setBranchName] = useState(
|
|
42
|
+
const [step, setStep] = useState<Step>("type-selection");
|
|
43
|
+
const [selectedType, setSelectedType] = useState<BranchType>("feature");
|
|
44
|
+
const [branchName, setBranchName] = useState("");
|
|
45
45
|
const [isCreating, setIsCreating] = useState(false);
|
|
46
|
-
const [pendingBranchName, setPendingBranchName] = useState<string | null>(
|
|
46
|
+
const [pendingBranchName, setPendingBranchName] = useState<string | null>(
|
|
47
|
+
null,
|
|
48
|
+
);
|
|
47
49
|
const spinnerIndexRef = useRef(0);
|
|
48
50
|
const [spinnerIndex, setSpinnerIndex] = useState(0);
|
|
49
51
|
|
|
@@ -63,40 +65,43 @@ export function BranchCreatorScreen({
|
|
|
63
65
|
// Branch type options
|
|
64
66
|
const branchTypeItems: BranchTypeItem[] = [
|
|
65
67
|
{
|
|
66
|
-
label:
|
|
67
|
-
value:
|
|
68
|
-
description:
|
|
68
|
+
label: "feature",
|
|
69
|
+
value: "feature",
|
|
70
|
+
description: "New feature development",
|
|
69
71
|
},
|
|
70
72
|
{
|
|
71
|
-
label:
|
|
72
|
-
value:
|
|
73
|
-
description:
|
|
73
|
+
label: "bugfix",
|
|
74
|
+
value: "bugfix",
|
|
75
|
+
description: "Bug fix",
|
|
74
76
|
},
|
|
75
77
|
{
|
|
76
|
-
label:
|
|
77
|
-
value:
|
|
78
|
-
description:
|
|
78
|
+
label: "hotfix",
|
|
79
|
+
value: "hotfix",
|
|
80
|
+
description: "Critical bug fix",
|
|
79
81
|
},
|
|
80
82
|
{
|
|
81
|
-
label:
|
|
82
|
-
value:
|
|
83
|
-
description:
|
|
83
|
+
label: "release",
|
|
84
|
+
value: "release",
|
|
85
|
+
description: "Release preparation",
|
|
84
86
|
},
|
|
85
87
|
];
|
|
86
88
|
|
|
87
89
|
// Handle branch type selection
|
|
88
90
|
const handleTypeSelect = useCallback((item: BranchTypeItem) => {
|
|
89
91
|
setSelectedType(item.value);
|
|
90
|
-
setStep(
|
|
92
|
+
setStep("name-input");
|
|
91
93
|
}, []);
|
|
92
94
|
|
|
93
95
|
// Handle branch name input
|
|
94
|
-
const handleNameChange = useCallback(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
const handleNameChange = useCallback(
|
|
97
|
+
(value: string) => {
|
|
98
|
+
if (isCreating) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
setBranchName(value);
|
|
102
|
+
},
|
|
103
|
+
[isCreating],
|
|
104
|
+
);
|
|
100
105
|
|
|
101
106
|
// Handle branch creation
|
|
102
107
|
const handleCreate = useCallback(async () => {
|
|
@@ -109,7 +114,10 @@ export function BranchCreatorScreen({
|
|
|
109
114
|
return;
|
|
110
115
|
}
|
|
111
116
|
|
|
112
|
-
const prefix =
|
|
117
|
+
const prefix =
|
|
118
|
+
BRANCH_PREFIXES[
|
|
119
|
+
selectedType.toUpperCase() as keyof typeof BRANCH_PREFIXES
|
|
120
|
+
];
|
|
113
121
|
const fullBranchName = `${prefix}${trimmedName}`;
|
|
114
122
|
|
|
115
123
|
setIsCreating(true);
|
|
@@ -125,18 +133,17 @@ export function BranchCreatorScreen({
|
|
|
125
133
|
}, [branchName, selectedType, onCreate, isCreating]);
|
|
126
134
|
|
|
127
135
|
// Footer actions
|
|
128
|
-
const footerActions =
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
];
|
|
136
|
+
const footerActions = isCreating
|
|
137
|
+
? []
|
|
138
|
+
: step === "type-selection"
|
|
139
|
+
? [
|
|
140
|
+
{ key: "enter", description: "Select" },
|
|
141
|
+
{ key: "esc", description: "Back" },
|
|
142
|
+
]
|
|
143
|
+
: [
|
|
144
|
+
{ key: "enter", description: "Create" },
|
|
145
|
+
{ key: "esc", description: "Back" },
|
|
146
|
+
];
|
|
140
147
|
|
|
141
148
|
useEffect(() => {
|
|
142
149
|
if (!isCreating || disableAnimation) {
|
|
@@ -146,7 +153,8 @@ export function BranchCreatorScreen({
|
|
|
146
153
|
}
|
|
147
154
|
|
|
148
155
|
const interval = setInterval(() => {
|
|
149
|
-
spinnerIndexRef.current =
|
|
156
|
+
spinnerIndexRef.current =
|
|
157
|
+
(spinnerIndexRef.current + 1) % SPINNER_FRAMES.length;
|
|
150
158
|
setSpinnerIndex(spinnerIndexRef.current);
|
|
151
159
|
}, 120);
|
|
152
160
|
|
|
@@ -167,7 +175,10 @@ export function BranchCreatorScreen({
|
|
|
167
175
|
{baseBranch && (
|
|
168
176
|
<Box marginBottom={1}>
|
|
169
177
|
<Text>
|
|
170
|
-
Base branch:
|
|
178
|
+
Base branch:{" "}
|
|
179
|
+
<Text bold color="cyan">
|
|
180
|
+
{baseBranch}
|
|
181
|
+
</Text>
|
|
171
182
|
</Text>
|
|
172
183
|
</Box>
|
|
173
184
|
)}
|
|
@@ -175,9 +186,9 @@ export function BranchCreatorScreen({
|
|
|
175
186
|
<Box flexDirection="column">
|
|
176
187
|
<Box marginBottom={1}>
|
|
177
188
|
<Text>
|
|
178
|
-
{spinnerFrame}{
|
|
189
|
+
{spinnerFrame}{" "}
|
|
179
190
|
<Text color="cyan">
|
|
180
|
-
Creating branch{
|
|
191
|
+
Creating branch{" "}
|
|
181
192
|
<Text bold>
|
|
182
193
|
{pendingBranchName ??
|
|
183
194
|
`${BRANCH_PREFIXES[selectedType.toUpperCase() as keyof typeof BRANCH_PREFIXES]}${branchName.trim()}`}
|
|
@@ -185,9 +196,11 @@ export function BranchCreatorScreen({
|
|
|
185
196
|
</Text>
|
|
186
197
|
</Text>
|
|
187
198
|
</Box>
|
|
188
|
-
<Text color="gray">
|
|
199
|
+
<Text color="gray">
|
|
200
|
+
Please wait while the branch is being created...
|
|
201
|
+
</Text>
|
|
189
202
|
</Box>
|
|
190
|
-
) : step ===
|
|
203
|
+
) : step === "type-selection" ? (
|
|
191
204
|
<Box flexDirection="column">
|
|
192
205
|
<Box marginBottom={1}>
|
|
193
206
|
<Text>Select branch type:</Text>
|
|
@@ -198,7 +211,14 @@ export function BranchCreatorScreen({
|
|
|
198
211
|
<Box flexDirection="column">
|
|
199
212
|
<Box marginBottom={1}>
|
|
200
213
|
<Text>
|
|
201
|
-
Branch name prefix:
|
|
214
|
+
Branch name prefix:{" "}
|
|
215
|
+
<Text bold>
|
|
216
|
+
{
|
|
217
|
+
BRANCH_PREFIXES[
|
|
218
|
+
selectedType.toUpperCase() as keyof typeof BRANCH_PREFIXES
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
</Text>
|
|
202
222
|
</Text>
|
|
203
223
|
</Box>
|
|
204
224
|
<Input
|