ccmanager 3.6.9 → 3.6.11

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.
@@ -18,6 +18,7 @@ const ConfigureOther = ({ onComplete }) => {
18
18
  const [customCommandDraft, setCustomCommandDraft] = useState(customCommand);
19
19
  const [timeout, setTimeout] = useState(autoApprovalConfig.timeout ?? 30);
20
20
  const [timeoutDraft, setTimeoutDraft] = useState(timeout);
21
+ const [clearHistoryOnClear, setClearHistoryOnClear] = useState(autoApprovalConfig.clearHistoryOnClear ?? false);
21
22
  // Show if inheriting from global (for project scope)
22
23
  const isInheriting = scope === 'project' && !configEditor.hasProjectOverride('autoApproval');
23
24
  useInput((input, key) => {
@@ -48,6 +49,10 @@ const ConfigureOther = ({ onComplete }) => {
48
49
  label: `⏱️ Set Timeout (${timeout}s)`,
49
50
  value: 'timeout',
50
51
  },
52
+ {
53
+ label: `Clear History on Screen Clear: ${clearHistoryOnClear ? '✅ Enabled' : '❌ Disabled'}`,
54
+ value: 'toggleClearHistory',
55
+ },
51
56
  {
52
57
  label: '💾 Save Changes',
53
58
  value: 'save',
@@ -70,11 +75,15 @@ const ConfigureOther = ({ onComplete }) => {
70
75
  setTimeoutDraft(timeout);
71
76
  setView('timeout');
72
77
  break;
78
+ case 'toggleClearHistory':
79
+ setClearHistoryOnClear(!clearHistoryOnClear);
80
+ break;
73
81
  case 'save':
74
82
  configEditor.setAutoApprovalConfig({
75
83
  enabled: autoApprovalEnabled,
76
84
  customCommand: customCommand.trim() || undefined,
77
85
  timeout,
86
+ clearHistoryOnClear,
78
87
  });
79
88
  onComplete();
80
89
  break;
@@ -104,6 +113,6 @@ const ConfigureOther = ({ onComplete }) => {
104
113
  } }));
105
114
  }
106
115
  const scopeLabel = scope === 'project' ? 'Project' : 'Global';
107
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, color: "green", children: ["Other & Experimental Settings (", scopeLabel, ")"] }) }), isInheriting && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { backgroundColor: "cyan", color: "black", children: [' ', "\uD83D\uDCCB Inheriting from global configuration", ' '] }) })), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Toggle experimental capabilities and other miscellaneous options." }) }), _jsx(CustomCommandSummary, { command: customCommand }), _jsx(SelectInput, { items: menuItems, onSelect: handleSelect, isFocused: true }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Press ", shortcutManager.getShortcutDisplay('cancel'), " to return without saving"] }) })] }));
116
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, color: "green", children: ["Other & Experimental Settings (", scopeLabel, ")"] }) }), isInheriting && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { backgroundColor: "cyan", color: "black", children: [' ', "\uD83D\uDCCB Inheriting from global configuration", ' '] }) })), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Toggle experimental capabilities and other miscellaneous options." }) }), _jsx(CustomCommandSummary, { command: customCommand }), clearHistoryOnClear && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Clear History: When enabled, session output history is cleared when a screen clear escape sequence is detected (e.g., /clear command). This prevents excessive scrolling during session restoration." }) })), _jsx(SelectInput, { items: menuItems, onSelect: handleSelect, isFocused: true }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Press ", shortcutManager.getShortcutDisplay('cancel'), " to return without saving"] }) })] }));
108
117
  };
109
118
  export default ConfigureOther;
@@ -37,22 +37,11 @@ const Menu = ({ sessionManager, worktreeService, onSelectWorktree, onSelectRecen
37
37
  const [recentProjects, setRecentProjects] = useState([]);
38
38
  const { stdout } = useStdout();
39
39
  const fixedRows = 6;
40
- const [terminalRows, setTerminalRows] = useState(stdout.rows);
41
- // Update terminal rows on resize
42
- useEffect(() => {
43
- const handleResize = () => {
44
- setTerminalRows(stdout.rows);
45
- };
46
- stdout.on('resize', handleResize);
47
- return () => {
48
- stdout.off('resize', handleResize);
49
- };
50
- }, [stdout]);
51
40
  // Use the search mode hook
52
41
  const { isSearchMode, searchQuery, selectedIndex, setSearchQuery } = useSearchMode(items.length, {
53
42
  isDisabled: !!error || !!loadError,
54
43
  });
55
- const limit = Math.max(5, terminalRows -
44
+ const limit = Math.max(5, stdout.rows -
56
45
  fixedRows -
57
46
  (isSearchMode ? 1 : 0) -
58
47
  (error || loadError ? 3 : 0));
@@ -17,6 +17,7 @@ export declare class ConfigReader implements IConfigReader {
17
17
  getConfiguration(): ConfigurationData;
18
18
  getAutoApprovalConfig(): NonNullable<ConfigurationData['autoApproval']>;
19
19
  isAutoApprovalEnabled(): boolean;
20
+ isClearHistoryOnClearEnabled(): boolean;
20
21
  getDefaultPreset(): CommandPreset;
21
22
  getSelectPresetOnStart(): boolean;
22
23
  getPresetByIdEffect(id: string): Either.Either<CommandPreset, ValidationError>;
@@ -86,6 +86,10 @@ export class ConfigReader {
86
86
  isAutoApprovalEnabled() {
87
87
  return this.getAutoApprovalConfig().enabled;
88
88
  }
89
+ // Check if clear history on clear is enabled
90
+ isClearHistoryOnClearEnabled() {
91
+ return this.getAutoApprovalConfig().clearHistoryOnClear ?? false;
92
+ }
89
93
  // Command Preset methods - delegate to global config for modifications
90
94
  getDefaultPreset() {
91
95
  const presets = this.getCommandPresets();
@@ -76,6 +76,7 @@ class GlobalConfigManager {
76
76
  this.config.autoApproval = {
77
77
  enabled: false,
78
78
  timeout: 30,
79
+ clearHistoryOnClear: false,
79
80
  };
80
81
  }
81
82
  else {
@@ -85,6 +86,9 @@ class GlobalConfigManager {
85
86
  if (!Object.prototype.hasOwnProperty.call(this.config.autoApproval, 'timeout')) {
86
87
  this.config.autoApproval.timeout = 30;
87
88
  }
89
+ if (!Object.prototype.hasOwnProperty.call(this.config.autoApproval, 'clearHistoryOnClear')) {
90
+ this.config.autoApproval.clearHistoryOnClear = false;
91
+ }
88
92
  }
89
93
  // Migrate legacy command config to presets if needed
90
94
  this.ensureDefaultPresets();
@@ -277,6 +277,13 @@ export class SessionManager extends EventEmitter {
277
277
  session.process.onData((data) => {
278
278
  // Write data to virtual terminal
279
279
  session.terminal.write(data);
280
+ // Check for screen clear escape sequence (e.g., from /clear command)
281
+ // When enabled and detected, clear the output history to prevent replaying old content on restore
282
+ // This helps avoid excessive scrolling when restoring sessions with large output history
283
+ if (configReader.isClearHistoryOnClearEnabled() &&
284
+ data.includes('\x1B[2J')) {
285
+ session.outputHistory = [];
286
+ }
280
287
  // Store in output history as Buffer
281
288
  const buffer = Buffer.from(data, 'utf8');
282
289
  session.outputHistory.push(buffer);
@@ -41,6 +41,7 @@ vi.mock('./config/configReader.js', () => ({
41
41
  getWorktreeLastOpened: vi.fn(() => ({})),
42
42
  isAutoApprovalEnabled: vi.fn(() => false),
43
43
  setAutoApprovalEnabled: vi.fn(),
44
+ isClearHistoryOnClearEnabled: vi.fn(() => false),
44
45
  },
45
46
  }));
46
47
  describe('SessionManager - State Persistence', () => {
@@ -30,6 +30,7 @@ vi.mock('./config/configReader.js', () => ({
30
30
  getWorktreeLastOpened: vi.fn(() => ({})),
31
31
  isAutoApprovalEnabled: vi.fn(() => false),
32
32
  setAutoApprovalEnabled: vi.fn(),
33
+ isClearHistoryOnClearEnabled: vi.fn(() => false),
33
34
  },
34
35
  }));
35
36
  // Mock Terminal
@@ -694,6 +695,89 @@ describe('SessionManager', () => {
694
695
  expect(session.isPrimaryCommand).toBe(false);
695
696
  });
696
697
  });
698
+ describe('clearHistoryOnClear', () => {
699
+ it('should clear output history when screen clear escape sequence is detected and setting is enabled', async () => {
700
+ // Setup
701
+ vi.mocked(configReader.getDefaultPreset).mockReturnValue({
702
+ id: '1',
703
+ name: 'Main',
704
+ command: 'claude',
705
+ });
706
+ vi.mocked(configReader.isClearHistoryOnClearEnabled).mockReturnValue(true);
707
+ vi.mocked(spawn).mockReturnValue(mockPty);
708
+ // Create session
709
+ const session = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'));
710
+ // Simulate some data output
711
+ mockPty.emit('data', 'Hello World');
712
+ mockPty.emit('data', 'More data');
713
+ // Verify output history has data
714
+ expect(session.outputHistory.length).toBe(2);
715
+ // Simulate screen clear escape sequence
716
+ mockPty.emit('data', '\x1B[2J');
717
+ // Verify output history was cleared and only contains the clear sequence
718
+ expect(session.outputHistory.length).toBe(1);
719
+ expect(session.outputHistory[0]?.toString()).toBe('\x1B[2J');
720
+ });
721
+ it('should not clear output history when screen clear escape sequence is detected but setting is disabled', async () => {
722
+ // Setup
723
+ vi.mocked(configReader.getDefaultPreset).mockReturnValue({
724
+ id: '1',
725
+ name: 'Main',
726
+ command: 'claude',
727
+ });
728
+ vi.mocked(configReader.isClearHistoryOnClearEnabled).mockReturnValue(false);
729
+ vi.mocked(spawn).mockReturnValue(mockPty);
730
+ // Create session
731
+ const session = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'));
732
+ // Simulate some data output
733
+ mockPty.emit('data', 'Hello World');
734
+ mockPty.emit('data', 'More data');
735
+ // Verify output history has data
736
+ expect(session.outputHistory.length).toBe(2);
737
+ // Simulate screen clear escape sequence
738
+ mockPty.emit('data', '\x1B[2J');
739
+ // Verify output history was NOT cleared
740
+ expect(session.outputHistory.length).toBe(3);
741
+ });
742
+ it('should not clear output history for normal data when setting is enabled', async () => {
743
+ // Setup
744
+ vi.mocked(configReader.getDefaultPreset).mockReturnValue({
745
+ id: '1',
746
+ name: 'Main',
747
+ command: 'claude',
748
+ });
749
+ vi.mocked(configReader.isClearHistoryOnClearEnabled).mockReturnValue(true);
750
+ vi.mocked(spawn).mockReturnValue(mockPty);
751
+ // Create session
752
+ const session = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'));
753
+ // Simulate normal data output without screen clear
754
+ mockPty.emit('data', 'Hello World');
755
+ mockPty.emit('data', 'More data');
756
+ mockPty.emit('data', 'Even more data');
757
+ // Verify output history contains all data
758
+ expect(session.outputHistory.length).toBe(3);
759
+ });
760
+ it('should clear history when screen clear is part of larger data chunk', async () => {
761
+ // Setup
762
+ vi.mocked(configReader.getDefaultPreset).mockReturnValue({
763
+ id: '1',
764
+ name: 'Main',
765
+ command: 'claude',
766
+ });
767
+ vi.mocked(configReader.isClearHistoryOnClearEnabled).mockReturnValue(true);
768
+ vi.mocked(spawn).mockReturnValue(mockPty);
769
+ // Create session
770
+ const session = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'));
771
+ // Simulate some data output
772
+ mockPty.emit('data', 'Hello World');
773
+ mockPty.emit('data', 'More data');
774
+ // Simulate screen clear as part of larger data chunk (e.g., from /clear command)
775
+ mockPty.emit('data', 'prefix\x1B[2Jsuffix');
776
+ // Verify output history was cleared and only contains the new chunk
777
+ expect(session.outputHistory.length).toBe(1);
778
+ expect(session.outputHistory[0]?.toString()).toBe('prefix\x1B[2Jsuffix');
779
+ });
780
+ });
697
781
  describe('static methods', () => {
698
782
  describe('getSessionCounts', () => {
699
783
  // Helper to create mock session with stateMutex
@@ -113,6 +113,7 @@ export interface ConfigurationData {
113
113
  enabled: boolean;
114
114
  customCommand?: string;
115
115
  timeout?: number;
116
+ clearHistoryOnClear?: boolean;
116
117
  };
117
118
  }
118
119
  export type ConfigScope = 'project' | 'global';
@@ -120,6 +121,7 @@ export interface AutoApprovalConfig {
120
121
  enabled: boolean;
121
122
  customCommand?: string;
122
123
  timeout?: number;
124
+ clearHistoryOnClear?: boolean;
123
125
  }
124
126
  export interface ProjectConfigurationData {
125
127
  shortcuts?: ShortcutConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccmanager",
3
- "version": "3.6.9",
3
+ "version": "3.6.11",
4
4
  "description": "TUI application for managing multiple Claude Code sessions across Git worktrees",
5
5
  "license": "MIT",
6
6
  "author": "Kodai Kabasawa",
@@ -41,11 +41,11 @@
41
41
  "bin"
42
42
  ],
43
43
  "optionalDependencies": {
44
- "@kodaikabasawa/ccmanager-darwin-arm64": "3.6.9",
45
- "@kodaikabasawa/ccmanager-darwin-x64": "3.6.9",
46
- "@kodaikabasawa/ccmanager-linux-arm64": "3.6.9",
47
- "@kodaikabasawa/ccmanager-linux-x64": "3.6.9",
48
- "@kodaikabasawa/ccmanager-win32-x64": "3.6.9"
44
+ "@kodaikabasawa/ccmanager-darwin-arm64": "3.6.11",
45
+ "@kodaikabasawa/ccmanager-darwin-x64": "3.6.11",
46
+ "@kodaikabasawa/ccmanager-linux-arm64": "3.6.11",
47
+ "@kodaikabasawa/ccmanager-linux-x64": "3.6.11",
48
+ "@kodaikabasawa/ccmanager-win32-x64": "3.6.11"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@eslint/js": "^9.28.0",