@akiojin/gwt 2.0.4 → 2.2.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.
Files changed (43) hide show
  1. package/README.ja.md +4 -4
  2. package/README.md +3 -3
  3. package/dist/cli/ui/components/common/Input.d.ts +6 -1
  4. package/dist/cli/ui/components/common/Input.d.ts.map +1 -1
  5. package/dist/cli/ui/components/common/Input.js +10 -2
  6. package/dist/cli/ui/components/common/Input.js.map +1 -1
  7. package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts.map +1 -1
  8. package/dist/cli/ui/components/screens/BranchCreatorScreen.js +5 -0
  9. package/dist/cli/ui/components/screens/BranchCreatorScreen.js.map +1 -1
  10. package/dist/cli/ui/components/screens/BranchListScreen.d.ts +5 -1
  11. package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
  12. package/dist/cli/ui/components/screens/BranchListScreen.js +78 -9
  13. package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
  14. package/dist/cli/ui/types.d.ts +2 -2
  15. package/dist/cli/ui/types.d.ts.map +1 -1
  16. package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -1
  17. package/dist/cli/ui/utils/branchFormatter.js +1 -0
  18. package/dist/cli/ui/utils/branchFormatter.js.map +1 -1
  19. package/dist/client/assets/index-V6hDu9KS.js +81 -0
  20. package/dist/client/index.html +1 -1
  21. package/dist/config/constants.d.ts +2 -0
  22. package/dist/config/constants.d.ts.map +1 -1
  23. package/dist/config/constants.js +2 -0
  24. package/dist/config/constants.js.map +1 -1
  25. package/dist/git.d.ts.map +1 -1
  26. package/dist/git.js +2 -0
  27. package/dist/git.js.map +1 -1
  28. package/dist/services/git.service.d.ts.map +1 -1
  29. package/dist/services/git.service.js +2 -0
  30. package/dist/services/git.service.js.map +1 -1
  31. package/package.json +2 -1
  32. package/src/cli/ui/__tests__/components/screens/BranchCreatorScreen.test.tsx +1 -0
  33. package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +313 -0
  34. package/src/cli/ui/__tests__/utils/branchFormatter.test.ts +14 -0
  35. package/src/cli/ui/components/common/Input.tsx +16 -2
  36. package/src/cli/ui/components/screens/BranchCreatorScreen.tsx +6 -1
  37. package/src/cli/ui/components/screens/BranchListScreen.tsx +117 -9
  38. package/src/cli/ui/types.ts +2 -1
  39. package/src/cli/ui/utils/branchFormatter.ts +1 -0
  40. package/src/config/constants.ts +2 -0
  41. package/src/git.ts +1 -0
  42. package/src/services/git.service.ts +1 -0
  43. package/dist/client/assets/index-CqaYsI0z.js +0 -81
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { Box, Text } from 'ink';
2
+ import { Box, Text, useInput } from 'ink';
3
3
  import TextInput from 'ink-text-input';
4
4
 
5
5
  export interface InputProps {
@@ -9,12 +9,26 @@ 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({ value, onChange, onSubmit, placeholder, label, mask }: InputProps) {
22
+ export function Input({ value, onChange, onSubmit, placeholder, label, mask, blockKeys }: InputProps) {
23
+ // Block specific keys from being processed by parent useInput handlers
24
+ // This prevents shortcuts (c/r/m) from triggering while typing in the input
25
+ useInput((input) => {
26
+ if (blockKeys && blockKeys.includes(input)) {
27
+ // Consume the key - don't let it propagate to parent handlers
28
+ return;
29
+ }
30
+ });
31
+
18
32
  return (
19
33
  <Box flexDirection="column">
20
34
  {label && (
@@ -7,7 +7,7 @@ import { Input } from '../common/Input.js';
7
7
  import { useTerminalSize } from '../../hooks/useTerminalSize.js';
8
8
  import { BRANCH_PREFIXES } from '../../../../config/constants.js';
9
9
 
10
- type BranchType = 'feature' | 'hotfix' | 'release';
10
+ type BranchType = 'feature' | 'bugfix' | 'hotfix' | 'release';
11
11
  type Step = 'type-selection' | 'name-input';
12
12
 
13
13
  const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧'];
@@ -67,6 +67,11 @@ export function BranchCreatorScreen({
67
67
  value: 'feature',
68
68
  description: 'New feature development',
69
69
  },
70
+ {
71
+ label: 'bugfix',
72
+ value: 'bugfix',
73
+ description: 'Bug fix',
74
+ },
70
75
  {
71
76
  label: 'hotfix',
72
77
  value: 'hotfix',
@@ -1,9 +1,10 @@
1
- import React, { useCallback } from 'react';
1
+ import React, { useCallback, useState, useMemo } from 'react';
2
2
  import { Box, Text, useInput } from 'ink';
3
3
  import { Header } from '../parts/Header.js';
4
4
  import { Stats } from '../parts/Stats.js';
5
5
  import { Footer } from '../parts/Footer.js';
6
6
  import { Select } from '../common/Select.js';
7
+ import { Input } from '../common/Input.js';
7
8
  import { LoadingIndicator } from '../common/LoadingIndicator.js';
8
9
  import { useTerminalSize } from '../../hooks/useTerminalSize.js';
9
10
  import type { BranchItem, Statistics } from '../../types.js';
@@ -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,16 +87,65 @@ 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 = testFilterQuery !== undefined ? testFilterQuery : internalFilterQuery;
100
+ const setFilterQuery = useCallback(
101
+ (query: string) => {
102
+ setInternalFilterQuery(query);
103
+ testOnFilterQueryChange?.(query);
104
+ },
105
+ [testOnFilterQueryChange]
106
+ );
107
+
108
+ // Focus management: true = filter mode, false = branch selection mode
109
+ // Allow test control via props
110
+ const [internalFilterMode, setInternalFilterMode] = useState(false);
111
+ const filterMode = testFilterMode !== undefined ? testFilterMode : internalFilterMode;
112
+ const setFilterMode = useCallback(
113
+ (mode: boolean) => {
114
+ setInternalFilterMode(mode);
115
+ testOnFilterModeChange?.(mode);
116
+ },
117
+ [testOnFilterModeChange]
118
+ );
119
+
87
120
  // Handle keyboard input
88
- // Note: Select component handles Enter and arrow keys
89
- useInput((input) => {
121
+ // Note: Input component blocks specific keys (c/r/m/f) using blockKeys prop
122
+ // This prevents shortcuts from triggering while typing in the filter
123
+ useInput((input, key) => {
90
124
  if (cleanupUI?.inputLocked) {
91
125
  return;
92
126
  }
93
127
 
128
+ // Escape key handling
129
+ if (key.escape) {
130
+ if (filterQuery) {
131
+ // Clear filter query first
132
+ setFilterQuery('');
133
+ return;
134
+ }
135
+ if (filterMode) {
136
+ // Exit filter mode if query is empty
137
+ setFilterMode(false);
138
+ return;
139
+ }
140
+ }
141
+
142
+ // Enter filter mode with 'f' key (only in branch selection mode)
143
+ if (input === 'f' && !filterMode) {
144
+ setFilterMode(true);
145
+ return;
146
+ }
147
+
148
+ // Global shortcuts (blocked by Input component when typing in filter mode)
94
149
  if (input === 'm' && onNavigate) {
95
150
  onNavigate('worktree-manager');
96
151
  } else if (input === 'c') {
@@ -100,23 +155,48 @@ export function BranchListScreen({
100
155
  }
101
156
  });
102
157
 
158
+ // Filter branches based on query
159
+ const filteredBranches = useMemo(() => {
160
+ if (!filterQuery.trim()) {
161
+ return branches;
162
+ }
163
+
164
+ const query = filterQuery.toLowerCase();
165
+ return branches.filter((branch) => {
166
+ // Search in branch name
167
+ if (branch.name.toLowerCase().includes(query)) {
168
+ return true;
169
+ }
170
+
171
+ // Search in PR title if available (only openPR has title)
172
+ if (branch.openPR?.title?.toLowerCase().includes(query)) {
173
+ return true;
174
+ }
175
+
176
+ return false;
177
+ });
178
+ }, [branches, filterQuery]);
179
+
103
180
  // Calculate available space for branch list
104
181
  // Header: 2 lines (title + divider)
182
+ // Filter input: 1 line
105
183
  // Stats: 1 line
106
184
  // Empty line: 1 line
107
185
  // Footer: 1 line
108
- // Total fixed: 5 lines
186
+ // Total fixed: 6 lines
109
187
  const headerLines = 2;
188
+ const filterLines = 1;
110
189
  const statsLines = 1;
111
190
  const emptyLine = 1;
112
191
  const footerLines = 1;
113
- const fixedLines = headerLines + statsLines + emptyLine + footerLines;
192
+ const fixedLines = headerLines + filterLines + statsLines + emptyLine + footerLines;
114
193
  const contentHeight = rows - fixedLines;
115
194
  const limit = Math.max(5, contentHeight); // Minimum 5 items visible
116
195
 
117
196
  // Footer actions
118
197
  const footerActions = [
119
198
  { key: 'enter', description: 'Select' },
199
+ { key: 'f', description: 'Filter' },
120
200
  { key: 'r', description: 'Refresh' },
121
201
  { key: 'm', description: 'Manage worktrees' },
122
202
  { key: 'c', description: 'Cleanup branches' },
@@ -219,7 +299,7 @@ export function BranchListScreen({
219
299
  }
220
300
 
221
301
  const output = isSelected
222
- ? `${line}`
302
+ ? `\u001b[46m\u001b[30m${line}\u001b[0m`
223
303
  : line;
224
304
  return <Text>{output}</Text>;
225
305
  },
@@ -236,8 +316,30 @@ export function BranchListScreen({
236
316
  {...(workingDirectory !== undefined && { workingDirectory })}
237
317
  />
238
318
 
319
+ {/* Filter Input - Always visible */}
320
+ <Box>
321
+ <Text dimColor>Filter: </Text>
322
+ {filterMode ? (
323
+ <Input
324
+ value={filterQuery}
325
+ onChange={setFilterQuery}
326
+ onSubmit={() => {}} // No-op: filter is applied in real-time
327
+ placeholder="Type to search..."
328
+ blockKeys={['c', 'r', 'm', 'f']} // Block shortcuts while typing
329
+ />
330
+ ) : (
331
+ <Text dimColor>{filterQuery || '(press f to filter)'}</Text>
332
+ )}
333
+ {filterQuery && (
334
+ <Text dimColor>
335
+ {' '}
336
+ (Showing {filteredBranches.length} of {branches.length})
337
+ </Text>
338
+ )}
339
+ </Box>
340
+
239
341
  {/* Stats */}
240
- <Box marginTop={1}>
342
+ <Box>
241
343
  <Stats stats={stats} lastUpdated={lastUpdated} />
242
344
  </Box>
243
345
 
@@ -268,9 +370,15 @@ export function BranchListScreen({
268
370
  </Box>
269
371
  )}
270
372
 
271
- {!loading && !error && branches.length > 0 && (
373
+ {!loading && !error && branches.length > 0 && filteredBranches.length === 0 && filterQuery && (
374
+ <Box>
375
+ <Text dimColor>No branches match your filter</Text>
376
+ </Box>
377
+ )}
378
+
379
+ {!loading && !error && branches.length > 0 && filteredBranches.length > 0 && (
272
380
  <Select
273
- items={branches}
381
+ items={filteredBranches}
274
382
  onSelect={onSelect}
275
383
  limit={limit}
276
384
  disabled={Boolean(cleanupUI?.inputLocked)}
@@ -8,7 +8,7 @@ export interface WorktreeInfo {
8
8
  export interface BranchInfo {
9
9
  name: string;
10
10
  type: "local" | "remote";
11
- branchType: "feature" | "hotfix" | "release" | "main" | "develop" | "other";
11
+ branchType: "feature" | "bugfix" | "hotfix" | "release" | "main" | "develop" | "other";
12
12
  isCurrent: boolean;
13
13
  description?: string;
14
14
  worktree?: WorktreeInfo;
@@ -35,6 +35,7 @@ export interface EnhancedBranchChoice extends BranchChoice {
35
35
 
36
36
  export type BranchType =
37
37
  | "feature"
38
+ | "bugfix"
38
39
  | "hotfix"
39
40
  | "release"
40
41
  | "main"
@@ -12,6 +12,7 @@ const branchIcons: Record<BranchType, string> = {
12
12
  main: "⚡",
13
13
  develop: "⚡",
14
14
  feature: "✨",
15
+ bugfix: "🐛",
15
16
  hotfix: "🔥",
16
17
  release: "🚀",
17
18
  other: "📌",
@@ -5,6 +5,7 @@
5
5
  // ブランチタイプ
6
6
  export const BRANCH_TYPES = {
7
7
  FEATURE: "feature",
8
+ BUGFIX: "bugfix",
8
9
  HOTFIX: "hotfix",
9
10
  RELEASE: "release",
10
11
  MAIN: "main",
@@ -15,6 +16,7 @@ export const BRANCH_TYPES = {
15
16
  // ブランチプレフィックス
16
17
  export const BRANCH_PREFIXES = {
17
18
  FEATURE: "feature/",
19
+ BUGFIX: "bugfix/",
18
20
  HOTFIX: "hotfix/",
19
21
  RELEASE: "release/",
20
22
  } as const;
package/src/git.ts CHANGED
@@ -351,6 +351,7 @@ function getBranchType(branchName: string): BranchInfo["branchType"] {
351
351
  if (branchName === "main" || branchName === "master") return "main";
352
352
  if (branchName === "develop" || branchName === "dev") return "develop";
353
353
  if (branchName.startsWith("feature/")) return "feature";
354
+ if (branchName.startsWith("bugfix/") || branchName.startsWith("bug/")) return "bugfix";
354
355
  if (branchName.startsWith("hotfix/")) return "hotfix";
355
356
  if (branchName.startsWith("release/")) return "release";
356
357
  return "other";
@@ -52,6 +52,7 @@ export class GitService {
52
52
 
53
53
  private getBranchType(branchName: string): BranchInfo["branchType"] {
54
54
  if (branchName.startsWith("feature/")) return "feature";
55
+ if (branchName.startsWith("bugfix/") || branchName.startsWith("bug/")) return "bugfix";
55
56
  if (branchName.startsWith("hotfix/")) return "hotfix";
56
57
  if (branchName.startsWith("release/")) return "release";
57
58
  if (branchName === "main" || branchName === "master") return "main";