ccmanager 4.1.17 → 4.1.18
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.
|
@@ -45,6 +45,8 @@ const Session = ({ session, sessionManager, onReturnToMenu, }) => {
|
|
|
45
45
|
return;
|
|
46
46
|
// Check for return to menu shortcut
|
|
47
47
|
if (shortcutManager.matchesRawInput('returnToMenu', data)) {
|
|
48
|
+
isExitingRef.current = true;
|
|
49
|
+
sessionManager.setSessionActive(session.id, false);
|
|
48
50
|
// Disable any extended input modes that might have been enabled by the PTY
|
|
49
51
|
if (stdout) {
|
|
50
52
|
resetTerminalInputModes();
|
|
@@ -72,7 +74,7 @@ const Session = ({ session, sessionManager, onReturnToMenu, }) => {
|
|
|
72
74
|
// restore and the deferred restore that may fire after Session.tsx
|
|
73
75
|
// has already disabled DECAWM for live TUI redraws.
|
|
74
76
|
const handleSessionRestore = (restoredSession, restoreSnapshot) => {
|
|
75
|
-
if (restoredSession.id === session.id) {
|
|
77
|
+
if (restoredSession.id === session.id && !isExitingRef.current) {
|
|
76
78
|
if (restoreSnapshot.length > 0) {
|
|
77
79
|
stdout.write(`\x1b[?7h${restoreSnapshot}\x1b[?7l`);
|
|
78
80
|
}
|
|
@@ -86,7 +88,9 @@ const Session = ({ session, sessionManager, onReturnToMenu, }) => {
|
|
|
86
88
|
// appends below the (already-clipped) viewport, producing duplicated
|
|
87
89
|
// rows equal to the resize delta.
|
|
88
90
|
const handleSessionResize = (resizedSession, redrawPayload) => {
|
|
89
|
-
if (resizedSession.id === session.id &&
|
|
91
|
+
if (resizedSession.id === session.id &&
|
|
92
|
+
redrawPayload.length > 0 &&
|
|
93
|
+
!isExitingRef.current) {
|
|
90
94
|
stdout.write(redrawPayload);
|
|
91
95
|
}
|
|
92
96
|
};
|
|
@@ -99,4 +99,56 @@ describe('Session', () => {
|
|
|
99
99
|
expect(testState.stdout?.write).toHaveBeenNthCalledWith(2, '\x1b[?7h\nrestored\x1b[?7l');
|
|
100
100
|
expect(testState.stdout?.write).toHaveBeenNthCalledWith(3, '\x1b[?7l');
|
|
101
101
|
});
|
|
102
|
+
it('detaches synchronously when returning to menu so late session output cannot repaint', async () => {
|
|
103
|
+
const listeners = new Map();
|
|
104
|
+
const session = {
|
|
105
|
+
id: 'session-1',
|
|
106
|
+
process: {
|
|
107
|
+
write: vi.fn(),
|
|
108
|
+
resize: vi.fn(),
|
|
109
|
+
},
|
|
110
|
+
terminal: {
|
|
111
|
+
resize: vi.fn(),
|
|
112
|
+
},
|
|
113
|
+
stateMutex: {
|
|
114
|
+
getSnapshot: () => ({ state: 'busy' }),
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
const onReturnToMenu = vi.fn();
|
|
118
|
+
const setSessionActive = vi.fn();
|
|
119
|
+
const sessionManager = {
|
|
120
|
+
on: vi.fn((event, handler) => {
|
|
121
|
+
const handlers = listeners.get(event) ?? new Set();
|
|
122
|
+
handlers.add(handler);
|
|
123
|
+
listeners.set(event, handlers);
|
|
124
|
+
return sessionManager;
|
|
125
|
+
}),
|
|
126
|
+
off: vi.fn((event, handler) => {
|
|
127
|
+
listeners.get(event)?.delete(handler);
|
|
128
|
+
return sessionManager;
|
|
129
|
+
}),
|
|
130
|
+
setSessionActive,
|
|
131
|
+
cancelAutoApproval: vi.fn(),
|
|
132
|
+
performResize: vi.fn(),
|
|
133
|
+
};
|
|
134
|
+
const { unmount } = render(_jsx(Session, { session: session, sessionManager: sessionManager, onReturnToMenu: onReturnToMenu }));
|
|
135
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
136
|
+
testState.stdout?.write.mockClear();
|
|
137
|
+
process.stdin.emit('data', '\u0005');
|
|
138
|
+
for (const handler of listeners.get('sessionData') ?? []) {
|
|
139
|
+
handler(session, 'late-data');
|
|
140
|
+
}
|
|
141
|
+
for (const handler of listeners.get('sessionRestore') ?? []) {
|
|
142
|
+
handler(session, 'late-restore');
|
|
143
|
+
}
|
|
144
|
+
for (const handler of listeners.get('sessionResize') ?? []) {
|
|
145
|
+
handler(session, 'late-resize');
|
|
146
|
+
}
|
|
147
|
+
expect(setSessionActive).toHaveBeenCalledWith(session.id, false);
|
|
148
|
+
expect(onReturnToMenu).toHaveBeenCalledTimes(1);
|
|
149
|
+
expect(testState.stdout?.write).not.toHaveBeenCalledWith('late-data');
|
|
150
|
+
expect(testState.stdout?.write).not.toHaveBeenCalledWith('\x1b[?7hlate-restore\x1b[?7l');
|
|
151
|
+
expect(testState.stdout?.write).not.toHaveBeenCalledWith('late-resize');
|
|
152
|
+
unmount();
|
|
153
|
+
});
|
|
102
154
|
});
|
|
@@ -22,6 +22,8 @@ export declare class SessionManager extends EventEmitter implements ISessionMana
|
|
|
22
22
|
private resizeSuppressTimers;
|
|
23
23
|
private restoreDeferTimers;
|
|
24
24
|
private restoreDeferDeadlines;
|
|
25
|
+
private cursorRedrawSessions;
|
|
26
|
+
private cursorRedrawTimers;
|
|
25
27
|
private spawn;
|
|
26
28
|
private resolvePreset;
|
|
27
29
|
detectTerminalState(session: Session): SessionState;
|
|
@@ -42,6 +44,9 @@ export declare class SessionManager extends EventEmitter implements ISessionMana
|
|
|
42
44
|
constructor();
|
|
43
45
|
private createTerminal;
|
|
44
46
|
private shouldResetRestoreScrollback;
|
|
47
|
+
private hasCursorAddressedRedraw;
|
|
48
|
+
private markCursorRedrawActive;
|
|
49
|
+
private handleScrollbackGrowthDuringRedraw;
|
|
45
50
|
private getRestoreSnapshot;
|
|
46
51
|
private getViewportRedrawSnapshot;
|
|
47
52
|
/**
|
|
@@ -89,6 +94,7 @@ export declare class SessionManager extends EventEmitter implements ISessionMana
|
|
|
89
94
|
private armRestoreDeferTimer;
|
|
90
95
|
private fireRestoreDefer;
|
|
91
96
|
private cancelRestoreDefer;
|
|
97
|
+
private clearCursorRedrawTracking;
|
|
92
98
|
cancelAutoApproval(sessionId: string, reason?: string): void;
|
|
93
99
|
toggleAutoApprovalForWorktree(worktreePath: string): boolean;
|
|
94
100
|
isAutoApprovalDisabledForWorktree(worktreePath: string): boolean;
|
|
@@ -20,7 +20,7 @@ import { preparePresetLaunch } from '../utils/presetPrompt.js';
|
|
|
20
20
|
const { Terminal } = pkg;
|
|
21
21
|
const TERMINAL_CONTENT_MAX_LINES = 300;
|
|
22
22
|
const TERMINAL_SCROLLBACK_LINES = 5000;
|
|
23
|
-
const TERMINAL_RESTORE_SCROLLBACK_LINES =
|
|
23
|
+
const TERMINAL_RESTORE_SCROLLBACK_LINES = TERMINAL_SCROLLBACK_LINES;
|
|
24
24
|
// How long to suppress PTY → stdout forwarding after a viewport resize so the
|
|
25
25
|
// child process's SIGWINCH-triggered re-emission of static content does not
|
|
26
26
|
// duplicate already-displayed rows in the user's terminal.
|
|
@@ -33,6 +33,11 @@ const RESIZE_SUPPRESS_MS = 250;
|
|
|
33
33
|
// streaming session still restores within a small bounded delay.
|
|
34
34
|
const RESTORE_DEFER_QUIET_MS = 80;
|
|
35
35
|
const RESTORE_DEFER_MAX_MS = 250;
|
|
36
|
+
// Cursor-addressed redraws (progress bars, spinners, TUIs) can leave transient
|
|
37
|
+
// rows in scrollback if the viewport scrolls before those rows are overwritten.
|
|
38
|
+
// Keep a short generic redraw window so restore can skip that ghost-bearing
|
|
39
|
+
// range without depending on any specific CLI output text.
|
|
40
|
+
const REDRAW_SCROLLBACK_QUIET_MS = 500;
|
|
36
41
|
export class SessionManager extends EventEmitter {
|
|
37
42
|
sessions;
|
|
38
43
|
waitingWithBottomBorder = new Map();
|
|
@@ -44,6 +49,8 @@ export class SessionManager extends EventEmitter {
|
|
|
44
49
|
resizeSuppressTimers = new Map();
|
|
45
50
|
restoreDeferTimers = new Map();
|
|
46
51
|
restoreDeferDeadlines = new Map();
|
|
52
|
+
cursorRedrawSessions = new Set();
|
|
53
|
+
cursorRedrawTimers = new Map();
|
|
47
54
|
async spawn(command, args, worktreePath, options = {}) {
|
|
48
55
|
const spawnOptions = {
|
|
49
56
|
name: 'xterm-256color',
|
|
@@ -231,6 +238,35 @@ export class SessionManager extends EventEmitter {
|
|
|
231
238
|
data.includes('\x1b[3J') ||
|
|
232
239
|
data.includes('\x1bc'));
|
|
233
240
|
}
|
|
241
|
+
hasCursorAddressedRedraw(data) {
|
|
242
|
+
return (
|
|
243
|
+
// CSI cursor movement/positioning, erase, and scroll controls.
|
|
244
|
+
/\x1b\[[0-?]*[ -/]*[ABCDEFGHJKSTX`abcdefg]/.test(data) ||
|
|
245
|
+
// DEC save/restore cursor.
|
|
246
|
+
/\x1b[78]/.test(data) ||
|
|
247
|
+
// Synchronized output mode usually brackets full-frame redraws.
|
|
248
|
+
/\x1b\[\?2026[hl]/.test(data));
|
|
249
|
+
}
|
|
250
|
+
markCursorRedrawActive(session) {
|
|
251
|
+
this.cursorRedrawSessions.add(session.id);
|
|
252
|
+
const existing = this.cursorRedrawTimers.get(session.id);
|
|
253
|
+
if (existing !== undefined) {
|
|
254
|
+
clearTimeout(existing);
|
|
255
|
+
}
|
|
256
|
+
const timer = setTimeout(() => {
|
|
257
|
+
this.cursorRedrawTimers.delete(session.id);
|
|
258
|
+
this.cursorRedrawSessions.delete(session.id);
|
|
259
|
+
}, REDRAW_SCROLLBACK_QUIET_MS);
|
|
260
|
+
this.cursorRedrawTimers.set(session.id, timer);
|
|
261
|
+
}
|
|
262
|
+
handleScrollbackGrowthDuringRedraw(session, beforeBaseY) {
|
|
263
|
+
const afterBaseY = session.terminal.buffer.normal.baseY;
|
|
264
|
+
if (afterBaseY <= beforeBaseY ||
|
|
265
|
+
!this.cursorRedrawSessions.has(session.id)) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
session.restoreScrollbackBaseLine = Math.max(session.restoreScrollbackBaseLine, afterBaseY);
|
|
269
|
+
}
|
|
234
270
|
getRestoreSnapshot(session) {
|
|
235
271
|
const activeBuffer = session.terminal.buffer.active;
|
|
236
272
|
if (activeBuffer.type !== 'normal') {
|
|
@@ -245,19 +281,6 @@ export class SessionManager extends EventEmitter {
|
|
|
245
281
|
}
|
|
246
282
|
const cursorRow = normalBuffer.cursorY + 1;
|
|
247
283
|
const cursorCol = normalBuffer.cursorX + 1;
|
|
248
|
-
// When the live viewport shows a transient footer (spinner activity,
|
|
249
|
-
// token stats, persistent shift+tab footer, etc.), the renderer keeps
|
|
250
|
-
// redrawing it in place and earlier copies have likely been pushed into
|
|
251
|
-
// scrollback by chat output scrolling beneath it. Replaying that
|
|
252
|
-
// scrollback would paint duplicated footer rows, so emit only the
|
|
253
|
-
// viewport in this case.
|
|
254
|
-
if (session.stateDetector.hasTransientRenderFooter(session.terminal)) {
|
|
255
|
-
const viewportSnapshot = session.serializer.serialize({
|
|
256
|
-
scrollback: 0,
|
|
257
|
-
excludeAltBuffer: true,
|
|
258
|
-
});
|
|
259
|
-
return `${viewportSnapshot}\x1b[${cursorRow};${cursorCol}H`;
|
|
260
|
-
}
|
|
261
284
|
const scrollbackStart = Math.max(0, normalBuffer.baseY - TERMINAL_RESTORE_SCROLLBACK_LINES);
|
|
262
285
|
const rangeStart = Math.max(session.restoreScrollbackBaseLine, scrollbackStart);
|
|
263
286
|
const rangeEnd = bufferLength - 1;
|
|
@@ -434,8 +457,13 @@ export class SessionManager extends EventEmitter {
|
|
|
434
457
|
setupDataHandler(session) {
|
|
435
458
|
// This handler always runs for all data
|
|
436
459
|
session.process.onData((data) => {
|
|
460
|
+
const beforeBaseY = session.terminal.buffer.normal.baseY;
|
|
461
|
+
if (this.hasCursorAddressedRedraw(data)) {
|
|
462
|
+
this.markCursorRedrawActive(session);
|
|
463
|
+
}
|
|
437
464
|
// Write data to virtual terminal
|
|
438
465
|
session.terminal.write(data);
|
|
466
|
+
this.handleScrollbackGrowthDuringRedraw(session, beforeBaseY);
|
|
439
467
|
if (this.shouldResetRestoreScrollback(data)) {
|
|
440
468
|
session.restoreScrollbackBaseLine =
|
|
441
469
|
session.terminal.buffer.normal.baseY;
|
|
@@ -678,6 +706,14 @@ export class SessionManager extends EventEmitter {
|
|
|
678
706
|
}
|
|
679
707
|
this.restoreDeferDeadlines.delete(sessionId);
|
|
680
708
|
}
|
|
709
|
+
clearCursorRedrawTracking(sessionId) {
|
|
710
|
+
this.cursorRedrawSessions.delete(sessionId);
|
|
711
|
+
const timer = this.cursorRedrawTimers.get(sessionId);
|
|
712
|
+
if (timer !== undefined) {
|
|
713
|
+
clearTimeout(timer);
|
|
714
|
+
this.cursorRedrawTimers.delete(sessionId);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
681
717
|
cancelAutoApproval(sessionId, reason = 'User input received') {
|
|
682
718
|
const session = this.sessions.get(sessionId);
|
|
683
719
|
if (!session) {
|
|
@@ -755,6 +791,7 @@ export class SessionManager extends EventEmitter {
|
|
|
755
791
|
clearTimeout(resizeTimer);
|
|
756
792
|
this.resizeSuppressTimers.delete(sessionId);
|
|
757
793
|
}
|
|
794
|
+
this.clearCursorRedrawTracking(sessionId);
|
|
758
795
|
this.cancelRestoreDefer(sessionId);
|
|
759
796
|
this.emit('sessionDestroyed', session);
|
|
760
797
|
}
|
|
@@ -815,6 +852,7 @@ export class SessionManager extends EventEmitter {
|
|
|
815
852
|
clearTimeout(resizeTimer);
|
|
816
853
|
this.resizeSuppressTimers.delete(sessionId);
|
|
817
854
|
}
|
|
855
|
+
this.clearCursorRedrawTracking(sessionId);
|
|
818
856
|
this.cancelRestoreDefer(sessionId);
|
|
819
857
|
this.emit('sessionDestroyed', session);
|
|
820
858
|
},
|
|
@@ -774,7 +774,7 @@ describe('SessionManager', () => {
|
|
|
774
774
|
});
|
|
775
775
|
});
|
|
776
776
|
describe('session restore snapshots', () => {
|
|
777
|
-
it('should emit a
|
|
777
|
+
it('should emit a normal-buffer restore snapshot from the restore baseline and restore the cursor position', async () => {
|
|
778
778
|
vi.mocked(configReader.getDefaultPreset).mockReturnValue({
|
|
779
779
|
id: '1',
|
|
780
780
|
name: 'Main',
|
|
@@ -803,6 +803,32 @@ describe('SessionManager', () => {
|
|
|
803
803
|
});
|
|
804
804
|
expect(restoreHandler).toHaveBeenCalledWith(session, '\u001b[31mrestored\u001b[0m\u001b[8;12H');
|
|
805
805
|
});
|
|
806
|
+
it('should restore all retained normal-buffer scrollback when no baseline excludes it', async () => {
|
|
807
|
+
vi.mocked(configReader.getDefaultPreset).mockReturnValue({
|
|
808
|
+
id: '1',
|
|
809
|
+
name: 'Main',
|
|
810
|
+
command: 'claude',
|
|
811
|
+
});
|
|
812
|
+
vi.mocked(spawn).mockReturnValue(mockPty);
|
|
813
|
+
const session = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'));
|
|
814
|
+
const normalBuffer = session.terminal.buffer.normal;
|
|
815
|
+
normalBuffer.baseY = 260;
|
|
816
|
+
normalBuffer.length = 300;
|
|
817
|
+
normalBuffer.cursorY = 7;
|
|
818
|
+
normalBuffer.cursorX = 11;
|
|
819
|
+
session.restoreScrollbackBaseLine = 0;
|
|
820
|
+
const serializeMock = vi
|
|
821
|
+
.spyOn(session.serializer, 'serialize')
|
|
822
|
+
.mockReturnValue('\u001b[31mrestored\u001b[0m');
|
|
823
|
+
sessionManager.setSessionActive(session.id, true);
|
|
824
|
+
expect(serializeMock).toHaveBeenCalledWith({
|
|
825
|
+
range: {
|
|
826
|
+
start: 0,
|
|
827
|
+
end: 299,
|
|
828
|
+
},
|
|
829
|
+
excludeAltBuffer: true,
|
|
830
|
+
});
|
|
831
|
+
});
|
|
806
832
|
it('should keep viewport-only restore behavior for alternate screen sessions', async () => {
|
|
807
833
|
vi.mocked(configReader.getDefaultPreset).mockReturnValue({
|
|
808
834
|
id: '1',
|
|
@@ -847,6 +873,69 @@ describe('SessionManager', () => {
|
|
|
847
873
|
mockPty.emit('data', '\x1b[2J\x1b[Hfresh');
|
|
848
874
|
expect(session.restoreScrollbackBaseLine).toBe(17);
|
|
849
875
|
});
|
|
876
|
+
it('should keep full scrollback for normal output that scrolls', async () => {
|
|
877
|
+
vi.mocked(configReader.getDefaultPreset).mockReturnValue({
|
|
878
|
+
id: '1',
|
|
879
|
+
name: 'Main',
|
|
880
|
+
command: 'claude',
|
|
881
|
+
});
|
|
882
|
+
vi.mocked(spawn).mockReturnValue(mockPty);
|
|
883
|
+
const session = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'));
|
|
884
|
+
const normalBuffer = session.terminal.buffer.normal;
|
|
885
|
+
normalBuffer.baseY = 10;
|
|
886
|
+
vi.mocked(session.terminal.write).mockImplementation(() => {
|
|
887
|
+
normalBuffer.baseY = 12;
|
|
888
|
+
});
|
|
889
|
+
mockPty.emit('data', 'ordinary output\n');
|
|
890
|
+
expect(session.restoreScrollbackBaseLine).toBe(0);
|
|
891
|
+
});
|
|
892
|
+
it('should skip scrollback that grows during cursor-addressed redraws', async () => {
|
|
893
|
+
vi.mocked(configReader.getDefaultPreset).mockReturnValue({
|
|
894
|
+
id: '1',
|
|
895
|
+
name: 'Main',
|
|
896
|
+
command: 'claude',
|
|
897
|
+
});
|
|
898
|
+
vi.mocked(spawn).mockReturnValue(mockPty);
|
|
899
|
+
const session = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'));
|
|
900
|
+
const normalBuffer = session.terminal.buffer.normal;
|
|
901
|
+
normalBuffer.baseY = 10;
|
|
902
|
+
vi.mocked(session.terminal.write).mockImplementation(() => {
|
|
903
|
+
normalBuffer.baseY = 12;
|
|
904
|
+
});
|
|
905
|
+
mockPty.emit('data', '\x1b[Hredrawn status\n');
|
|
906
|
+
expect(session.restoreScrollbackBaseLine).toBe(12);
|
|
907
|
+
});
|
|
908
|
+
it('should keep cursor-addressed redraw tracking active for a short quiet window', async () => {
|
|
909
|
+
vi.useFakeTimers();
|
|
910
|
+
try {
|
|
911
|
+
vi.mocked(configReader.getDefaultPreset).mockReturnValue({
|
|
912
|
+
id: '1',
|
|
913
|
+
name: 'Main',
|
|
914
|
+
command: 'claude',
|
|
915
|
+
});
|
|
916
|
+
vi.mocked(spawn).mockReturnValue(mockPty);
|
|
917
|
+
const session = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'));
|
|
918
|
+
const normalBuffer = session.terminal.buffer.normal;
|
|
919
|
+
normalBuffer.baseY = 10;
|
|
920
|
+
vi.mocked(session.terminal.write).mockImplementation(() => {
|
|
921
|
+
if (normalBuffer.baseY === 10) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
normalBuffer.baseY++;
|
|
925
|
+
});
|
|
926
|
+
mockPty.emit('data', '\x1b[Hredraw frame');
|
|
927
|
+
normalBuffer.baseY = 11;
|
|
928
|
+
mockPty.emit('data', 'plain continuation that scrolls');
|
|
929
|
+
expect(session.restoreScrollbackBaseLine).toBe(12);
|
|
930
|
+
vi.advanceTimersByTime(501);
|
|
931
|
+
normalBuffer.baseY = 20;
|
|
932
|
+
mockPty.emit('data', 'ordinary output after quiet window');
|
|
933
|
+
expect(session.restoreScrollbackBaseLine).toBe(12);
|
|
934
|
+
}
|
|
935
|
+
finally {
|
|
936
|
+
vi.useRealTimers();
|
|
937
|
+
}
|
|
938
|
+
});
|
|
850
939
|
it('should flush live session data after the restore snapshot completes', async () => {
|
|
851
940
|
vi.mocked(configReader.getDefaultPreset).mockReturnValue({
|
|
852
941
|
id: '1',
|
|
@@ -872,7 +961,7 @@ describe('SessionManager', () => {
|
|
|
872
961
|
sessionManager.setSessionActive(session.id, true);
|
|
873
962
|
expect(eventOrder).toEqual(['restore', 'data']);
|
|
874
963
|
});
|
|
875
|
-
it('should defer the
|
|
964
|
+
it('should defer the scrollback restore until PTY output is quiet when a transient footer is visible', async () => {
|
|
876
965
|
vi.useFakeTimers();
|
|
877
966
|
try {
|
|
878
967
|
vi.mocked(configReader.getDefaultPreset).mockReturnValue({
|
|
@@ -899,10 +988,13 @@ describe('SessionManager', () => {
|
|
|
899
988
|
expect(serializeMock).not.toHaveBeenCalled();
|
|
900
989
|
vi.advanceTimersByTime(80);
|
|
901
990
|
expect(serializeMock).toHaveBeenCalledWith({
|
|
902
|
-
|
|
991
|
+
range: {
|
|
992
|
+
start: 120,
|
|
993
|
+
end: 299,
|
|
994
|
+
},
|
|
903
995
|
excludeAltBuffer: true,
|
|
904
996
|
});
|
|
905
|
-
expect(serializeMock).not.toHaveBeenCalledWith(expect.objectContaining({
|
|
997
|
+
expect(serializeMock).not.toHaveBeenCalledWith(expect.objectContaining({ scrollback: 0 }));
|
|
906
998
|
expect(restoreHandler).toHaveBeenCalledWith(session, '[31mviewport[0m[8;12H');
|
|
907
999
|
}
|
|
908
1000
|
finally {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccmanager",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.18",
|
|
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": "4.1.
|
|
45
|
-
"@kodaikabasawa/ccmanager-darwin-x64": "4.1.
|
|
46
|
-
"@kodaikabasawa/ccmanager-linux-arm64": "4.1.
|
|
47
|
-
"@kodaikabasawa/ccmanager-linux-x64": "4.1.
|
|
48
|
-
"@kodaikabasawa/ccmanager-win32-x64": "4.1.
|
|
44
|
+
"@kodaikabasawa/ccmanager-darwin-arm64": "4.1.18",
|
|
45
|
+
"@kodaikabasawa/ccmanager-darwin-x64": "4.1.18",
|
|
46
|
+
"@kodaikabasawa/ccmanager-linux-arm64": "4.1.18",
|
|
47
|
+
"@kodaikabasawa/ccmanager-linux-x64": "4.1.18",
|
|
48
|
+
"@kodaikabasawa/ccmanager-win32-x64": "4.1.18"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@eslint/js": "^9.28.0",
|