ccmanager 1.4.0 → 1.4.1

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.
@@ -246,18 +246,12 @@ const ConfigureCommand = ({ onComplete }) => {
246
246
  // In input mode, let TextInput or SelectInput handle it
247
247
  return;
248
248
  }
249
- if (viewMode === 'list' || viewMode === 'edit') {
249
+ if (viewMode === 'list' ||
250
+ viewMode === 'edit' ||
251
+ viewMode === 'delete-confirm') {
250
252
  // SelectInput handles navigation and selection
251
253
  return;
252
254
  }
253
- else if (viewMode === 'delete-confirm') {
254
- if (key.upArrow || key.downArrow) {
255
- setSelectedIndex(prev => (prev === 0 ? 1 : 0));
256
- }
257
- else if (key.return) {
258
- handleDeleteConfirm();
259
- }
260
- }
261
255
  });
262
256
  // Render strategy selection
263
257
  if (isSelectingStrategy) {
@@ -360,6 +354,19 @@ const ConfigureCommand = ({ onComplete }) => {
360
354
  // Render delete confirmation
361
355
  if (viewMode === 'delete-confirm') {
362
356
  const preset = presets.find(p => p.id === selectedPresetId);
357
+ const confirmItems = [
358
+ { label: 'Yes, delete', value: 'yes' },
359
+ { label: 'Cancel', value: 'cancel' },
360
+ ];
361
+ const handleConfirmSelect = (item) => {
362
+ if (item.value === 'yes') {
363
+ handleDeleteConfirm();
364
+ }
365
+ else {
366
+ setViewMode('edit');
367
+ setSelectedIndex(6); // Return to delete option in edit menu
368
+ }
369
+ };
363
370
  return (React.createElement(Box, { flexDirection: "column" },
364
371
  React.createElement(Box, { marginBottom: 1 },
365
372
  React.createElement(Text, { bold: true, color: "red" }, "Confirm Delete")),
@@ -368,17 +375,15 @@ const ConfigureCommand = ({ onComplete }) => {
368
375
  "Delete preset \"",
369
376
  preset?.name,
370
377
  "\"?")),
371
- React.createElement(Box, { flexDirection: "column" },
372
- React.createElement(Box, null,
373
- React.createElement(Text, { color: selectedIndex === 0 ? 'red' : undefined },
374
- selectedIndex === 0 ? '> ' : ' ',
375
- "Yes, delete")),
376
- React.createElement(Box, null,
377
- React.createElement(Text, { color: selectedIndex === 1 ? 'cyan' : undefined },
378
- selectedIndex === 1 ? '> ' : ' ',
379
- "Cancel"))),
378
+ React.createElement(SelectInput, { items: confirmItems, onSelect: handleConfirmSelect, initialIndex: 1, indicatorComponent: ({ isSelected }) => (React.createElement(Text, { color: isSelected ? 'red' : undefined }, isSelected ? '>' : ' ')), itemComponent: ({ isSelected, label }) => (React.createElement(Text, { color: label === 'Yes, delete'
379
+ ? isSelected
380
+ ? 'red'
381
+ : undefined
382
+ : isSelected
383
+ ? 'cyan'
384
+ : undefined, inverse: isSelected }, label)) }),
380
385
  React.createElement(Box, { marginTop: 1 },
381
- React.createElement(Text, { dimColor: true }, "Press \u2191\u2193 to navigate, Enter to confirm"))));
386
+ React.createElement(Text, { dimColor: true }, "Press \u2191\u2193/j/k to navigate, Enter to confirm"))));
382
387
  }
383
388
  // Render edit preset view
384
389
  if (viewMode === 'edit') {
@@ -1,81 +1,61 @@
1
1
  import React, { useState } from 'react';
2
2
  import { Box, Text, useInput } from 'ink';
3
+ import SelectInput from 'ink-select-input';
3
4
  import { shortcutManager } from '../services/shortcutManager.js';
4
5
  const DeleteConfirmation = ({ worktrees, onConfirm, onCancel, }) => {
5
6
  // Check if any worktrees have branches
6
7
  const hasAnyBranches = worktrees.some(wt => wt.branch);
7
8
  const [deleteBranch, setDeleteBranch] = useState(true);
8
- const [focusedOption, setFocusedOption] = useState(hasAnyBranches ? 'deleteBranch' : 'confirm');
9
- // Helper functions for navigation
10
- const isRadioOption = (option) => option === 'deleteBranch' || option === 'keepBranch';
11
- const isActionButton = (option) => option === 'confirm' || option === 'cancel';
12
- const handleUpArrow = () => {
13
- if (!hasAnyBranches) {
14
- if (focusedOption === 'cancel')
15
- setFocusedOption('confirm');
16
- return;
17
- }
18
- const navigationMap = {
19
- keepBranch: 'deleteBranch',
20
- confirm: 'keepBranch',
21
- cancel: 'keepBranch',
22
- };
23
- const next = navigationMap[focusedOption];
24
- if (next)
25
- setFocusedOption(next);
26
- };
27
- const handleDownArrow = () => {
28
- if (!hasAnyBranches) {
29
- if (focusedOption === 'confirm')
30
- setFocusedOption('cancel');
31
- return;
32
- }
33
- const navigationMap = {
34
- deleteBranch: 'keepBranch',
35
- keepBranch: 'confirm',
36
- confirm: 'cancel',
37
- };
38
- const next = navigationMap[focusedOption];
39
- if (next)
40
- setFocusedOption(next);
41
- };
42
- const handleHorizontalArrow = (direction) => {
43
- if (isActionButton(focusedOption)) {
44
- setFocusedOption(direction === 'left' ? 'confirm' : 'cancel');
45
- }
9
+ const [view, setView] = useState(hasAnyBranches ? 'options' : 'confirm');
10
+ const [focusedOption, setFocusedOption] = useState(deleteBranch ? 'deleteBranch' : 'keepBranch');
11
+ // Menu items for branch options
12
+ const branchOptions = [
13
+ {
14
+ label: `${deleteBranch ? '()' : '( )'} Delete the branches too`,
15
+ value: 'deleteBranch',
16
+ },
17
+ {
18
+ label: `${!deleteBranch ? '(•)' : '( )'} Keep the branches`,
19
+ value: 'keepBranch',
20
+ },
21
+ ];
22
+ // Menu items for actions
23
+ const actionOptions = [
24
+ { label: 'Confirm', value: 'confirm' },
25
+ { label: 'Cancel', value: 'cancel' },
26
+ ];
27
+ const handleBranchSelect = (item) => {
28
+ // Don't toggle on Enter - only update focused option
29
+ setFocusedOption(item.value);
46
30
  };
47
- const handleSelect = () => {
48
- if (isRadioOption(focusedOption)) {
49
- setDeleteBranch(focusedOption === 'deleteBranch');
50
- }
51
- else if (focusedOption === 'confirm') {
31
+ const handleActionSelect = (item) => {
32
+ if (item.value === 'confirm') {
52
33
  onConfirm(deleteBranch);
53
34
  }
54
- else if (focusedOption === 'cancel') {
35
+ else {
55
36
  onCancel();
56
37
  }
57
38
  };
58
39
  useInput((input, key) => {
59
- if (key.upArrow) {
60
- handleUpArrow();
61
- }
62
- else if (key.downArrow) {
63
- handleDownArrow();
64
- }
65
- else if (key.leftArrow) {
66
- handleHorizontalArrow('left');
67
- }
68
- else if (key.rightArrow) {
69
- handleHorizontalArrow('right');
40
+ if (shortcutManager.matchesShortcut('cancel', input, key)) {
41
+ onCancel();
70
42
  }
71
- else if (input === ' ' && isRadioOption(focusedOption)) {
72
- setDeleteBranch(focusedOption === 'deleteBranch');
43
+ else if (hasAnyBranches && view === 'options' && key.return) {
44
+ // Move to confirm view when Enter is pressed in options
45
+ setView('confirm');
73
46
  }
74
- else if (key.return) {
75
- handleSelect();
47
+ else if (hasAnyBranches && view === 'confirm' && key.escape) {
48
+ // Go back to options when Escape is pressed in confirm
49
+ setView('options');
76
50
  }
77
- else if (shortcutManager.matchesShortcut('cancel', input, key)) {
78
- onCancel();
51
+ else if (hasAnyBranches && view === 'options' && input === ' ') {
52
+ // Toggle selection on space for radio buttons
53
+ if (focusedOption === 'deleteBranch') {
54
+ setDeleteBranch(true);
55
+ }
56
+ else {
57
+ setDeleteBranch(false);
58
+ }
79
59
  }
80
60
  });
81
61
  return (React.createElement(Box, { flexDirection: "column" },
@@ -101,33 +81,38 @@ const DeleteConfirmation = ({ worktrees, onConfirm, onCancel, }) => {
101
81
  "... and ",
102
82
  worktrees.length - 8,
103
83
  " more worktrees")))),
104
- hasAnyBranches && (React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
84
+ hasAnyBranches && view === 'options' && (React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
105
85
  React.createElement(Text, { bold: true }, "What do you want to do with the associated branches?"),
106
- React.createElement(Box, { marginTop: 1, flexDirection: "column" },
107
- React.createElement(Box, null,
108
- React.createElement(Text, { color: focusedOption === 'deleteBranch' ? 'red' : undefined, inverse: focusedOption === 'deleteBranch' },
109
- deleteBranch ? '()' : '( )',
110
- " Delete the branches too")),
111
- React.createElement(Box, null,
112
- React.createElement(Text, { color: focusedOption === 'keepBranch' ? 'green' : undefined, inverse: focusedOption === 'keepBranch' },
113
- !deleteBranch ? '(•)' : '( )',
114
- " Keep the branches"))))),
115
- React.createElement(Box, { marginTop: 1 },
116
- React.createElement(Box, { marginRight: 2 },
117
- React.createElement(Text, { color: focusedOption === 'confirm' ? 'green' : 'white', inverse: focusedOption === 'confirm' },
118
- ' ',
119
- "Confirm",
120
- ' ')),
121
- React.createElement(Box, null,
122
- React.createElement(Text, { color: focusedOption === 'cancel' ? 'red' : 'white', inverse: focusedOption === 'cancel' },
123
- ' ',
124
- "Cancel",
125
- ' '))),
86
+ React.createElement(Box, { marginTop: 1 },
87
+ React.createElement(SelectInput, { items: branchOptions, onSelect: handleBranchSelect, onHighlight: (item) => {
88
+ setFocusedOption(item.value);
89
+ }, initialIndex: deleteBranch ? 0 : 1, indicatorComponent: ({ isSelected }) => (React.createElement(Text, { color: isSelected ? 'red' : undefined }, isSelected ? '>' : ' ')), itemComponent: ({ isSelected, label }) => (React.createElement(Text, { color: isSelected ? 'red' : undefined, inverse: isSelected }, label)) })))),
90
+ hasAnyBranches && view === 'confirm' && (React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
91
+ React.createElement(Text, { bold: true }, "Branch option selected:"),
92
+ React.createElement(Text, { color: "yellow" }, deleteBranch ? '✓ Delete the branches too' : '✓ Keep the branches'))),
93
+ (view === 'confirm' || !hasAnyBranches) && (React.createElement(Box, { marginTop: 1 },
94
+ React.createElement(SelectInput, { items: actionOptions, onSelect: handleActionSelect, initialIndex: 1, indicatorComponent: ({ isSelected }) => (React.createElement(Text, null, isSelected ? '>' : ' ')), itemComponent: ({ isSelected, label }) => {
95
+ const color = label === 'Confirm' ? 'green' : 'red';
96
+ return (React.createElement(Text, { color: isSelected ? color : 'white', inverse: isSelected },
97
+ ' ',
98
+ label,
99
+ ' '));
100
+ } }))),
126
101
  React.createElement(Box, { marginTop: 1 },
127
- React.createElement(Text, { dimColor: true },
128
- "Use \u2191\u2193 to navigate options, Space/Enter to select,",
102
+ React.createElement(Text, { dimColor: true }, hasAnyBranches && view === 'options' ? (React.createElement(React.Fragment, null,
103
+ "Use \u2191\u2193/j/k to navigate, Space to toggle, Enter to continue,",
104
+ ' ',
105
+ shortcutManager.getShortcutDisplay('cancel'),
106
+ " to cancel")) : view === 'confirm' ? (React.createElement(React.Fragment, null,
107
+ "Use \u2191\u2193/j/k to navigate, Enter to select",
108
+ hasAnyBranches ? ', Esc to go back' : '',
109
+ ",",
110
+ ' ',
111
+ shortcutManager.getShortcutDisplay('cancel'),
112
+ " to cancel")) : (React.createElement(React.Fragment, null,
113
+ "Use \u2191\u2193/j/k to navigate, Enter to select,",
129
114
  ' ',
130
115
  shortcutManager.getShortcutDisplay('cancel'),
131
- " to cancel"))));
116
+ " to cancel"))))));
132
117
  };
133
118
  export default DeleteConfirmation;
@@ -1,14 +1,14 @@
1
1
  import React, { useState, useEffect } from 'react';
2
2
  import { Box, Text, useInput } from 'ink';
3
+ import SelectInput from 'ink-select-input';
3
4
  import { WorktreeService } from '../services/worktreeService.js';
4
5
  import DeleteConfirmation from './DeleteConfirmation.js';
5
6
  import { shortcutManager } from '../services/shortcutManager.js';
6
7
  const DeleteWorktree = ({ onComplete, onCancel, }) => {
7
8
  const [worktrees, setWorktrees] = useState([]);
8
9
  const [selectedIndices, setSelectedIndices] = useState(new Set());
9
- const [focusedIndex, setFocusedIndex] = useState(0);
10
10
  const [confirmMode, setConfirmMode] = useState(false);
11
- const VIEWPORT_SIZE = 10; // Maximum number of items to display at once
11
+ const [focusedIndex, setFocusedIndex] = useState(0);
12
12
  useEffect(() => {
13
13
  const worktreeService = new WorktreeService();
14
14
  const allWorktrees = worktreeService.getWorktrees();
@@ -16,23 +16,30 @@ const DeleteWorktree = ({ onComplete, onCancel, }) => {
16
16
  const deletableWorktrees = allWorktrees.filter(wt => !wt.isMainWorktree);
17
17
  setWorktrees(deletableWorktrees);
18
18
  }, []);
19
+ // Create menu items from worktrees
20
+ const menuItems = worktrees.map((worktree, index) => {
21
+ const branchName = worktree.branch
22
+ ? worktree.branch.replace('refs/heads/', '')
23
+ : 'detached';
24
+ const isSelected = selectedIndices.has(index);
25
+ return {
26
+ label: `${isSelected ? '[✓]' : '[ ]'} ${branchName} (${worktree.path})`,
27
+ value: index.toString(),
28
+ };
29
+ });
30
+ const handleSelect = (item) => {
31
+ // Don't toggle on Enter - this will be used to confirm
32
+ // We'll handle Space key separately for toggling
33
+ const index = parseInt(item.value, 10);
34
+ setFocusedIndex(index);
35
+ };
19
36
  useInput((input, key) => {
20
- if (key.ctrl && input === 'c') {
21
- onCancel();
22
- return;
23
- }
24
37
  if (confirmMode) {
25
38
  // Confirmation component handles input
26
39
  return;
27
40
  }
28
- if (key.upArrow) {
29
- setFocusedIndex(prev => Math.max(0, prev - 1));
30
- }
31
- else if (key.downArrow) {
32
- setFocusedIndex(prev => Math.min(worktrees.length - 1, prev + 1));
33
- }
34
- else if (input === ' ') {
35
- // Toggle selection
41
+ if (input === ' ') {
42
+ // Toggle selection on space
36
43
  setSelectedIndices(prev => {
37
44
  const newSet = new Set(prev);
38
45
  if (newSet.has(focusedIndex)) {
@@ -44,10 +51,8 @@ const DeleteWorktree = ({ onComplete, onCancel, }) => {
44
51
  return newSet;
45
52
  });
46
53
  }
47
- else if (key.return) {
48
- if (selectedIndices.size > 0) {
49
- setConfirmMode(true);
50
- }
54
+ else if (key.return && selectedIndices.size > 0) {
55
+ setConfirmMode(true);
51
56
  }
52
57
  else if (shortcutManager.matchesShortcut('cancel', input, key)) {
53
58
  onCancel();
@@ -77,40 +82,17 @@ const DeleteWorktree = ({ onComplete, onCancel, }) => {
77
82
  React.createElement(Text, { bold: true, color: "red" }, "Delete Worktrees")),
78
83
  React.createElement(Box, { marginBottom: 1 },
79
84
  React.createElement(Text, { dimColor: true }, "Select worktrees to delete (Space to select, Enter to confirm):")),
80
- (() => {
81
- // Calculate viewport window
82
- const viewportStart = Math.max(0, Math.min(focusedIndex - Math.floor(VIEWPORT_SIZE / 2), worktrees.length - VIEWPORT_SIZE));
83
- const viewportEnd = Math.min(viewportStart + VIEWPORT_SIZE, worktrees.length);
84
- const visibleWorktrees = worktrees.slice(viewportStart, viewportEnd);
85
- return (React.createElement(React.Fragment, null,
86
- viewportStart > 0 && (React.createElement(Text, { dimColor: true },
87
- "\u2191 ",
88
- viewportStart,
89
- " more...")),
90
- visibleWorktrees.map((worktree, relativeIndex) => {
91
- const actualIndex = viewportStart + relativeIndex;
92
- const isSelected = selectedIndices.has(actualIndex);
93
- const isFocused = actualIndex === focusedIndex;
94
- const branchName = worktree.branch
95
- ? worktree.branch.replace('refs/heads/', '')
96
- : 'detached';
97
- return (React.createElement(Box, { key: worktree.path },
98
- React.createElement(Text, { color: isFocused ? 'green' : undefined, inverse: isFocused, dimColor: !isFocused && !isSelected },
99
- isSelected ? '[✓]' : '[ ]',
100
- " ",
101
- branchName,
102
- " (",
103
- worktree.path,
104
- ")")));
105
- }),
106
- viewportEnd < worktrees.length && (React.createElement(Text, { dimColor: true },
107
- "\u2193 ",
108
- worktrees.length - viewportEnd,
109
- " more..."))));
110
- })(),
85
+ React.createElement(SelectInput, { items: menuItems, onSelect: handleSelect, onHighlight: (item) => {
86
+ const index = parseInt(item.value, 10);
87
+ setFocusedIndex(index);
88
+ }, limit: 10, indicatorComponent: ({ isSelected }) => (React.createElement(Text, { color: isSelected ? 'green' : undefined }, isSelected ? '>' : ' ')), itemComponent: ({ isSelected, label }) => {
89
+ // Check if this item is actually selected (checkbox checked)
90
+ const hasCheckmark = label.includes('[✓]');
91
+ return (React.createElement(Text, { color: isSelected ? 'green' : undefined, inverse: isSelected, dimColor: !isSelected && !hasCheckmark }, label));
92
+ } }),
111
93
  React.createElement(Box, { marginTop: 1, flexDirection: "column" },
112
94
  React.createElement(Text, { dimColor: true },
113
- "Controls: \u2191\u2193 Navigate, Space Select, Enter Confirm,",
95
+ "Controls: \u2191\u2193/j/k Navigate, Space Select, Enter Confirm,",
114
96
  ' ',
115
97
  shortcutManager.getShortcutDisplay('cancel'),
116
98
  " Cancel"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccmanager",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "TUI application for managing multiple Claude Code sessions across Git worktrees",
5
5
  "license": "MIT",
6
6
  "author": "Kodai Kabasawa",