ccmanager 3.5.0 → 3.5.2

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 (82) hide show
  1. package/dist/cli.js +3 -2
  2. package/dist/components/App.d.ts +1 -0
  3. package/dist/components/App.js +17 -39
  4. package/dist/components/App.test.js +12 -44
  5. package/dist/components/Configuration.js +10 -15
  6. package/dist/components/ConfigureCommand.js +18 -106
  7. package/dist/components/ConfigureCustomCommand.js +2 -17
  8. package/dist/components/ConfigureOther.js +5 -23
  9. package/dist/components/ConfigureOther.test.js +7 -31
  10. package/dist/components/ConfigureShortcuts.js +4 -31
  11. package/dist/components/ConfigureStatusHooks.js +5 -44
  12. package/dist/components/ConfigureStatusHooks.test.js +7 -31
  13. package/dist/components/ConfigureTimeout.js +2 -14
  14. package/dist/components/ConfigureWorktree.js +4 -47
  15. package/dist/components/ConfigureWorktreeHooks.js +5 -37
  16. package/dist/components/ConfigureWorktreeHooks.test.js +8 -33
  17. package/dist/components/Confirmation.js +9 -21
  18. package/dist/components/CustomCommandSummary.js +2 -5
  19. package/dist/components/DeleteConfirmation.js +10 -47
  20. package/dist/components/DeleteWorktree.js +14 -42
  21. package/dist/components/DeleteWorktree.test.js +6 -6
  22. package/dist/components/LoadingSpinner.js +3 -6
  23. package/dist/components/LoadingSpinner.test.js +22 -22
  24. package/dist/components/Menu.d.ts +1 -0
  25. package/dist/components/Menu.js +10 -42
  26. package/dist/components/Menu.recent-projects.test.js +8 -8
  27. package/dist/components/Menu.test.js +10 -10
  28. package/dist/components/MergeWorktree.js +16 -88
  29. package/dist/components/MergeWorktree.test.js +5 -5
  30. package/dist/components/NewWorktree.js +25 -105
  31. package/dist/components/NewWorktree.test.js +8 -8
  32. package/dist/components/PresetSelector.js +3 -9
  33. package/dist/components/ProjectList.js +9 -38
  34. package/dist/components/ProjectList.recent-projects.test.js +7 -7
  35. package/dist/components/ProjectList.test.js +37 -37
  36. package/dist/components/RemoteBranchSelector.js +2 -21
  37. package/dist/components/RemoteBranchSelector.test.js +8 -8
  38. package/dist/components/TextInputWrapper.d.ts +5 -0
  39. package/dist/components/TextInputWrapper.js +138 -11
  40. package/dist/constants/statusIcons.d.ts +2 -1
  41. package/dist/constants/statusIcons.js +13 -4
  42. package/dist/constants/statusIcons.test.js +41 -11
  43. package/dist/contexts/ConfigEditorContext.d.ts +1 -1
  44. package/dist/contexts/ConfigEditorContext.js +3 -2
  45. package/dist/services/autoApprovalVerifier.js +1 -8
  46. package/dist/services/bunTerminal.js +41 -136
  47. package/dist/services/config/configEditor.js +2 -12
  48. package/dist/services/config/globalConfigManager.js +4 -24
  49. package/dist/services/config/projectConfigManager.js +3 -18
  50. package/dist/services/globalSessionOrchestrator.js +3 -12
  51. package/dist/services/globalSessionOrchestrator.test.js +1 -8
  52. package/dist/services/projectManager.js +13 -68
  53. package/dist/services/sessionManager.d.ts +1 -1
  54. package/dist/services/sessionManager.effect.test.js +9 -37
  55. package/dist/services/sessionManager.js +12 -28
  56. package/dist/services/sessionManager.test.js +48 -40
  57. package/dist/services/shortcutManager.js +7 -13
  58. package/dist/services/stateDetector/base.d.ts +1 -1
  59. package/dist/services/stateDetector/claude.d.ts +1 -1
  60. package/dist/services/stateDetector/claude.js +11 -4
  61. package/dist/services/stateDetector/claude.test.js +47 -24
  62. package/dist/services/stateDetector/cline.d.ts +1 -1
  63. package/dist/services/stateDetector/cline.js +1 -1
  64. package/dist/services/stateDetector/codex.d.ts +1 -1
  65. package/dist/services/stateDetector/codex.js +1 -1
  66. package/dist/services/stateDetector/cursor.d.ts +1 -1
  67. package/dist/services/stateDetector/cursor.js +1 -1
  68. package/dist/services/stateDetector/gemini.d.ts +1 -1
  69. package/dist/services/stateDetector/gemini.js +1 -1
  70. package/dist/services/stateDetector/github-copilot.d.ts +1 -1
  71. package/dist/services/stateDetector/github-copilot.js +1 -1
  72. package/dist/services/stateDetector/opencode.d.ts +1 -1
  73. package/dist/services/stateDetector/opencode.js +1 -1
  74. package/dist/services/stateDetector/types.d.ts +1 -1
  75. package/dist/services/worktreeConfigManager.js +3 -8
  76. package/dist/services/worktreeService.js +2 -12
  77. package/dist/types/index.js +4 -12
  78. package/dist/utils/logger.js +12 -33
  79. package/dist/utils/mutex.d.ts +1 -1
  80. package/dist/utils/mutex.js +4 -19
  81. package/dist/utils/worktreeUtils.js +4 -4
  82. package/package.json +12 -12
@@ -1,4 +1,5 @@
1
- import React, { useState, useEffect } from 'react';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
2
3
  import { Box, Text, useInput } from 'ink';
3
4
  import SelectInput from 'ink-select-input';
4
5
  import { Effect } from 'effect';
@@ -80,25 +81,13 @@ const DeleteWorktree = ({ projectPath, onComplete, onCancel, }) => {
80
81
  }
81
82
  });
82
83
  if (isLoading) {
83
- return (React.createElement(Box, { flexDirection: "column" },
84
- React.createElement(Text, { color: "cyan" }, "Loading worktrees...")));
84
+ return (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { color: "cyan", children: "Loading worktrees..." }) }));
85
85
  }
86
86
  if (error) {
87
- return (React.createElement(Box, { flexDirection: "column" },
88
- React.createElement(Text, { color: "red" }, "Error loading worktrees:"),
89
- React.createElement(Text, { color: "red" }, error),
90
- React.createElement(Text, { dimColor: true },
91
- "Press ",
92
- shortcutManager.getShortcutDisplay('cancel'),
93
- " to return to menu")));
87
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "red", children: "Error loading worktrees:" }), _jsx(Text, { color: "red", children: error }), _jsxs(Text, { dimColor: true, children: ["Press ", shortcutManager.getShortcutDisplay('cancel'), " to return to menu"] })] }));
94
88
  }
95
89
  if (worktrees.length === 0) {
96
- return (React.createElement(Box, { flexDirection: "column" },
97
- React.createElement(Text, { color: "yellow" }, "No worktrees available to delete."),
98
- React.createElement(Text, { dimColor: true },
99
- "Press ",
100
- shortcutManager.getShortcutDisplay('cancel'),
101
- " to return to menu")));
90
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: "No worktrees available to delete." }), _jsxs(Text, { dimColor: true, children: ["Press ", shortcutManager.getShortcutDisplay('cancel'), " to return to menu"] })] }));
102
91
  }
103
92
  if (confirmMode) {
104
93
  const selectedWorktrees = Array.from(selectedIndices).map(index => worktrees[index]);
@@ -109,32 +98,15 @@ const DeleteWorktree = ({ projectPath, onComplete, onCancel, }) => {
109
98
  const handleCancel = () => {
110
99
  setConfirmMode(false);
111
100
  };
112
- return (React.createElement(DeleteConfirmation, { worktrees: selectedWorktrees, onConfirm: handleConfirm, onCancel: handleCancel }));
101
+ return (_jsx(DeleteConfirmation, { worktrees: selectedWorktrees, onConfirm: handleConfirm, onCancel: handleCancel }));
113
102
  }
114
- return (React.createElement(Box, { flexDirection: "column" },
115
- React.createElement(Box, { marginBottom: 1 },
116
- React.createElement(Text, { bold: true, color: "red" }, "Delete Worktrees")),
117
- React.createElement(Box, { marginBottom: 1 },
118
- React.createElement(Text, { dimColor: true }, "Select worktrees to delete (Space to select, Enter to confirm):")),
119
- React.createElement(SelectInput, { items: menuItems, onSelect: handleSelect, onHighlight: (item) => {
120
- const index = parseInt(item.value, 10);
121
- setFocusedIndex(index);
122
- }, limit: 10, indicatorComponent: ({ isSelected }) => (React.createElement(Text, { color: isSelected ? 'green' : undefined }, isSelected ? '>' : ' ')), itemComponent: ({ isSelected, label }) => {
123
- // Check if this item is actually selected (checkbox checked)
124
- const hasCheckmark = label.includes('[✓]');
125
- return (React.createElement(Text, { color: isSelected ? 'green' : undefined, inverse: isSelected, dimColor: !isSelected && !hasCheckmark }, label));
126
- } }),
127
- React.createElement(Box, { marginTop: 1, flexDirection: "column" },
128
- React.createElement(Text, { dimColor: true },
129
- "Controls: \u2191\u2193/j/k Navigate, Space Select, Enter Confirm,",
130
- ' ',
131
- shortcutManager.getShortcutDisplay('cancel'),
132
- " Cancel"),
133
- selectedIndices.size > 0 && (React.createElement(Text, { color: "yellow" },
134
- selectedIndices.size,
135
- " worktree",
136
- selectedIndices.size > 1 ? 's' : '',
137
- ' ',
138
- "selected")))));
103
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "red", children: "Delete Worktrees" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Select worktrees to delete (Space to select, Enter to confirm):" }) }), _jsx(SelectInput, { items: menuItems, onSelect: handleSelect, onHighlight: (item) => {
104
+ const index = parseInt(item.value, 10);
105
+ setFocusedIndex(index);
106
+ }, limit: 10, indicatorComponent: ({ isSelected }) => (_jsx(Text, { color: isSelected ? 'green' : undefined, children: isSelected ? '>' : ' ' })), itemComponent: ({ isSelected, label }) => {
107
+ // Check if this item is actually selected (checkbox checked)
108
+ const hasCheckmark = label.includes('[✓]');
109
+ return (_jsx(Text, { color: isSelected ? 'green' : undefined, inverse: isSelected, dimColor: !isSelected && !hasCheckmark, children: label }));
110
+ } }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["Controls: \u2191\u2193/j/k Navigate, Space Select, Enter Confirm,", ' ', shortcutManager.getShortcutDisplay('cancel'), " Cancel"] }), selectedIndices.size > 0 && (_jsxs(Text, { color: "yellow", children: [selectedIndices.size, " worktree", selectedIndices.size > 1 ? 's' : '', ' ', "selected"] }))] })] }));
139
111
  };
140
112
  export default DeleteWorktree;
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { render } from 'ink-testing-library';
3
3
  import { describe, it, expect, vi, beforeEach } from 'vitest';
4
4
  import { Effect } from 'effect';
@@ -60,7 +60,7 @@ describe('DeleteWorktree - Effect Integration', () => {
60
60
  const onComplete = vi.fn();
61
61
  const onCancel = vi.fn();
62
62
  // WHEN: Component renders with projectPath
63
- render(React.createElement(DeleteWorktree, { projectPath: projectPath, onComplete: onComplete, onCancel: onCancel }));
63
+ render(_jsx(DeleteWorktree, { projectPath: projectPath, onComplete: onComplete, onCancel: onCancel }));
64
64
  // Wait for Effect to execute
65
65
  await new Promise(resolve => setTimeout(resolve, 50));
66
66
  // THEN: WorktreeService was called with projectPath
@@ -85,7 +85,7 @@ describe('DeleteWorktree - Effect Integration', () => {
85
85
  const onComplete = vi.fn();
86
86
  const onCancel = vi.fn();
87
87
  // WHEN: Component renders without projectPath
88
- render(React.createElement(DeleteWorktree, { onComplete: onComplete, onCancel: onCancel }));
88
+ render(_jsx(DeleteWorktree, { onComplete: onComplete, onCancel: onCancel }));
89
89
  // Wait for Effect to execute
90
90
  await new Promise(resolve => setTimeout(resolve, 50));
91
91
  // THEN: WorktreeService was called with undefined (defaults to cwd)
@@ -117,7 +117,7 @@ describe('DeleteWorktree - Effect Integration', () => {
117
117
  const onComplete = vi.fn();
118
118
  const onCancel = vi.fn();
119
119
  // WHEN: Component is rendered
120
- const { lastFrame } = render(React.createElement(DeleteWorktree, { onComplete: onComplete, onCancel: onCancel }));
120
+ const { lastFrame } = render(_jsx(DeleteWorktree, { onComplete: onComplete, onCancel: onCancel }));
121
121
  // Wait for Effect to execute
122
122
  await new Promise(resolve => setTimeout(resolve, 50));
123
123
  // THEN: Effect method should be called
@@ -144,7 +144,7 @@ describe('DeleteWorktree - Effect Integration', () => {
144
144
  const onComplete = vi.fn();
145
145
  const onCancel = vi.fn();
146
146
  // WHEN: Component is rendered
147
- const { lastFrame } = render(React.createElement(DeleteWorktree, { onComplete: onComplete, onCancel: onCancel }));
147
+ const { lastFrame } = render(_jsx(DeleteWorktree, { onComplete: onComplete, onCancel: onCancel }));
148
148
  // Wait for Effect to execute
149
149
  await new Promise(resolve => setTimeout(resolve, 50));
150
150
  // THEN: Error should be displayed
@@ -180,7 +180,7 @@ describe('DeleteWorktree - Effect Integration', () => {
180
180
  const onComplete = vi.fn();
181
181
  const onCancel = vi.fn();
182
182
  // WHEN: Component is rendered
183
- const { lastFrame } = render(React.createElement(DeleteWorktree, { onComplete: onComplete, onCancel: onCancel }));
183
+ const { lastFrame } = render(_jsx(DeleteWorktree, { onComplete: onComplete, onCancel: onCancel }));
184
184
  // Wait for Effect to execute
185
185
  await new Promise(resolve => setTimeout(resolve, 50));
186
186
  // THEN: Only non-main worktree should be shown
@@ -1,4 +1,5 @@
1
- import React, { useState, useEffect } from 'react';
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
2
3
  import { Box, Text } from 'ink';
3
4
  import { supportsUnicode } from '../utils/terminalCapabilities.js';
4
5
  const LoadingSpinner = ({ message, spinnerType, color = 'cyan', }) => {
@@ -28,10 +29,6 @@ const LoadingSpinner = ({ message, spinnerType, color = 'cyan', }) => {
28
29
  };
29
30
  }, [frames.length]);
30
31
  const currentFrame = frames[frameIndex];
31
- return (React.createElement(Box, { flexDirection: "row" },
32
- React.createElement(Text, { color: color },
33
- currentFrame,
34
- " "),
35
- React.createElement(Text, null, message)));
32
+ return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: color, children: [currentFrame, " "] }), _jsx(Text, { children: message })] }));
36
33
  };
37
34
  export default LoadingSpinner;
@@ -1,5 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
1
2
  import { describe, it, expect, vi, afterEach } from 'vitest';
2
- import React from 'react';
3
3
  import { render } from 'ink-testing-library';
4
4
  import LoadingSpinner from './LoadingSpinner.js';
5
5
  describe('LoadingSpinner', () => {
@@ -19,21 +19,21 @@ describe('LoadingSpinner', () => {
19
19
  });
20
20
  describe('Core LoadingSpinner component with Unicode animation', () => {
21
21
  it('should render with default props and display message with cyan spinner', () => {
22
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: "Loading..." }));
22
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Loading..." }));
23
23
  const output = lastFrame();
24
24
  expect(output).toContain('Loading...');
25
25
  // Should contain one of the Unicode spinner frames
26
26
  expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
27
27
  });
28
28
  it('should render with custom color prop (yellow for devcontainer operations)', () => {
29
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: "Starting devcontainer...", color: "yellow" }));
29
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Starting devcontainer...", color: "yellow" }));
30
30
  const output = lastFrame();
31
31
  expect(output).toContain('Starting devcontainer...');
32
32
  // Verify spinner is present
33
33
  expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
34
34
  });
35
35
  it('should use ASCII fallback frames when spinnerType is "line"', () => {
36
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: "Processing...", spinnerType: "line" }));
36
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Processing...", spinnerType: "line" }));
37
37
  const output = lastFrame();
38
38
  expect(output).toContain('Processing...');
39
39
  // Should contain one of the ASCII spinner frames
@@ -41,23 +41,23 @@ describe('LoadingSpinner', () => {
41
41
  });
42
42
  it('should set up animation interval with 120ms timing', () => {
43
43
  const setIntervalSpy = vi.spyOn(global, 'setInterval');
44
- render(React.createElement(LoadingSpinner, { message: "Loading..." }));
44
+ render(_jsx(LoadingSpinner, { message: "Loading..." }));
45
45
  // Verify setInterval was called with 120ms timing
46
46
  expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 120);
47
47
  });
48
48
  it('should cleanup interval on component unmount to prevent memory leaks', () => {
49
- const { unmount } = render(React.createElement(LoadingSpinner, { message: "Loading..." }));
49
+ const { unmount } = render(_jsx(LoadingSpinner, { message: "Loading..." }));
50
50
  // Verify unmount completes without errors (cleanup function runs properly)
51
51
  expect(() => unmount()).not.toThrow();
52
52
  });
53
53
  it('should preserve message text', () => {
54
54
  const message = 'Creating session...';
55
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: message }));
55
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: message }));
56
56
  // Check message is rendered
57
57
  expect(lastFrame()).toContain(message);
58
58
  });
59
59
  it('should render in flexDirection="row" layout with spinner and message', () => {
60
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: "Test message" }));
60
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Test message" }));
61
61
  const output = lastFrame();
62
62
  // Verify both spinner and message are present
63
63
  // ANSI color codes may be present between spinner and message
@@ -67,45 +67,45 @@ describe('LoadingSpinner', () => {
67
67
  });
68
68
  describe('Component variations and edge cases', () => {
69
69
  it('should accept "green" color option', () => {
70
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: "Success loading...", color: "green" }));
70
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Success loading...", color: "green" }));
71
71
  const output = lastFrame();
72
72
  expect(output).toContain('Success loading...');
73
73
  expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
74
74
  });
75
75
  it('should render Unicode frames correctly', () => {
76
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: "Test" }));
76
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Test" }));
77
77
  const output = lastFrame();
78
78
  // Should contain one of the Unicode frames
79
79
  expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
80
80
  });
81
81
  it('should render ASCII frames when using "line" spinner type', () => {
82
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: "Test", spinnerType: "line" }));
82
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Test", spinnerType: "line" }));
83
83
  const output = lastFrame();
84
84
  // Should contain one of the ASCII frames
85
85
  expect(output).toMatch(/[-\\|/]/);
86
86
  });
87
87
  it('should handle empty message string', () => {
88
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: "" }));
88
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: "" }));
89
89
  const output = lastFrame();
90
90
  // Should still render spinner even with empty message
91
91
  expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
92
92
  });
93
93
  it('should handle long message text without breaking layout', () => {
94
94
  const longMessage = 'This is a very long loading message that might wrap on narrow terminals';
95
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: longMessage }));
95
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: longMessage }));
96
96
  const output = lastFrame();
97
97
  expect(output).toContain(longMessage);
98
98
  expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
99
99
  });
100
100
  it('should use cyan as default color', () => {
101
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: "Test" }));
101
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Test" }));
102
102
  const output = lastFrame();
103
103
  // Just verify it renders successfully with default color
104
104
  expect(output).toContain('Test');
105
105
  expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
106
106
  });
107
107
  it('should use dots as default spinner type', () => {
108
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: "Test" }));
108
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Test" }));
109
109
  const output = lastFrame();
110
110
  // Default should be Unicode dots, not ASCII line
111
111
  expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
@@ -115,7 +115,7 @@ describe('LoadingSpinner', () => {
115
115
  it('should automatically detect Unicode support and use Unicode frames', () => {
116
116
  // Set up environment for Unicode support
117
117
  process.env['TERM'] = 'xterm-256color';
118
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: "Loading..." }));
118
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Loading..." }));
119
119
  const output = lastFrame();
120
120
  // Should use Unicode frames when terminal supports it
121
121
  expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
@@ -126,7 +126,7 @@ describe('LoadingSpinner', () => {
126
126
  process.env['TERM'] = 'dumb';
127
127
  delete process.env['LANG'];
128
128
  delete process.env['LC_ALL'];
129
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: "Loading..." }));
129
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Loading..." }));
130
130
  const output = lastFrame();
131
131
  // Should use ASCII frames when terminal doesn't support Unicode
132
132
  expect(output).toMatch(/[-\\|/]/);
@@ -135,7 +135,7 @@ describe('LoadingSpinner', () => {
135
135
  it('should respect explicit spinnerType prop over automatic detection', () => {
136
136
  // Even with Unicode support, explicit "line" should use ASCII
137
137
  process.env['TERM'] = 'xterm-256color';
138
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: "Loading...", spinnerType: "line" }));
138
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Loading...", spinnerType: "line" }));
139
139
  const output = lastFrame();
140
140
  // Explicit spinnerType should override detection
141
141
  expect(output).toMatch(/[-\\|/]/);
@@ -148,7 +148,7 @@ describe('LoadingSpinner', () => {
148
148
  configurable: true,
149
149
  });
150
150
  process.env['WT_SESSION'] = 'some-session-id';
151
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: "Loading..." }));
151
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Loading..." }));
152
152
  const output = lastFrame();
153
153
  // Should use Unicode on Windows Terminal
154
154
  expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
@@ -163,7 +163,7 @@ describe('LoadingSpinner', () => {
163
163
  delete process.env['WT_SESSION'];
164
164
  delete process.env['TERM'];
165
165
  delete process.env['LANG'];
166
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: "Loading..." }));
166
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Loading..." }));
167
167
  const output = lastFrame();
168
168
  // Should use ASCII on Windows without WT
169
169
  expect(output).toMatch(/[-\\|/]/);
@@ -171,7 +171,7 @@ describe('LoadingSpinner', () => {
171
171
  it('should detect Unicode support from LANG environment variable', () => {
172
172
  delete process.env['TERM'];
173
173
  process.env['LANG'] = 'en_US.UTF-8';
174
- const { lastFrame } = render(React.createElement(LoadingSpinner, { message: "Loading..." }));
174
+ const { lastFrame } = render(_jsx(LoadingSpinner, { message: "Loading..." }));
175
175
  const output = lastFrame();
176
176
  // Should use Unicode when LANG indicates UTF-8
177
177
  expect(output).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
@@ -179,7 +179,7 @@ describe('LoadingSpinner', () => {
179
179
  it('should detect Unicode support with appropriate frame rate', () => {
180
180
  process.env['TERM'] = 'xterm-256color';
181
181
  const setIntervalSpy = vi.spyOn(global, 'setInterval');
182
- render(React.createElement(LoadingSpinner, { message: "Loading..." }));
182
+ render(_jsx(LoadingSpinner, { message: "Loading..." }));
183
183
  // Verify frame rate is 120ms regardless of detection
184
184
  expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 120);
185
185
  });
@@ -11,6 +11,7 @@ interface MenuProps {
11
11
  onDismissError?: () => void;
12
12
  projectName?: string;
13
13
  multiProject?: boolean;
14
+ version: string;
14
15
  }
15
16
  declare const Menu: React.FC<MenuProps>;
16
17
  export default Menu;
@@ -1,4 +1,5 @@
1
- import React, { useState, useEffect } from 'react';
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
2
3
  import { Box, Text, useInput } from 'ink';
3
4
  import SelectInput from 'ink-select-input';
4
5
  import { Effect } from 'effect';
@@ -26,7 +27,7 @@ const createSeparatorWithText = (text, totalWidth = 35) => {
26
27
  const formatGitError = (error) => {
27
28
  return `Git command failed: ${error.command} (exit ${error.exitCode})\n${error.stderr}`;
28
29
  };
29
- const Menu = ({ sessionManager, worktreeService, onSelectWorktree, onSelectRecentProject, error, onDismissError, projectName, multiProject = false, }) => {
30
+ const Menu = ({ sessionManager, worktreeService, onSelectWorktree, onSelectRecentProject, error, onDismissError, projectName, multiProject = false, version, }) => {
30
31
  const [baseWorktrees, setBaseWorktrees] = useState([]);
31
32
  const [defaultBranch, setDefaultBranch] = useState(null);
32
33
  const [loadError, setLoadError] = useState(null);
@@ -479,45 +480,12 @@ const Menu = ({ sessionManager, worktreeService, onSelectWorktree, onSelectRecen
479
480
  onSelectWorktree(item.worktree);
480
481
  }
481
482
  };
482
- return (React.createElement(Box, { flexDirection: "column" },
483
- React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
484
- React.createElement(Text, { bold: true, color: "green" }, "CCManager - Claude Code Worktree Manager"),
485
- projectName && (React.createElement(Text, { bold: true, color: "green" }, projectName))),
486
- React.createElement(Box, { marginBottom: 1 },
487
- React.createElement(Text, { dimColor: true }, "Select a worktree to start or resume a Claude Code session:")),
488
- isSearchMode && (React.createElement(Box, { marginBottom: 1 },
489
- React.createElement(Text, null, "Search: "),
490
- React.createElement(TextInputWrapper, { value: searchQuery, onChange: setSearchQuery, focus: true, placeholder: "Type to filter worktrees..." }))),
491
- isSearchMode && items.length === 0 ? (React.createElement(Box, null,
492
- React.createElement(Text, { color: "yellow" }, "No worktrees match your search"))) : isSearchMode ? (
493
- // In search mode, show the items as a list without SelectInput
494
- React.createElement(Box, { flexDirection: "column" }, items.slice(0, limit).map((item, index) => (React.createElement(Text, { key: item.value, color: index === selectedIndex ? 'green' : undefined },
495
- index === selectedIndex ? '❯ ' : ' ',
496
- item.label))))) : (React.createElement(SelectInput, { items: items, onSelect: item => handleSelect(item), isFocused: !error, initialIndex: selectedIndex, limit: limit })),
497
- (error || loadError) && (React.createElement(Box, { marginTop: 1, paddingX: 1, borderStyle: "round", borderColor: "red" },
498
- React.createElement(Box, { flexDirection: "column" },
499
- React.createElement(Text, { color: "red", bold: true },
500
- "Error: ",
501
- error || loadError),
502
- React.createElement(Text, { color: "gray", dimColor: true }, "Press any key to dismiss")))),
503
- React.createElement(Box, { marginTop: 1, flexDirection: "column" },
504
- React.createElement(Text, { dimColor: true },
505
- "Status: ",
506
- STATUS_ICONS.BUSY,
507
- " ",
508
- STATUS_LABELS.BUSY,
509
- ' ',
510
- STATUS_ICONS.WAITING,
511
- " ",
512
- STATUS_LABELS.WAITING,
513
- " ",
514
- STATUS_ICONS.IDLE,
515
- ' ',
516
- STATUS_LABELS.IDLE),
517
- React.createElement(Text, { dimColor: true }, isSearchMode
518
- ? 'Search Mode: Type to filter, Enter to exit search, ESC to exit search'
519
- : searchQuery
520
- ? `Filtered: "${searchQuery}" | ↑↓ Navigate Enter Select | /-Search ESC-Clear 0-9 Quick Select N-New M-Merge D-Delete ${multiProject ? 'C-Config' : 'P-ProjConfig C-GlobalConfig'} ${projectName ? 'B-Back' : 'Q-Quit'}`
521
- : `Controls: ↑↓ Navigate Enter Select | Hotkeys: 0-9 Quick Select /-Search N-New M-Merge D-Delete ${multiProject ? 'C-Config' : 'P-ProjConfig C-GlobalConfig'} ${projectName ? 'B-Back' : 'Q-Quit'}`))));
483
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsxs(Text, { bold: true, color: "green", children: ["CCManager - Claude Code Worktree Manager v", version] }), projectName && (_jsx(Text, { bold: true, color: "green", children: projectName }))] }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Select a worktree to start or resume a Claude Code session:" }) }), isSearchMode && (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { children: "Search: " }), _jsx(TextInputWrapper, { value: searchQuery, onChange: setSearchQuery, focus: true, placeholder: "Type to filter worktrees..." })] })), isSearchMode && items.length === 0 ? (_jsx(Box, { children: _jsx(Text, { color: "yellow", children: "No worktrees match your search" }) })) : isSearchMode ? (
484
+ // In search mode, show the items as a list without SelectInput
485
+ _jsx(Box, { flexDirection: "column", children: items.slice(0, limit).map((item, index) => (_jsxs(Text, { color: index === selectedIndex ? 'green' : undefined, children: [index === selectedIndex ? '❯ ' : ' ', item.label] }, item.value))) })) : (_jsx(SelectInput, { items: items, onSelect: item => handleSelect(item), isFocused: !error, initialIndex: selectedIndex, limit: limit })), (error || loadError) && (_jsx(Box, { marginTop: 1, paddingX: 1, borderStyle: "round", borderColor: "red", children: _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "red", bold: true, children: ["Error: ", error || loadError] }), _jsx(Text, { color: "gray", dimColor: true, children: "Press any key to dismiss" })] }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["Status: ", STATUS_ICONS.BUSY, " ", STATUS_LABELS.BUSY, ' ', STATUS_ICONS.WAITING, " ", STATUS_LABELS.WAITING, " ", STATUS_ICONS.IDLE, ' ', STATUS_LABELS.IDLE] }), _jsx(Text, { dimColor: true, children: isSearchMode
486
+ ? 'Search Mode: Type to filter, Enter to exit search, ESC to exit search'
487
+ : searchQuery
488
+ ? `Filtered: "${searchQuery}" | ↑↓ Navigate Enter Select | /-Search ESC-Clear 0-9 Quick Select N-New M-Merge D-Delete ${multiProject ? 'C-Config' : 'P-ProjConfig C-GlobalConfig'} ${projectName ? 'B-Back' : 'Q-Quit'}`
489
+ : `Controls: ↑↓ Navigate Enter Select | Hotkeys: 0-9 Quick Select /-Search N-New M-Merge D-Delete ${multiProject ? 'C-Config' : 'P-ProjConfig C-GlobalConfig'} ${projectName ? 'B-Back' : 'Q-Quit'}` })] })] }));
522
490
  };
523
491
  export default Menu;
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
3
3
  import { render } from 'ink-testing-library';
4
4
  import { Effect } from 'effect';
@@ -106,7 +106,7 @@ describe('Menu - Recent Projects', () => {
106
106
  vi.mocked(projectManager.getRecentProjects).mockReturnValue([
107
107
  { path: '/project1', name: 'Project 1', lastAccessed: 1000 },
108
108
  ]);
109
- const { lastFrame } = render(React.createElement(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), multiProject: false }));
109
+ const { lastFrame } = render(_jsx(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), multiProject: false, version: "test" }));
110
110
  const output = lastFrame();
111
111
  expect(output).not.toContain('─ Recent ─');
112
112
  expect(output).not.toContain('Project 1');
@@ -126,7 +126,7 @@ describe('Menu - Recent Projects', () => {
126
126
  backgroundTasks: 0,
127
127
  });
128
128
  vi.spyOn(SessionManager, 'formatSessionCounts').mockReturnValue('');
129
- const { lastFrame } = render(React.createElement(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true }));
129
+ const { lastFrame } = render(_jsx(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true, version: "test" }));
130
130
  // Wait for Effect to execute
131
131
  await new Promise(resolve => setTimeout(resolve, 100));
132
132
  const output = lastFrame();
@@ -136,7 +136,7 @@ describe('Menu - Recent Projects', () => {
136
136
  });
137
137
  it('should not show recent projects section when no recent projects', () => {
138
138
  vi.mocked(projectManager.getRecentProjects).mockReturnValue([]);
139
- const { lastFrame } = render(React.createElement(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true }));
139
+ const { lastFrame } = render(_jsx(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true, version: "test" }));
140
140
  const output = lastFrame();
141
141
  expect(output).not.toContain('─ Recent ─');
142
142
  });
@@ -157,7 +157,7 @@ describe('Menu - Recent Projects', () => {
157
157
  backgroundTasks: 0,
158
158
  });
159
159
  vi.spyOn(SessionManager, 'formatSessionCounts').mockReturnValue('');
160
- const { lastFrame } = render(React.createElement(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true }));
160
+ const { lastFrame } = render(_jsx(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true, version: "test" }));
161
161
  // Wait for Effect to execute
162
162
  await new Promise(resolve => setTimeout(resolve, 100));
163
163
  const output = lastFrame();
@@ -177,9 +177,9 @@ describe('Menu - Recent Projects', () => {
177
177
  ...mockWorktreeService,
178
178
  getGitRootPath: vi.fn().mockReturnValue('/current/project'),
179
179
  };
180
- const { lastFrame, rerender } = render(React.createElement(Menu, { sessionManager: mockSessionManager, worktreeService: worktreeServiceWithGitRoot, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true }));
180
+ const { lastFrame, rerender } = render(_jsx(Menu, { sessionManager: mockSessionManager, worktreeService: worktreeServiceWithGitRoot, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true, version: "test" }));
181
181
  // Force a rerender to ensure all effects have run
182
- rerender(React.createElement(Menu, { sessionManager: mockSessionManager, worktreeService: worktreeServiceWithGitRoot, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true }));
182
+ rerender(_jsx(Menu, { sessionManager: mockSessionManager, worktreeService: worktreeServiceWithGitRoot, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true, version: "test" }));
183
183
  // Wait for the state to update and component to re-render
184
184
  await new Promise(resolve => setTimeout(resolve, 50));
185
185
  const output = lastFrame();
@@ -198,7 +198,7 @@ describe('Menu - Recent Projects', () => {
198
198
  ]);
199
199
  // Mock getGitRootPath to return the current project path
200
200
  vi.mocked(mockWorktreeService.getGitRootPath).mockReturnValue('/current/project');
201
- const { lastFrame } = render(React.createElement(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true }));
201
+ const { lastFrame } = render(_jsx(Menu, { sessionManager: mockSessionManager, worktreeService: mockWorktreeService, onSelectWorktree: vi.fn(), onSelectRecentProject: vi.fn(), multiProject: true, version: "test" }));
202
202
  const output = lastFrame();
203
203
  expect(output).not.toContain('─ Recent ─');
204
204
  expect(output).not.toContain('Current Project');
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { render } from 'ink-testing-library';
3
3
  import Menu from './Menu.js';
4
4
  import { SessionManager } from '../services/sessionManager.js';
@@ -88,7 +88,7 @@ describe('Menu component Effect-based error handling', () => {
88
88
  stdout: '',
89
89
  });
90
90
  vi.spyOn(worktreeService, 'getWorktreesEffect').mockReturnValue(Effect.fail(gitError));
91
- const { lastFrame } = render(React.createElement(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, onDismissError: onDismissError }));
91
+ const { lastFrame } = render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, onDismissError: onDismissError, version: "test" }));
92
92
  // Wait for Effect to execute
93
93
  await new Promise(resolve => setTimeout(resolve, 100));
94
94
  const output = lastFrame();
@@ -118,7 +118,7 @@ describe('Menu component Effect-based error handling', () => {
118
118
  vi.spyOn(worktreeService, 'getWorktreesEffect').mockReturnValue(Effect.succeed(mockWorktrees));
119
119
  // Mock getDefaultBranchEffect to return successful Effect
120
120
  vi.spyOn(worktreeService, 'getDefaultBranchEffect').mockReturnValue(Effect.succeed('main'));
121
- const { lastFrame } = render(React.createElement(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree }));
121
+ const { lastFrame } = render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, version: "test" }));
122
122
  // Wait for Effect to execute
123
123
  await new Promise(resolve => setTimeout(resolve, 100));
124
124
  const output = lastFrame();
@@ -149,7 +149,7 @@ describe('Menu component Effect-based error handling', () => {
149
149
  stdout: '',
150
150
  });
151
151
  vi.spyOn(worktreeService, 'getDefaultBranchEffect').mockReturnValue(Effect.fail(gitError));
152
- const { lastFrame } = render(React.createElement(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, onDismissError: onDismissError }));
152
+ const { lastFrame } = render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, onDismissError: onDismissError, version: "test" }));
153
153
  // Wait for Effect to execute
154
154
  await new Promise(resolve => setTimeout(resolve, 100));
155
155
  const output = lastFrame();
@@ -176,7 +176,7 @@ describe('Menu component Effect-based error handling', () => {
176
176
  const getDefaultBranchSpy = vi
177
177
  .spyOn(worktreeService, 'getDefaultBranchEffect')
178
178
  .mockReturnValue(Effect.succeed('main'));
179
- render(React.createElement(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree }));
179
+ render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, version: "test" }));
180
180
  // Wait for Effect to execute
181
181
  await new Promise(resolve => setTimeout(resolve, 100));
182
182
  // Verify both Effect-based methods were called (Effect composition)
@@ -205,7 +205,7 @@ describe('Menu component rendering', () => {
205
205
  it('should not render duplicate title when re-rendered with new key', async () => {
206
206
  const onSelectWorktree = vi.fn();
207
207
  // First render
208
- const { unmount, lastFrame } = render(React.createElement(Menu, { key: 1, sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree }));
208
+ const { unmount, lastFrame } = render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, version: "test" }, 1));
209
209
  // Wait for async operations
210
210
  await new Promise(resolve => setTimeout(resolve, 100));
211
211
  const firstRenderOutput = lastFrame();
@@ -215,7 +215,7 @@ describe('Menu component rendering', () => {
215
215
  expect(titleCount).toBe(1);
216
216
  // Unmount and re-render with new key
217
217
  unmount();
218
- const { lastFrame: lastFrame2 } = render(React.createElement(Menu, { key: 2, sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree }));
218
+ const { lastFrame: lastFrame2 } = render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, version: "test" }, 2));
219
219
  await new Promise(resolve => setTimeout(resolve, 100));
220
220
  const secondRenderOutput = lastFrame2();
221
221
  const titleCount2 = (secondRenderOutput?.match(/CCManager - Claude Code Worktree Manager/g) ||
@@ -224,7 +224,7 @@ describe('Menu component rendering', () => {
224
224
  });
225
225
  it('should render title and description only once', async () => {
226
226
  const onSelectWorktree = vi.fn();
227
- const { lastFrame } = render(React.createElement(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree }));
227
+ const { lastFrame } = render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, version: "test" }));
228
228
  await new Promise(resolve => setTimeout(resolve, 100));
229
229
  const output = lastFrame();
230
230
  // Check title appears only once
@@ -279,7 +279,7 @@ describe('Menu component rendering', () => {
279
279
  backgroundTasks: 0,
280
280
  });
281
281
  vi.spyOn(SessionManager, 'formatSessionCounts').mockReturnValue('');
282
- const { lastFrame } = render(React.createElement(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, onSelectRecentProject: onSelectRecentProject, multiProject: true }));
282
+ const { lastFrame } = render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, onSelectRecentProject: onSelectRecentProject, multiProject: true, version: "test" }));
283
283
  await new Promise(resolve => setTimeout(resolve, 100));
284
284
  const output = lastFrame();
285
285
  // Check that worktrees have numbers 0-2
@@ -321,7 +321,7 @@ describe('Menu component rendering', () => {
321
321
  backgroundTasks: 0,
322
322
  });
323
323
  vi.spyOn(SessionManager, 'formatSessionCounts').mockReturnValue('');
324
- const { lastFrame } = render(React.createElement(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, onSelectRecentProject: onSelectRecentProject, multiProject: true }));
324
+ const { lastFrame } = render(_jsx(Menu, { sessionManager: sessionManager, worktreeService: worktreeService, onSelectWorktree: onSelectWorktree, onSelectRecentProject: onSelectRecentProject, multiProject: true, version: "test" }));
325
325
  await new Promise(resolve => setTimeout(resolve, 100));
326
326
  const output = lastFrame();
327
327
  // Check that recent projects don't have numbers (just ❯ prefix)