jupyterlab_claude_code_extension 1.1.7 → 1.1.9

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/index.js CHANGED
@@ -39,9 +39,10 @@ const plugin = {
39
39
  labShell.add(widget, currentSidebar, { rank: 600 });
40
40
  const apply = () => {
41
41
  const mode = settings.get('presentationMode').composite;
42
- if (mode === 'session' || mode === 'folder' || mode === 'path') {
43
- widget.setPresentationMode(mode);
44
- }
42
+ // 'session' was a pre-1.1.8 mode that pulled labels from the
43
+ // session's `name` field; we now ignore that field entirely, so
44
+ // any value other than 'path' is treated as 'folder'.
45
+ widget.setPresentationMode(mode === 'path' ? 'path' : 'folder');
45
46
  const limit = settings.get('recentLimit').composite;
46
47
  if (typeof limit === 'number') {
47
48
  widget.setRecentLimit(limit);
package/lib/widget.d.ts CHANGED
@@ -2,7 +2,7 @@ import { JupyterFrontEnd } from '@jupyterlab/application';
2
2
  import { ITerminalTracker } from '@jupyterlab/terminal';
3
3
  import { Widget } from '@lumino/widgets';
4
4
  import { Message } from '@lumino/messaging';
5
- export type PresentationMode = 'session' | 'folder' | 'path';
5
+ export type PresentationMode = 'folder' | 'path';
6
6
  export declare class ClaudeCodeSessionsWidget extends Widget {
7
7
  constructor(app: JupyterFrontEnd, rootDir: string, terminalTracker?: ITerminalTracker | null);
8
8
  refresh(): void;
@@ -54,18 +54,15 @@ export declare class ClaudeCodeSessionsWidget extends Widget {
54
54
  */
55
55
  private _showLaunchSpinner;
56
56
  private _wireTerminalDisposal;
57
- /** Apply the presentation-mode setting (path-segment disambiguation is
58
- * handled separately in ``_disambiguate``). */
57
+ /** Apply the presentation-mode setting (basename collisions for folder
58
+ * mode are resolved separately in ``_disambiguate``). */
59
59
  private _displayName;
60
60
  private _basename;
61
61
  /** Walk path tails until every name in a colliding group is unique.
62
- *
63
- * User-set rename names (``name_source === 'rename'``) survive
64
- * disambiguation untouched: a Claude ``/rename`` should never be rolled
65
- * back to a path tail just because some other row happens to share its
66
- * folder basename. Basename-derived rows in the same group get the path
67
- * tail suffix, picked so it stays distinct from every rename label too.
68
- */
62
+ * Folder-mode labels are folder basenames, so two different projects can
63
+ * end up with the same label (e.g. two `datascience` folders under
64
+ * different parents); we extend each colliding label with as much of its
65
+ * parent path as it takes to make it unique. */
69
66
  private _disambiguate;
70
67
  private _render;
71
68
  private _renderSection;
package/lib/widget.js CHANGED
@@ -7,7 +7,7 @@ import { claudeIcon, refreshIcon, removeIcon, shieldIcon, starFilledIcon } from
7
7
  const POLL_INTERVAL_MS = 30000;
8
8
  const DEFAULT_RECENT_LIMIT = 10;
9
9
  const EXPANDED_STORAGE_KEY = 'jupyterlab_claude_code_extension:expanded';
10
- const DEFAULT_PRESENTATION_MODE = 'session';
10
+ const DEFAULT_PRESENTATION_MODE = 'folder';
11
11
  const SECTION_LABELS = {
12
12
  favourites: 'Favorites',
13
13
  recent: 'Recent',
@@ -487,19 +487,14 @@ export class ClaudeCodeSessionsWidget extends Widget {
487
487
  });
488
488
  }
489
489
  // -------------------------------------------------------------- rendering
490
- /** Apply the presentation-mode setting (path-segment disambiguation is
491
- * handled separately in ``_disambiguate``). */
490
+ /** Apply the presentation-mode setting (basename collisions for folder
491
+ * mode are resolved separately in ``_disambiguate``). */
492
492
  _displayName(s) {
493
493
  const folder = this._basename(s.project_path) || s.encoded_path;
494
- switch (this._presentationMode) {
495
- case 'folder':
496
- return folder;
497
- case 'path':
498
- return this._displayPath(s.project_path) || folder;
499
- case 'session':
500
- default:
501
- return s.name || folder;
494
+ if (this._presentationMode === 'path') {
495
+ return this._displayPath(s.project_path) || folder;
502
496
  }
497
+ return folder;
503
498
  }
504
499
  _basename(p) {
505
500
  if (!p) {
@@ -509,13 +504,10 @@ export class ClaudeCodeSessionsWidget extends Widget {
509
504
  return parts[parts.length - 1] || '';
510
505
  }
511
506
  /** Walk path tails until every name in a colliding group is unique.
512
- *
513
- * User-set rename names (``name_source === 'rename'``) survive
514
- * disambiguation untouched: a Claude ``/rename`` should never be rolled
515
- * back to a path tail just because some other row happens to share its
516
- * folder basename. Basename-derived rows in the same group get the path
517
- * tail suffix, picked so it stays distinct from every rename label too.
518
- */
507
+ * Folder-mode labels are folder basenames, so two different projects can
508
+ * end up with the same label (e.g. two `datascience` folders under
509
+ * different parents); we extend each colliding label with as much of its
510
+ * parent path as it takes to make it unique. */
519
511
  _disambiguate(rows) {
520
512
  var _a;
521
513
  const out = new Map();
@@ -529,38 +521,24 @@ export class ClaudeCodeSessionsWidget extends Widget {
529
521
  out.set(group[0].project_path, name);
530
522
  continue;
531
523
  }
532
- const renames = group.filter(r => r.name_source === 'rename');
533
- const basenames = group.filter(r => r.name_source !== 'rename');
534
- // Rename rows win unchanged. The ``taken`` set guards basename
535
- // disambiguation from colliding back into a rename's label.
536
- const taken = new Set();
537
- for (const r of renames) {
538
- out.set(r.project_path, r.name);
539
- taken.add(r.name);
540
- }
541
- if (basenames.length === 0) {
542
- continue;
543
- }
544
- // Walk path tails for basename rows: tail must be unique among
545
- // basenames AND not equal to any rename label already taken.
546
- const segs = basenames.map(r => r.project_path.split('/').filter(Boolean));
524
+ const segs = group.map(r => r.project_path.split('/').filter(Boolean));
547
525
  const max = Math.max(...segs.map(s => s.length));
548
526
  let depth = 1;
549
527
  let resolved = false;
550
528
  while (depth <= max) {
551
529
  const tails = segs.map(s => s.slice(-depth).join('/'));
552
- const unique = new Set(tails).size === tails.length;
553
- const noConflict = tails.every(t => !taken.has(t));
554
- if (unique && noConflict) {
555
- basenames.forEach((r, i) => out.set(r.project_path, tails[i]));
530
+ if (new Set(tails).size === tails.length) {
531
+ group.forEach((r, i) => out.set(r.project_path, tails[i]));
556
532
  resolved = true;
557
533
  break;
558
534
  }
559
535
  depth += 1;
560
536
  }
561
537
  if (!resolved) {
562
- // Fallback to absolute path
563
- basenames.forEach(r => out.set(r.project_path, r.project_path));
538
+ // Identical project_path values across rows shouldn't happen
539
+ // (list_sessions dedups by path) but if it ever does, fall back to
540
+ // the absolute path so rows stay distinguishable.
541
+ group.forEach(r => out.set(r.project_path, r.project_path));
564
542
  }
565
543
  }
566
544
  return out;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyterlab_claude_code_extension",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
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",
@@ -94,7 +94,8 @@
94
94
  "yjs": "^13.5.0"
95
95
  },
96
96
  "resolutions": {
97
- "lib0": "0.2.111"
97
+ "lib0": "0.2.111",
98
+ "license-webpack-plugin": "4.0.2"
98
99
  },
99
100
  "sideEffects": [
100
101
  "style/*.css",
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@ import { ITerminalTracker } from '@jupyterlab/terminal';
9
9
 
10
10
  import { requestAPI } from './request';
11
11
  import { IStatusResponse } from './types';
12
- import { ClaudeCodeSessionsWidget, PresentationMode } from './widget';
12
+ import { ClaudeCodeSessionsWidget } from './widget';
13
13
 
14
14
  const PLUGIN_ID = 'jupyterlab_claude_code_extension:plugin';
15
15
  const WIDGET_ID = 'jupyterlab-claude-code-extension';
@@ -69,9 +69,10 @@ const plugin: JupyterFrontEndPlugin<void> = {
69
69
 
70
70
  const apply = (): void => {
71
71
  const mode = settings.get('presentationMode').composite as string;
72
- if (mode === 'session' || mode === 'folder' || mode === 'path') {
73
- widget.setPresentationMode(mode as PresentationMode);
74
- }
72
+ // 'session' was a pre-1.1.8 mode that pulled labels from the
73
+ // session's `name` field; we now ignore that field entirely, so
74
+ // any value other than 'path' is treated as 'folder'.
75
+ widget.setPresentationMode(mode === 'path' ? 'path' : 'folder');
75
76
  const limit = settings.get('recentLimit').composite as number;
76
77
  if (typeof limit === 'number') {
77
78
  widget.setRecentLimit(limit);
package/src/widget.ts CHANGED
@@ -34,9 +34,9 @@ const EXPANDED_STORAGE_KEY = 'jupyterlab_claude_code_extension:expanded';
34
34
 
35
35
  type SectionKey = 'favourites' | 'recent' | 'all';
36
36
 
37
- export type PresentationMode = 'session' | 'folder' | 'path';
37
+ export type PresentationMode = 'folder' | 'path';
38
38
 
39
- const DEFAULT_PRESENTATION_MODE: PresentationMode = 'session';
39
+ const DEFAULT_PRESENTATION_MODE: PresentationMode = 'folder';
40
40
 
41
41
  const SECTION_LABELS: Record<SectionKey, string> = {
42
42
  favourites: 'Favorites',
@@ -585,19 +585,14 @@ export class ClaudeCodeSessionsWidget extends Widget {
585
585
 
586
586
  // -------------------------------------------------------------- rendering
587
587
 
588
- /** Apply the presentation-mode setting (path-segment disambiguation is
589
- * handled separately in ``_disambiguate``). */
588
+ /** Apply the presentation-mode setting (basename collisions for folder
589
+ * mode are resolved separately in ``_disambiguate``). */
590
590
  private _displayName(s: ISession): string {
591
591
  const folder = this._basename(s.project_path) || s.encoded_path;
592
- switch (this._presentationMode) {
593
- case 'folder':
594
- return folder;
595
- case 'path':
596
- return this._displayPath(s.project_path) || folder;
597
- case 'session':
598
- default:
599
- return s.name || folder;
592
+ if (this._presentationMode === 'path') {
593
+ return this._displayPath(s.project_path) || folder;
600
594
  }
595
+ return folder;
601
596
  }
602
597
 
603
598
  private _basename(p: string): string {
@@ -609,13 +604,10 @@ export class ClaudeCodeSessionsWidget extends Widget {
609
604
  }
610
605
 
611
606
  /** Walk path tails until every name in a colliding group is unique.
612
- *
613
- * User-set rename names (``name_source === 'rename'``) survive
614
- * disambiguation untouched: a Claude ``/rename`` should never be rolled
615
- * back to a path tail just because some other row happens to share its
616
- * folder basename. Basename-derived rows in the same group get the path
617
- * tail suffix, picked so it stays distinct from every rename label too.
618
- */
607
+ * Folder-mode labels are folder basenames, so two different projects can
608
+ * end up with the same label (e.g. two `datascience` folders under
609
+ * different parents); we extend each colliding label with as much of its
610
+ * parent path as it takes to make it unique. */
619
611
  private _disambiguate(rows: ISession[]): Map<string, string> {
620
612
  const out = new Map<string, string>();
621
613
  const groups = new Map<string, ISession[]>();
@@ -628,43 +620,24 @@ export class ClaudeCodeSessionsWidget extends Widget {
628
620
  out.set(group[0].project_path, name);
629
621
  continue;
630
622
  }
631
-
632
- const renames = group.filter(r => r.name_source === 'rename');
633
- const basenames = group.filter(r => r.name_source !== 'rename');
634
-
635
- // Rename rows win unchanged. The ``taken`` set guards basename
636
- // disambiguation from colliding back into a rename's label.
637
- const taken = new Set<string>();
638
- for (const r of renames) {
639
- out.set(r.project_path, r.name);
640
- taken.add(r.name);
641
- }
642
- if (basenames.length === 0) {
643
- continue;
644
- }
645
-
646
- // Walk path tails for basename rows: tail must be unique among
647
- // basenames AND not equal to any rename label already taken.
648
- const segs = basenames.map(r =>
649
- r.project_path.split('/').filter(Boolean)
650
- );
623
+ const segs = group.map(r => r.project_path.split('/').filter(Boolean));
651
624
  const max = Math.max(...segs.map(s => s.length));
652
625
  let depth = 1;
653
626
  let resolved = false;
654
627
  while (depth <= max) {
655
628
  const tails = segs.map(s => s.slice(-depth).join('/'));
656
- const unique = new Set(tails).size === tails.length;
657
- const noConflict = tails.every(t => !taken.has(t));
658
- if (unique && noConflict) {
659
- basenames.forEach((r, i) => out.set(r.project_path, tails[i]));
629
+ if (new Set(tails).size === tails.length) {
630
+ group.forEach((r, i) => out.set(r.project_path, tails[i]));
660
631
  resolved = true;
661
632
  break;
662
633
  }
663
634
  depth += 1;
664
635
  }
665
636
  if (!resolved) {
666
- // Fallback to absolute path
667
- basenames.forEach(r => out.set(r.project_path, r.project_path));
637
+ // Identical project_path values across rows shouldn't happen
638
+ // (list_sessions dedups by path) but if it ever does, fall back to
639
+ // the absolute path so rows stay distinguishable.
640
+ group.forEach(r => out.set(r.project_path, r.project_path));
668
641
  }
669
642
  }
670
643
  return out;