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 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.23",
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;