jupyterlab_claude_code_extension 1.2.12 → 1.2.19

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/icons.d.ts CHANGED
@@ -6,4 +6,5 @@ export declare const removeIcon: LabIcon;
6
6
  export declare const shieldIcon: LabIcon;
7
7
  export declare const addIcon: LabIcon;
8
8
  export declare const branchIcon: LabIcon;
9
+ export declare const switchIcon: LabIcon;
9
10
  export declare const filterIcon: LabIcon;
package/lib/icons.js CHANGED
@@ -67,6 +67,15 @@ export const branchIcon = new LabIcon({
67
67
  name: 'jupyterlab_claude_code_extension:branch',
68
68
  svgstr: branchSvgStr
69
69
  });
70
+ // Arrow-switch glyph (Octicons arrow-switch-16, MIT) - marks the
71
+ // "Switch and Manage Sessions" submenu.
72
+ const switchSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
73
+ <path class="jp-icon3" fill="#616161" d="M5.22 14.78a.75.75 0 0 0 1.06-1.06L4.56 12h8.69a.75.75 0 0 0 0-1.5H4.56l1.72-1.72a.75.75 0 0 0-1.06-1.06l-3 3a.75.75 0 0 0 0 1.06l3 3Zm5.56-6.5a.75.75 0 1 1-1.06-1.06l1.72-1.72H2.75a.75.75 0 0 1 0-1.5h8.69L9.72 2.28a.75.75 0 0 1 1.06-1.06l3 3a.75.75 0 0 1 0 1.06l-3 3Z"/>
74
+ </svg>`;
75
+ export const switchIcon = new LabIcon({
76
+ name: 'jupyterlab_claude_code_extension:switch',
77
+ svgstr: switchSvgStr
78
+ });
70
79
  // Funnel copied verbatim from @jupyterlab/ui-components'
71
80
  // `search/filter.svg` - the same image the file browser's filter
72
81
  // toggle uses. The `class="jp-icon3"` lets JupyterLab's theme drive
package/lib/widget.d.ts CHANGED
@@ -149,6 +149,7 @@ export declare class ClaudeCodeSessionsWidget extends Widget {
149
149
  private _commands;
150
150
  private _contextMenu;
151
151
  private _branchSubmenu;
152
+ private _branchSessionMenu;
152
153
  private _lastBranches;
153
154
  private _lastBranchesCurrent;
154
155
  private _newSessionMenu;
package/lib/widget.js CHANGED
@@ -5,7 +5,7 @@ import { CommandRegistry } from '@lumino/commands';
5
5
  import { UUID } from '@lumino/coreutils';
6
6
  import { Menu, Widget } from '@lumino/widgets';
7
7
  import { requestAPI } from './request';
8
- import { addIcon, branchIcon, claudeIcon, filterIcon, refreshIcon, removeIcon, shieldIcon, starFilledIcon } from './icons';
8
+ import { addIcon, branchIcon, switchIcon, claudeIcon, filterIcon, refreshIcon, removeIcon, shieldIcon, starFilledIcon } from './icons';
9
9
  const POLL_INTERVAL_MS = 30000;
10
10
  const DEFAULT_RECENT_LIMIT = 10;
11
11
  const EXPANDED_STORAGE_KEY = 'jupyterlab_claude_code_extension:expanded';
@@ -845,12 +845,14 @@ export class ClaudeCodeSessionsWidget extends Widget {
845
845
  starFilledIcon.element({ container: star });
846
846
  row.appendChild(star);
847
847
  }
848
- if (session.file_mtime) {
849
- const time = document.createElement('span');
850
- time.className = 'jp-ClaudeSessionsPanel-rowTime';
851
- time.textContent = this._formatRelativeTime(session.file_mtime);
852
- row.appendChild(time);
853
- }
848
+ // Always present (empty without an mtime) so the star column keeps
849
+ // the same anchor across every row in the panel.
850
+ const time = document.createElement('span');
851
+ time.className = 'jp-ClaudeSessionsPanel-rowTime';
852
+ time.textContent = session.file_mtime
853
+ ? this._formatRelativeTime(session.file_mtime)
854
+ : '';
855
+ row.appendChild(time);
854
856
  row.addEventListener('click', () => {
855
857
  if (removing) {
856
858
  return;
@@ -1073,12 +1075,11 @@ export class ClaudeCodeSessionsWidget extends Widget {
1073
1075
  }
1074
1076
  });
1075
1077
  this._commands.addCommand('claude-code-sessions:branch-session', {
1076
- label: 'Branch Session...',
1077
- icon: branchIcon,
1078
+ label: 'Normal',
1078
1079
  execute: () => void this._branchSession(false)
1079
1080
  });
1080
1081
  this._commands.addCommand('claude-code-sessions:branch-session-dangerous', {
1081
- label: 'Branch Session (Skip Permissions)...',
1082
+ label: 'Skip Permissions',
1082
1083
  icon: shieldIcon,
1083
1084
  execute: () => void this._branchSession(true)
1084
1085
  });
@@ -1116,6 +1117,18 @@ export class ClaudeCodeSessionsWidget extends Widget {
1116
1117
  this._branchSubmenu = new Menu({ commands: this._commands });
1117
1118
  this._branchSubmenu.addClass('jp-ClaudeSessionsContextMenu');
1118
1119
  this._branchSubmenu.title.label = 'Switch and Manage Sessions';
1120
+ this._branchSubmenu.title.icon = switchIcon;
1121
+ // Submenu grouping the two branch-session launch modes.
1122
+ this._branchSessionMenu = new Menu({ commands: this._commands });
1123
+ this._branchSessionMenu.addClass('jp-ClaudeSessionsContextMenu');
1124
+ this._branchSessionMenu.title.label = 'Branch Session';
1125
+ this._branchSessionMenu.title.icon = branchIcon;
1126
+ this._branchSessionMenu.addItem({
1127
+ command: 'claude-code-sessions:branch-session'
1128
+ });
1129
+ this._branchSessionMenu.addItem({
1130
+ command: 'claude-code-sessions:branch-session-dangerous'
1131
+ });
1119
1132
  this._contextMenu = new Menu({ commands: this._commands });
1120
1133
  this._contextMenu.addClass('jp-ClaudeSessionsContextMenu');
1121
1134
  this._rebuildContextMenu(false);
@@ -1154,10 +1167,8 @@ export class ClaudeCodeSessionsWidget extends Widget {
1154
1167
  });
1155
1168
  }
1156
1169
  this._contextMenu.addItem({
1157
- command: 'claude-code-sessions:branch-session'
1158
- });
1159
- this._contextMenu.addItem({
1160
- command: 'claude-code-sessions:branch-session-dangerous'
1170
+ type: 'submenu',
1171
+ submenu: this._branchSessionMenu
1161
1172
  });
1162
1173
  this._contextMenu.addItem({
1163
1174
  command: 'claude-code-sessions:cleanup-parallel'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyterlab_claude_code_extension",
3
- "version": "1.2.12",
3
+ "version": "1.2.19",
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",
@@ -318,12 +318,22 @@ describe('launch spinner dismiss contract', () => {
318
318
  const rowTime = (css.match(
319
319
  /\.jp-ClaudeSessionsPanel-rowTime \{[\s\S]*?\}/
320
320
  ) ?? [''])[0];
321
- expect(rowTime).toMatch(/width: 52px/);
321
+ expect(rowTime).toMatch(/width: 4em/);
322
+ expect(rowTime).toMatch(/white-space: nowrap/);
322
323
  expect(rowTime).toMatch(/text-align: right/);
323
324
  const branchTime = (css.match(
324
325
  /\.jp-ClaudeSessionsPanel-branchTime \{[\s\S]*?\}/
325
326
  ) ?? [''])[0];
326
- expect(branchTime).toMatch(/width: 52px/);
327
+ expect(branchTime).toMatch(/width: 4em/);
328
+ expect(branchTime).toMatch(/white-space: nowrap/);
329
+ // Stars line up vertically across the entire panel: every row keeps
330
+ // the time slot (empty without an mtime) and every section reserves
331
+ // the scrollbar gutter.
332
+ expect(widgetSrc).toMatch(/time\.textContent = session\.file_mtime\s*\?/);
333
+ const list = (css.match(/\.jp-ClaudeSessionsPanel-list \{[\s\S]*?\}/) ?? [
334
+ ''
335
+ ])[0];
336
+ expect(list).toMatch(/scrollbar-gutter: stable/);
327
337
  expect(branchTime).toMatch(/text-align: right/);
328
338
  });
329
339
 
@@ -337,14 +347,20 @@ describe('launch spinner dismiss contract', () => {
337
347
  );
338
348
  });
339
349
 
340
- it('branch session commands exist in normal and skip-permissions modes', () => {
350
+ it('branch session is a submenu with normal and skip-permissions entries', () => {
341
351
  expect(widgetSrc).toMatch(/claude-code-sessions:branch-session'/);
342
352
  expect(widgetSrc).toMatch(
343
353
  /claude-code-sessions:branch-session-dangerous/
344
354
  );
345
355
  expect(widgetSrc).toMatch(
346
- /Branch Session \(Skip Permissions\)\.\.\.[\s\S]{0,80}?icon: shieldIcon/
356
+ /_branchSessionMenu\.title\.label = 'Branch Session'/
357
+ );
358
+ expect(widgetSrc).toMatch(
359
+ /label: 'Skip Permissions',\s*icon: shieldIcon/
347
360
  );
361
+ expect(widgetSrc).toMatch(/submenu: this\._branchSessionMenu/);
362
+ // No ellipsis on the branch-session labels.
363
+ expect(widgetSrc).not.toMatch(/Branch Session\.\.\./);
348
364
  });
349
365
 
350
366
  it('branch session asks for a name and launches a known fork id', () => {
package/src/icons.ts CHANGED
@@ -82,6 +82,17 @@ export const branchIcon = new LabIcon({
82
82
  svgstr: branchSvgStr
83
83
  });
84
84
 
85
+ // Arrow-switch glyph (Octicons arrow-switch-16, MIT) - marks the
86
+ // "Switch and Manage Sessions" submenu.
87
+ const switchSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
88
+ <path class="jp-icon3" fill="#616161" d="M5.22 14.78a.75.75 0 0 0 1.06-1.06L4.56 12h8.69a.75.75 0 0 0 0-1.5H4.56l1.72-1.72a.75.75 0 0 0-1.06-1.06l-3 3a.75.75 0 0 0 0 1.06l3 3Zm5.56-6.5a.75.75 0 1 1-1.06-1.06l1.72-1.72H2.75a.75.75 0 0 1 0-1.5h8.69L9.72 2.28a.75.75 0 0 1 1.06-1.06l3 3a.75.75 0 0 1 0 1.06l-3 3Z"/>
89
+ </svg>`;
90
+
91
+ export const switchIcon = new LabIcon({
92
+ name: 'jupyterlab_claude_code_extension:switch',
93
+ svgstr: switchSvgStr
94
+ });
95
+
85
96
  // Funnel copied verbatim from @jupyterlab/ui-components'
86
97
  // `search/filter.svg` - the same image the file browser's filter
87
98
  // toggle uses. The `class="jp-icon3"` lets JupyterLab's theme drive
package/src/widget.ts CHANGED
@@ -19,6 +19,7 @@ import { requestAPI } from './request';
19
19
  import {
20
20
  addIcon,
21
21
  branchIcon,
22
+ switchIcon,
22
23
  claudeIcon,
23
24
  filterIcon,
24
25
  refreshIcon,
@@ -996,12 +997,14 @@ export class ClaudeCodeSessionsWidget extends Widget {
996
997
  row.appendChild(star);
997
998
  }
998
999
 
999
- if (session.file_mtime) {
1000
- const time = document.createElement('span');
1001
- time.className = 'jp-ClaudeSessionsPanel-rowTime';
1002
- time.textContent = this._formatRelativeTime(session.file_mtime);
1003
- row.appendChild(time);
1004
- }
1000
+ // Always present (empty without an mtime) so the star column keeps
1001
+ // the same anchor across every row in the panel.
1002
+ const time = document.createElement('span');
1003
+ time.className = 'jp-ClaudeSessionsPanel-rowTime';
1004
+ time.textContent = session.file_mtime
1005
+ ? this._formatRelativeTime(session.file_mtime)
1006
+ : '';
1007
+ row.appendChild(time);
1005
1008
 
1006
1009
  row.addEventListener('click', () => {
1007
1010
  if (removing) {
@@ -1252,13 +1255,12 @@ export class ClaudeCodeSessionsWidget extends Widget {
1252
1255
  });
1253
1256
 
1254
1257
  this._commands.addCommand('claude-code-sessions:branch-session', {
1255
- label: 'Branch Session...',
1256
- icon: branchIcon,
1258
+ label: 'Normal',
1257
1259
  execute: () => void this._branchSession(false)
1258
1260
  });
1259
1261
 
1260
1262
  this._commands.addCommand('claude-code-sessions:branch-session-dangerous', {
1261
- label: 'Branch Session (Skip Permissions)...',
1263
+ label: 'Skip Permissions',
1262
1264
  icon: shieldIcon,
1263
1265
  execute: () => void this._branchSession(true)
1264
1266
  });
@@ -1301,6 +1303,19 @@ export class ClaudeCodeSessionsWidget extends Widget {
1301
1303
  this._branchSubmenu = new Menu({ commands: this._commands });
1302
1304
  this._branchSubmenu.addClass('jp-ClaudeSessionsContextMenu');
1303
1305
  this._branchSubmenu.title.label = 'Switch and Manage Sessions';
1306
+ this._branchSubmenu.title.icon = switchIcon;
1307
+
1308
+ // Submenu grouping the two branch-session launch modes.
1309
+ this._branchSessionMenu = new Menu({ commands: this._commands });
1310
+ this._branchSessionMenu.addClass('jp-ClaudeSessionsContextMenu');
1311
+ this._branchSessionMenu.title.label = 'Branch Session';
1312
+ this._branchSessionMenu.title.icon = branchIcon;
1313
+ this._branchSessionMenu.addItem({
1314
+ command: 'claude-code-sessions:branch-session'
1315
+ });
1316
+ this._branchSessionMenu.addItem({
1317
+ command: 'claude-code-sessions:branch-session-dangerous'
1318
+ });
1304
1319
 
1305
1320
  this._contextMenu = new Menu({ commands: this._commands });
1306
1321
  this._contextMenu.addClass('jp-ClaudeSessionsContextMenu');
@@ -1342,10 +1357,8 @@ export class ClaudeCodeSessionsWidget extends Widget {
1342
1357
  });
1343
1358
  }
1344
1359
  this._contextMenu.addItem({
1345
- command: 'claude-code-sessions:branch-session'
1346
- });
1347
- this._contextMenu.addItem({
1348
- command: 'claude-code-sessions:branch-session-dangerous'
1360
+ type: 'submenu',
1361
+ submenu: this._branchSessionMenu
1349
1362
  });
1350
1363
  this._contextMenu.addItem({
1351
1364
  command: 'claude-code-sessions:cleanup-parallel'
@@ -1803,6 +1816,7 @@ export class ClaudeCodeSessionsWidget extends Widget {
1803
1816
  private _commands!: CommandRegistry;
1804
1817
  private _contextMenu!: Menu;
1805
1818
  private _branchSubmenu!: Menu;
1819
+ private _branchSessionMenu!: Menu;
1806
1820
  private _lastBranches: IBranch[] = [];
1807
1821
  private _lastBranchesCurrent = '';
1808
1822
  private _newSessionMenu!: Menu;
package/style/base.css CHANGED
@@ -155,6 +155,10 @@
155
155
  flex: 1 1 auto;
156
156
  overflow-y: auto;
157
157
  min-height: 0;
158
+
159
+ /* Reserve the scrollbar gutter in every section so star/time columns
160
+ line up across the entire panel, scrollbar or not. */
161
+ scrollbar-gutter: stable;
158
162
  }
159
163
 
160
164
  .jp-ClaudeSessionsPanel-row {
@@ -382,7 +386,8 @@
382
386
 
383
387
  .jp-ClaudeSessionsPanel-branchTime {
384
388
  flex: none;
385
- width: 52px;
389
+ width: 4em;
390
+ white-space: nowrap;
386
391
  text-align: right;
387
392
  color: var(--jp-ui-font-color2);
388
393
  font-size: var(--jp-ui-font-size0);
@@ -458,7 +463,8 @@
458
463
  column so values line up across rows; the star column sits before it. */
459
464
  .jp-ClaudeSessionsPanel-rowTime {
460
465
  flex: 0 0 auto;
461
- width: 52px;
466
+ width: 4em;
467
+ white-space: nowrap;
462
468
  text-align: right;
463
469
  color: var(--jp-ui-font-color2);
464
470
  font-size: var(--jp-ui-font-size0);