jupyterlab_claude_code_extension 1.0.55 → 1.0.57

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.d.ts CHANGED
@@ -38,6 +38,15 @@ export declare class ClaudeCodeSessionsWidget extends Widget {
38
38
  private _remove;
39
39
  private _resumeInTerminal;
40
40
  private _doResumeInTerminal;
41
+ /**
42
+ * Bring a terminal tab to the front AND hand it keyboard focus, so the
43
+ * user can start typing without an extra click. `activateById` only
44
+ * reveals the tab; the xterm inside doesn't always grab DOM focus,
45
+ * especially when the click originated in this sidebar. We defer the
46
+ * `term.focus()` to the next frame so the widget is attached and visible
47
+ * first.
48
+ */
49
+ private _focusTerminal;
41
50
  private _findTerminalForCwd;
42
51
  private _showCloseExistingDialog;
43
52
  /** Show a modal with a spinner while the terminal is being launched. The
package/lib/widget.js CHANGED
@@ -3,7 +3,7 @@ import { CommandRegistry } from '@lumino/commands';
3
3
  import { Menu, Widget } from '@lumino/widgets';
4
4
  import { requestAPI } from './request';
5
5
  import { claudeIcon, refreshIcon, removeIcon, shieldIcon, starFilledIcon } from './icons';
6
- const POLL_INTERVAL_MS = 10000;
6
+ const POLL_INTERVAL_MS = 30000;
7
7
  const DEFAULT_RECENT_LIMIT = 10;
8
8
  const EXPANDED_STORAGE_KEY = 'jupyterlab_claude_code_extension:expanded';
9
9
  const DEFAULT_PRESENTATION_MODE = 'session';
@@ -244,7 +244,10 @@ export class ClaudeCodeSessionsWidget extends Widget {
244
244
  // ------------------------------------------------------------------ data
245
245
  async _fetch() {
246
246
  var _a;
247
- const data = await requestAPI('sessions', this._serverSettings);
247
+ // `cache: 'no-store'` so the manual refresh button (and the post-launch
248
+ // refresh) always re-read the server's view of ~/.claude rather than a
249
+ // possibly-stale browser-cached response.
250
+ const data = await requestAPI('sessions', this._serverSettings, { cache: 'no-store' });
248
251
  this._sessions = (_a = data.sessions) !== null && _a !== void 0 ? _a : [];
249
252
  this._render();
250
253
  }
@@ -315,10 +318,8 @@ export class ClaudeCodeSessionsWidget extends Widget {
315
318
  if (cached && !cached.isDisposed) {
316
319
  if (forceDangerous) {
317
320
  await this._showCloseExistingDialog();
318
- this._app.shell.activateById(cached.id);
319
- return;
320
321
  }
321
- this._app.shell.activateById(cached.id);
322
+ this._focusTerminal(cached);
322
323
  return;
323
324
  }
324
325
  // 2. Walk every live terminal widget JL knows about.
@@ -328,10 +329,8 @@ export class ClaudeCodeSessionsWidget extends Widget {
328
329
  this._wireTerminalDisposal(session.project_path, found);
329
330
  if (forceDangerous) {
330
331
  await this._showCloseExistingDialog();
331
- this._app.shell.activateById(found.id);
332
- return;
333
332
  }
334
- this._app.shell.activateById(found.id);
333
+ this._focusTerminal(found);
335
334
  return;
336
335
  }
337
336
  // 3. No matching terminal - spawn a new one with `claude --resume <id>`
@@ -357,6 +356,7 @@ export class ClaudeCodeSessionsWidget extends Widget {
357
356
  if (widget === null || widget === void 0 ? void 0 : widget.id) {
358
357
  this._terminalsByPath.set(session.project_path, widget);
359
358
  this._wireTerminalDisposal(session.project_path, widget);
359
+ this._focusTerminal(widget);
360
360
  }
361
361
  }
362
362
  finally {
@@ -366,6 +366,36 @@ export class ClaudeCodeSessionsWidget extends Widget {
366
366
  catch (err) {
367
367
  this._showError(err);
368
368
  }
369
+ finally {
370
+ // Reuse or fresh launch, either way the picture changed (a session may
371
+ // now be remote-controlled, a row may have appeared). Pull fresh state.
372
+ void this._fetch().catch(() => {
373
+ /* a poll tick will retry; nothing actionable here */
374
+ });
375
+ }
376
+ }
377
+ /**
378
+ * Bring a terminal tab to the front AND hand it keyboard focus, so the
379
+ * user can start typing without an extra click. `activateById` only
380
+ * reveals the tab; the xterm inside doesn't always grab DOM focus,
381
+ * especially when the click originated in this sidebar. We defer the
382
+ * `term.focus()` to the next frame so the widget is attached and visible
383
+ * first.
384
+ */
385
+ _focusTerminal(widget) {
386
+ if (!widget || widget.isDisposed) {
387
+ return;
388
+ }
389
+ this._app.shell.activateById(widget.id);
390
+ requestAnimationFrame(() => {
391
+ var _a, _b, _c;
392
+ try {
393
+ (_c = (_b = (_a = widget.content) === null || _a === void 0 ? void 0 : _a.term) === null || _b === void 0 ? void 0 : _b.focus) === null || _c === void 0 ? void 0 : _c.call(_b);
394
+ }
395
+ catch (_err) {
396
+ /* terminal may have been disposed in the meantime - ignore */
397
+ }
398
+ });
369
399
  }
370
400
  async _findTerminalForCwd(projectPath) {
371
401
  var _a, _b;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyterlab_claude_code_extension",
3
- "version": "1.0.55",
3
+ "version": "1.0.57",
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",
package/src/widget.ts CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  ISessionsListResponse
23
23
  } from './types';
24
24
 
25
- const POLL_INTERVAL_MS = 10_000;
25
+ const POLL_INTERVAL_MS = 30_000;
26
26
  const DEFAULT_RECENT_LIMIT = 10;
27
27
  const EXPANDED_STORAGE_KEY = 'jupyterlab_claude_code_extension:expanded';
28
28
 
@@ -296,9 +296,13 @@ export class ClaudeCodeSessionsWidget extends Widget {
296
296
  // ------------------------------------------------------------------ data
297
297
 
298
298
  private async _fetch(): Promise<void> {
299
+ // `cache: 'no-store'` so the manual refresh button (and the post-launch
300
+ // refresh) always re-read the server's view of ~/.claude rather than a
301
+ // possibly-stale browser-cached response.
299
302
  const data = await requestAPI<ISessionsListResponse>(
300
303
  'sessions',
301
- this._serverSettings
304
+ this._serverSettings,
305
+ { cache: 'no-store' }
302
306
  );
303
307
  this._sessions = data.sessions ?? [];
304
308
  this._render();
@@ -390,10 +394,8 @@ export class ClaudeCodeSessionsWidget extends Widget {
390
394
  if (cached && !cached.isDisposed) {
391
395
  if (forceDangerous) {
392
396
  await this._showCloseExistingDialog();
393
- this._app.shell.activateById(cached.id);
394
- return;
395
397
  }
396
- this._app.shell.activateById(cached.id);
398
+ this._focusTerminal(cached);
397
399
  return;
398
400
  }
399
401
 
@@ -404,10 +406,8 @@ export class ClaudeCodeSessionsWidget extends Widget {
404
406
  this._wireTerminalDisposal(session.project_path, found);
405
407
  if (forceDangerous) {
406
408
  await this._showCloseExistingDialog();
407
- this._app.shell.activateById(found.id);
408
- return;
409
409
  }
410
- this._app.shell.activateById(found.id);
410
+ this._focusTerminal(found);
411
411
  return;
412
412
  }
413
413
 
@@ -441,15 +441,44 @@ export class ClaudeCodeSessionsWidget extends Widget {
441
441
  if (widget?.id) {
442
442
  this._terminalsByPath.set(session.project_path, widget);
443
443
  this._wireTerminalDisposal(session.project_path, widget);
444
+ this._focusTerminal(widget);
444
445
  }
445
446
  } finally {
446
447
  spinner.resolve();
447
448
  }
448
449
  } catch (err) {
449
450
  this._showError(err);
451
+ } finally {
452
+ // Reuse or fresh launch, either way the picture changed (a session may
453
+ // now be remote-controlled, a row may have appeared). Pull fresh state.
454
+ void this._fetch().catch(() => {
455
+ /* a poll tick will retry; nothing actionable here */
456
+ });
450
457
  }
451
458
  }
452
459
 
460
+ /**
461
+ * Bring a terminal tab to the front AND hand it keyboard focus, so the
462
+ * user can start typing without an extra click. `activateById` only
463
+ * reveals the tab; the xterm inside doesn't always grab DOM focus,
464
+ * especially when the click originated in this sidebar. We defer the
465
+ * `term.focus()` to the next frame so the widget is attached and visible
466
+ * first.
467
+ */
468
+ private _focusTerminal(widget: any): void {
469
+ if (!widget || widget.isDisposed) {
470
+ return;
471
+ }
472
+ this._app.shell.activateById(widget.id);
473
+ requestAnimationFrame(() => {
474
+ try {
475
+ widget.content?.term?.focus?.();
476
+ } catch (_err) {
477
+ /* terminal may have been disposed in the meantime - ignore */
478
+ }
479
+ });
480
+ }
481
+
453
482
  private async _findTerminalForCwd(projectPath: string): Promise<any | null> {
454
483
  if (!this._terminalTracker) {
455
484
  return null;