jupyterlab_claude_code_extension 1.1.23 → 1.1.25
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.
- package/lib/widget.js +36 -1
- package/package.json +1 -1
- package/src/__tests__/jupyterlab_claude_code_extension.spec.ts +38 -0
- package/src/widget.ts +47 -1
- package/style/base.css +18 -0
package/lib/widget.js
CHANGED
|
@@ -335,17 +335,52 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
async _cleanupParallel(session) {
|
|
338
|
+
const body = new Widget();
|
|
339
|
+
body.node.className = 'jp-ClaudeSessionsPanel-cleanupBody';
|
|
340
|
+
const message = document.createElement('div');
|
|
341
|
+
message.className = 'jp-ClaudeSessionsPanel-cleanupMessage';
|
|
342
|
+
const count = session.extra_sessions;
|
|
343
|
+
message.textContent = `Removing ${count} parallel session${count === 1 ? '' : 's'}...`;
|
|
344
|
+
body.node.appendChild(message);
|
|
345
|
+
// No `value` attribute -> indeterminate (animated) while the request is
|
|
346
|
+
// in flight; set to max on completion so the bar reads as finished.
|
|
347
|
+
const bar = document.createElement('progress');
|
|
348
|
+
bar.className = 'jp-ClaudeSessionsPanel-cleanupProgress';
|
|
349
|
+
bar.max = 1;
|
|
350
|
+
body.node.appendChild(bar);
|
|
351
|
+
const dialog = new Dialog({
|
|
352
|
+
title: 'Clean Up Parallel Sessions',
|
|
353
|
+
body,
|
|
354
|
+
buttons: [Dialog.okButton({ label: 'Close' })]
|
|
355
|
+
});
|
|
356
|
+
// Hide the Close button while work is in progress; restore it once the
|
|
357
|
+
// outcome (success or error) is shown so the user dismisses the popup.
|
|
358
|
+
const footer = dialog.node.querySelector('.jp-Dialog-footer');
|
|
359
|
+
if (footer) {
|
|
360
|
+
footer.style.display = 'none';
|
|
361
|
+
}
|
|
362
|
+
void dialog.launch();
|
|
338
363
|
try {
|
|
339
|
-
await requestAPI('sessions/cleanup', this._serverSettings, {
|
|
364
|
+
const data = await requestAPI('sessions/cleanup', this._serverSettings, {
|
|
340
365
|
method: 'POST',
|
|
341
366
|
body: JSON.stringify({ encoded_path: session.encoded_path })
|
|
342
367
|
});
|
|
368
|
+
bar.value = 1;
|
|
369
|
+
message.textContent = `Removed ${data.removed_count} parallel session${data.removed_count === 1 ? '' : 's'}.`;
|
|
343
370
|
// Refresh so the row's extra_sessions count (and menu label) update
|
|
344
371
|
await this._fetch();
|
|
345
372
|
}
|
|
346
373
|
catch (err) {
|
|
374
|
+
bar.remove();
|
|
375
|
+
message.classList.add('jp-ClaudeSessionsPanel-cleanupError');
|
|
376
|
+
message.textContent = `Cleanup failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
347
377
|
this._showError(err);
|
|
348
378
|
}
|
|
379
|
+
finally {
|
|
380
|
+
if (footer) {
|
|
381
|
+
footer.style.display = '';
|
|
382
|
+
}
|
|
383
|
+
}
|
|
349
384
|
}
|
|
350
385
|
// -------------------------------------------------------------- terminal
|
|
351
386
|
async _resumeInTerminal(session, forceDangerous = false) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterlab_claude_code_extension",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.25",
|
|
4
4
|
"description": "Browse, resume, and manage your Claude Code CLI sessions from a JupyterLab side panel. One click reactivates the right terminal - no duplicate tabs, live remote-control indicator, and favourites for the projects you keep coming back to.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -84,6 +84,44 @@ describe('launch spinner dismiss contract', () => {
|
|
|
84
84
|
'utf-8'
|
|
85
85
|
);
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Contract for the cleanup-parallel popup: the dialog opens with the
|
|
89
|
+
* Close button hidden while the POST is in flight (footer display
|
|
90
|
+
* 'none'), shows an indeterminate <progress> bar, then on completion
|
|
91
|
+
* fills the bar and reports success - or drops the bar and shows the
|
|
92
|
+
* error - and restores the footer so the user can dismiss.
|
|
93
|
+
*/
|
|
94
|
+
describe('cleanup popup contract', () => {
|
|
95
|
+
const cleanup = (widgetSrc.match(
|
|
96
|
+
/private async _cleanupParallel[\s\S]*?\n \}/
|
|
97
|
+
) ?? [''])[0];
|
|
98
|
+
|
|
99
|
+
it('creates a progress element in the dialog body', () => {
|
|
100
|
+
expect(cleanup).toMatch(/createElement\('progress'\)/);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('hides the footer during the request and restores it in finally', () => {
|
|
104
|
+
expect(cleanup).toMatch(/footer\.style\.display = 'none'/);
|
|
105
|
+
expect(cleanup).toMatch(/finally[\s\S]*?footer\.style\.display = ''/);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('fills the bar and reports the removed count on success', () => {
|
|
109
|
+
expect(cleanup).toMatch(/bar\.value = 1/);
|
|
110
|
+
expect(cleanup).toMatch(/Removed \$\{data\.removed_count\}/);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('shows an error message styled with the error class on failure', () => {
|
|
114
|
+
expect(cleanup).toMatch(/catch[\s\S]*?bar\.remove\(\)/);
|
|
115
|
+
expect(cleanup).toMatch(/jp-ClaudeSessionsPanel-cleanupError/);
|
|
116
|
+
expect(cleanup).toMatch(/Cleanup failed: /);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('refreshes the session list after a successful cleanup', () => {
|
|
120
|
+
const successBlock = (cleanup.match(/try[\s\S]*?catch/) ?? [''])[0];
|
|
121
|
+
expect(successBlock).toMatch(/await this\._fetch\(\)/);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
87
125
|
it('_doResumeInTerminal dismisses spinner via dispose(), not resolve()', () => {
|
|
88
126
|
expect(widgetSrc).toMatch(/spinner\.dispose\(\)/);
|
|
89
127
|
expect(widgetSrc).not.toMatch(/spinner\.resolve\(\)/);
|
package/src/widget.ts
CHANGED
|
@@ -408,8 +408,41 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
408
408
|
}
|
|
409
409
|
|
|
410
410
|
private async _cleanupParallel(session: ISession): Promise<void> {
|
|
411
|
+
const body = new Widget();
|
|
412
|
+
body.node.className = 'jp-ClaudeSessionsPanel-cleanupBody';
|
|
413
|
+
|
|
414
|
+
const message = document.createElement('div');
|
|
415
|
+
message.className = 'jp-ClaudeSessionsPanel-cleanupMessage';
|
|
416
|
+
const count = session.extra_sessions;
|
|
417
|
+
message.textContent = `Removing ${count} parallel session${
|
|
418
|
+
count === 1 ? '' : 's'
|
|
419
|
+
}...`;
|
|
420
|
+
body.node.appendChild(message);
|
|
421
|
+
|
|
422
|
+
// No `value` attribute -> indeterminate (animated) while the request is
|
|
423
|
+
// in flight; set to max on completion so the bar reads as finished.
|
|
424
|
+
const bar = document.createElement('progress');
|
|
425
|
+
bar.className = 'jp-ClaudeSessionsPanel-cleanupProgress';
|
|
426
|
+
bar.max = 1;
|
|
427
|
+
body.node.appendChild(bar);
|
|
428
|
+
|
|
429
|
+
const dialog = new Dialog<unknown>({
|
|
430
|
+
title: 'Clean Up Parallel Sessions',
|
|
431
|
+
body,
|
|
432
|
+
buttons: [Dialog.okButton({ label: 'Close' })]
|
|
433
|
+
});
|
|
434
|
+
// Hide the Close button while work is in progress; restore it once the
|
|
435
|
+
// outcome (success or error) is shown so the user dismisses the popup.
|
|
436
|
+
const footer = dialog.node.querySelector(
|
|
437
|
+
'.jp-Dialog-footer'
|
|
438
|
+
) as HTMLElement | null;
|
|
439
|
+
if (footer) {
|
|
440
|
+
footer.style.display = 'none';
|
|
441
|
+
}
|
|
442
|
+
void dialog.launch();
|
|
443
|
+
|
|
411
444
|
try {
|
|
412
|
-
await requestAPI<ICleanupResponse>(
|
|
445
|
+
const data = await requestAPI<ICleanupResponse>(
|
|
413
446
|
'sessions/cleanup',
|
|
414
447
|
this._serverSettings,
|
|
415
448
|
{
|
|
@@ -417,10 +450,23 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
417
450
|
body: JSON.stringify({ encoded_path: session.encoded_path })
|
|
418
451
|
}
|
|
419
452
|
);
|
|
453
|
+
bar.value = 1;
|
|
454
|
+
message.textContent = `Removed ${data.removed_count} parallel session${
|
|
455
|
+
data.removed_count === 1 ? '' : 's'
|
|
456
|
+
}.`;
|
|
420
457
|
// Refresh so the row's extra_sessions count (and menu label) update
|
|
421
458
|
await this._fetch();
|
|
422
459
|
} catch (err) {
|
|
460
|
+
bar.remove();
|
|
461
|
+
message.classList.add('jp-ClaudeSessionsPanel-cleanupError');
|
|
462
|
+
message.textContent = `Cleanup failed: ${
|
|
463
|
+
err instanceof Error ? err.message : String(err)
|
|
464
|
+
}`;
|
|
423
465
|
this._showError(err);
|
|
466
|
+
} finally {
|
|
467
|
+
if (footer) {
|
|
468
|
+
footer.style.display = '';
|
|
469
|
+
}
|
|
424
470
|
}
|
|
425
471
|
}
|
|
426
472
|
|
package/style/base.css
CHANGED
|
@@ -261,6 +261,24 @@
|
|
|
261
261
|
border-width: 3px;
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
.jp-ClaudeSessionsPanel-cleanupBody {
|
|
265
|
+
display: flex;
|
|
266
|
+
flex-direction: column;
|
|
267
|
+
align-items: center;
|
|
268
|
+
justify-content: center;
|
|
269
|
+
gap: 12px;
|
|
270
|
+
padding: 12px 8px;
|
|
271
|
+
min-width: 280px;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.jp-ClaudeSessionsPanel-cleanupProgress {
|
|
275
|
+
width: 100%;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.jp-ClaudeSessionsPanel-cleanupError {
|
|
279
|
+
color: var(--jp-error-color1);
|
|
280
|
+
}
|
|
281
|
+
|
|
264
282
|
.jp-ClaudeSessionsPanel-row.jp-mod-busy {
|
|
265
283
|
opacity: 0.55;
|
|
266
284
|
pointer-events: none;
|