xtralab 0.15.0 → 0.15.1

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.
Files changed (228) hide show
  1. package/README.md +10 -0
  2. package/lib/about/icons.d.ts +0 -5
  3. package/lib/about/icons.js +0 -5
  4. package/lib/about/index.d.ts +4 -8
  5. package/lib/about/index.js +6 -17
  6. package/lib/agentSessions.d.ts +9 -29
  7. package/lib/agentSessions.js +2 -7
  8. package/lib/askAgent/editorSelection.d.ts +20 -26
  9. package/lib/askAgent/editorSelection.js +11 -26
  10. package/lib/askAgent/icons.d.ts +2 -3
  11. package/lib/askAgent/icons.js +2 -3
  12. package/lib/askAgent/index.d.ts +4 -26
  13. package/lib/askAgent/index.js +54 -158
  14. package/lib/askAgent/popup.d.ts +12 -16
  15. package/lib/askAgent/popup.js +18 -37
  16. package/lib/askAgent/prompt.d.ts +3 -5
  17. package/lib/askAgent/prompt.js +6 -19
  18. package/lib/askAgent/queue.d.ts +11 -21
  19. package/lib/askAgent/queue.js +9 -19
  20. package/lib/askAgent/queuePanel.d.ts +13 -18
  21. package/lib/askAgent/queuePanel.js +15 -37
  22. package/lib/askAgent/targetPicker.d.ts +2 -9
  23. package/lib/askAgent/targetPicker.js +6 -11
  24. package/lib/askAgent/tokens.d.ts +32 -46
  25. package/lib/askAgent/tokens.js +4 -6
  26. package/lib/commandBar/index.d.ts +4 -13
  27. package/lib/commandBar/index.js +21 -39
  28. package/lib/customPanel/index.js +15 -0
  29. package/lib/editorBreadcrumbs/index.d.ts +3 -6
  30. package/lib/editorBreadcrumbs/index.js +10 -6
  31. package/lib/editorBreadcrumbs/widget.d.ts +17 -4
  32. package/lib/editorBreadcrumbs/widget.js +11 -17
  33. package/lib/editorIndent/index.d.ts +4 -12
  34. package/lib/editorIndent/index.js +8 -23
  35. package/lib/fileBrowser/commands.d.ts +28 -19
  36. package/lib/fileBrowser/commands.js +39 -98
  37. package/lib/fileBrowser/contents.d.ts +3 -0
  38. package/lib/fileBrowser/dragAndDrop.d.ts +17 -4
  39. package/lib/fileBrowser/dragAndDrop.js +2 -7
  40. package/lib/fileBrowser/fileBrowser.d.ts +19 -9
  41. package/lib/fileBrowser/fileBrowser.js +68 -216
  42. package/lib/fileBrowser/gitStatus.d.ts +4 -9
  43. package/lib/fileBrowser/gitStatus.js +7 -17
  44. package/lib/fileBrowser/gitignore.d.ts +4 -17
  45. package/lib/fileBrowser/gitignore.js +4 -25
  46. package/lib/fileBrowser/icons.d.ts +7 -17
  47. package/lib/fileBrowser/icons.js +25 -80
  48. package/lib/fileBrowser/index.js +2 -4
  49. package/lib/fileBrowser/widget.d.ts +126 -48
  50. package/lib/fileBrowser/widget.js +89 -4
  51. package/lib/fileTypeIcons/index.js +0 -10
  52. package/lib/git/api.d.ts +5 -13
  53. package/lib/git/api.js +10 -36
  54. package/lib/git/askRequest.d.ts +4 -8
  55. package/lib/git/askRequest.js +6 -20
  56. package/lib/git/commands.d.ts +40 -5
  57. package/lib/git/commands.js +16 -10
  58. package/lib/git/diffModel.js +4 -0
  59. package/lib/git/diffSurface.d.ts +41 -55
  60. package/lib/git/diffSurface.js +42 -117
  61. package/lib/git/diffWidget.d.ts +81 -2
  62. package/lib/git/diffWidget.js +62 -12
  63. package/lib/git/imageDiff.d.ts +5 -6
  64. package/lib/git/imageDiff.js +18 -48
  65. package/lib/git/index.d.ts +0 -3
  66. package/lib/git/index.js +3 -8
  67. package/lib/git/notebookDiff.d.ts +110 -33
  68. package/lib/git/notebookDiff.js +52 -195
  69. package/lib/git/tokens.d.ts +73 -31
  70. package/lib/git/tokens.js +2 -3
  71. package/lib/highlight/index.d.ts +4 -10
  72. package/lib/highlight/index.js +10 -41
  73. package/lib/index.d.ts +0 -9
  74. package/lib/index.js +0 -9
  75. package/lib/launcher/agents.d.ts +55 -51
  76. package/lib/launcher/agents.js +15 -57
  77. package/lib/launcher/availability.d.ts +3 -9
  78. package/lib/launcher/availability.js +3 -9
  79. package/lib/launcher/commands.d.ts +7 -32
  80. package/lib/launcher/commands.js +14 -45
  81. package/lib/launcher/dashboard.d.ts +19 -24
  82. package/lib/launcher/dashboard.js +21 -67
  83. package/lib/launcher/editorRegistry.d.ts +10 -22
  84. package/lib/launcher/editorRegistry.js +14 -18
  85. package/lib/launcher/editors.d.ts +39 -45
  86. package/lib/launcher/editors.js +8 -34
  87. package/lib/launcher/icons.d.ts +0 -12
  88. package/lib/launcher/icons.js +5 -39
  89. package/lib/launcher/index.js +12 -55
  90. package/lib/launcher/invocation.d.ts +4 -10
  91. package/lib/launcher/invocation.js +7 -22
  92. package/lib/launcher/registry.d.ts +9 -7
  93. package/lib/launcher/registry.js +9 -7
  94. package/lib/launcher/tokens.d.ts +10 -22
  95. package/lib/launcher/tokens.js +5 -8
  96. package/lib/menuBar/index.d.ts +3 -10
  97. package/lib/menuBar/index.js +11 -28
  98. package/lib/menus/index.d.ts +3 -9
  99. package/lib/menus/index.js +14 -30
  100. package/lib/omnibox/files.d.ts +2 -4
  101. package/lib/omnibox/files.js +5 -11
  102. package/lib/omnibox/index.d.ts +4 -14
  103. package/lib/omnibox/index.js +9 -27
  104. package/lib/omnibox/model.d.ts +38 -10
  105. package/lib/omnibox/model.js +15 -33
  106. package/lib/omnibox/recents.d.ts +15 -17
  107. package/lib/omnibox/recents.js +12 -17
  108. package/lib/omnibox/tokens.d.ts +5 -10
  109. package/lib/omnibox/tokens.js +2 -6
  110. package/lib/omnibox/widget.d.ts +11 -7
  111. package/lib/omnibox/widget.js +8 -16
  112. package/lib/searchReplace/index.d.ts +4 -20
  113. package/lib/searchReplace/index.js +7 -27
  114. package/lib/showOutput/index.d.ts +3 -12
  115. package/lib/showOutput/index.js +6 -22
  116. package/lib/sidebar/index.d.ts +4 -7
  117. package/lib/sidebar/index.js +5 -12
  118. package/lib/terminalNotifications/index.d.ts +4 -10
  119. package/lib/terminalNotifications/index.js +6 -20
  120. package/lib/terminals/agentTerminals.d.ts +12 -5
  121. package/lib/terminals/agentTerminals.js +25 -44
  122. package/lib/terminals/detection.d.ts +4 -8
  123. package/lib/terminals/detection.js +4 -8
  124. package/lib/terminals/index.d.ts +0 -35
  125. package/lib/terminals/index.js +12 -101
  126. package/lib/terminals/model.d.ts +64 -139
  127. package/lib/terminals/model.js +80 -220
  128. package/lib/terminals/tokens.d.ts +11 -22
  129. package/lib/terminals/tokens.js +2 -2
  130. package/lib/terminals/widget.d.ts +43 -13
  131. package/lib/terminals/widget.js +33 -18
  132. package/lib/topBar/icons.d.ts +3 -8
  133. package/lib/topBar/icons.js +3 -8
  134. package/lib/topBar/index.d.ts +4 -11
  135. package/lib/topBar/index.js +10 -38
  136. package/lib/walkthrough/index.d.ts +4 -9
  137. package/lib/walkthrough/index.js +6 -20
  138. package/lib/walkthrough/panel.d.ts +40 -9
  139. package/lib/walkthrough/panel.js +4 -10
  140. package/package.json +2 -2
  141. package/src/about/icons.ts +0 -5
  142. package/src/about/index.tsx +6 -17
  143. package/src/agentSessions.ts +9 -29
  144. package/src/askAgent/editorSelection.ts +22 -29
  145. package/src/askAgent/icons.ts +2 -3
  146. package/src/askAgent/index.ts +54 -158
  147. package/src/askAgent/popup.tsx +23 -45
  148. package/src/askAgent/prompt.ts +6 -19
  149. package/src/askAgent/queue.ts +13 -24
  150. package/src/askAgent/queuePanel.tsx +21 -46
  151. package/src/askAgent/targetPicker.tsx +5 -15
  152. package/src/askAgent/tokens.ts +32 -46
  153. package/src/commandBar/index.ts +24 -39
  154. package/src/customPanel/index.ts +15 -0
  155. package/src/editorBreadcrumbs/index.ts +10 -6
  156. package/src/editorBreadcrumbs/widget.ts +23 -17
  157. package/src/editorIndent/index.ts +17 -23
  158. package/src/fileBrowser/commands.ts +57 -98
  159. package/src/fileBrowser/contents.ts +3 -0
  160. package/src/fileBrowser/dragAndDrop.ts +17 -7
  161. package/src/fileBrowser/fileBrowser.tsx +84 -223
  162. package/src/fileBrowser/gitStatus.ts +7 -17
  163. package/src/fileBrowser/gitignore.ts +4 -25
  164. package/src/fileBrowser/icons.ts +25 -80
  165. package/src/fileBrowser/index.ts +2 -4
  166. package/src/fileBrowser/widget.tsx +149 -50
  167. package/src/fileTypeIcons/index.ts +0 -10
  168. package/src/git/api.ts +10 -36
  169. package/src/git/askRequest.ts +6 -20
  170. package/src/git/commands.ts +70 -10
  171. package/src/git/diffModel.ts +25 -0
  172. package/src/git/diffSurface.tsx +71 -159
  173. package/src/git/diffWidget.tsx +104 -14
  174. package/src/git/imageDiff.tsx +45 -51
  175. package/src/git/index.ts +3 -8
  176. package/src/git/notebookDiff.tsx +211 -238
  177. package/src/git/tokens.ts +73 -31
  178. package/src/highlight/index.ts +10 -41
  179. package/src/index.ts +0 -9
  180. package/src/launcher/agents.ts +64 -95
  181. package/src/launcher/availability.ts +3 -9
  182. package/src/launcher/commands.ts +14 -51
  183. package/src/launcher/dashboard.tsx +49 -88
  184. package/src/launcher/editorRegistry.ts +21 -29
  185. package/src/launcher/editors.ts +39 -58
  186. package/src/launcher/icons.ts +5 -39
  187. package/src/launcher/index.ts +12 -55
  188. package/src/launcher/invocation.ts +7 -22
  189. package/src/launcher/registry.ts +9 -7
  190. package/src/launcher/tokens.ts +10 -22
  191. package/src/menuBar/index.ts +11 -28
  192. package/src/menus/index.ts +14 -30
  193. package/src/omnibox/files.ts +10 -14
  194. package/src/omnibox/index.ts +9 -27
  195. package/src/omnibox/model.ts +50 -38
  196. package/src/omnibox/recents.ts +17 -22
  197. package/src/omnibox/tokens.ts +5 -10
  198. package/src/omnibox/widget.tsx +14 -19
  199. package/src/searchReplace/index.ts +7 -27
  200. package/src/showOutput/index.ts +6 -22
  201. package/src/sidebar/index.ts +20 -12
  202. package/src/terminalNotifications/index.ts +42 -25
  203. package/src/terminals/agentTerminals.ts +25 -49
  204. package/src/terminals/detection.ts +4 -8
  205. package/src/terminals/index.ts +12 -101
  206. package/src/terminals/model.ts +130 -246
  207. package/src/terminals/tokens.ts +11 -22
  208. package/src/terminals/widget.tsx +48 -31
  209. package/src/topBar/icons.ts +3 -8
  210. package/src/topBar/index.ts +21 -44
  211. package/src/walkthrough/index.ts +6 -20
  212. package/src/walkthrough/panel.ts +40 -10
  213. package/style/about.css +2 -7
  214. package/style/askAgent.css +8 -40
  215. package/style/base.css +9 -45
  216. package/style/chrome.css +108 -308
  217. package/style/commandBar.css +4 -20
  218. package/style/customPanel.css +1 -4
  219. package/style/git.css +22 -184
  220. package/style/highlight.css +1 -3
  221. package/style/launcher.css +6 -70
  222. package/style/omnibox.css +2 -14
  223. package/style/showOutput.css +0 -6
  224. package/style/sidebar.css +29 -9
  225. package/style/tabs.css +5 -13
  226. package/style/terminals.css +6 -53
  227. package/style/topBar.css +2 -22
  228. package/style/walkthrough.css +0 -8
@@ -9,24 +9,13 @@ import type { IAgent } from './agents';
9
9
  import { buildAgentInvocation } from './invocation';
10
10
  import { agentCommandId } from './tokens';
11
11
 
12
- /**
13
- * The command id for opening the launcher.
14
- */
15
12
  export const CREATE_LAUNCHER_COMMAND = 'launcher:create';
16
13
 
17
14
  /**
18
- * Register a JupyterLab command per agent. Each command opens a new
19
- * terminal via `terminal:create-new` and feeds the agent's shell command
20
- * into the fresh session as if the user had typed it.
21
- *
22
- * Returns a single disposable that tears down every registered command —
23
- * the launcher disposes the previous set before re-registering on settings
24
- * changes so the command palette stays in sync with the configured agents.
25
- *
26
- * When an `agentSessions` registry is supplied, each launch tags its terminal
27
- * session with the agent's command, so the terminals panel can badge the row
28
- * with the agent's logo immediately (before server-side detection confirms
29
- * it).
15
+ * Register a JupyterLab command per agent that opens a new terminal and types
16
+ * the agent's shell command into it. Returns one disposable tearing down every
17
+ * command, so a settings change can re-register without stale palette entries.
18
+ * `agentSessions`, when given, launch-tags each session for the terminals panel.
30
19
  */
31
20
  export function registerAgentCommands(
32
21
  app: JupyterFrontEnd,
@@ -48,9 +37,7 @@ export function registerAgentCommands(
48
37
  cwd,
49
38
  invocation,
50
39
  label: agent.label,
51
- // Optimistically tag the session with this agent so the terminals
52
- // panel can show its logo right away; server-side detection takes
53
- // over as the source of truth on its next poll.
40
+ // Optimistic tag; server-side detection takes over on its next poll.
54
41
  onSession: name => agentSessions?.set(name, agent.command)
55
42
  });
56
43
  }
@@ -62,29 +49,15 @@ export function registerAgentCommands(
62
49
 
63
50
  /**
64
51
  * Open a fresh terminal, type `invocation` into it as if the user had, and
65
- * label the tab. Shared by the per-agent launch commands and the launcher's
66
- * editor tile so the fiddly "wait for the websocket, then write stdin"
67
- * sequence lives in one place.
68
- *
69
- * `onSession`, when given, is called once with the new session's name right
70
- * after the terminal is revealed. Callers use it to optimistically tag the
71
- * session so the terminals panel can badge it with the agent's (or editor's)
72
- * logo before server-side detection confirms the running process.
73
- *
74
- * Resolves with the host `MainAreaWidget` so the caller can place it — the
75
- * launcher swaps it into its own tab.
52
+ * label the tab. `onSession` receives the new session's name so callers can
53
+ * launch-tag it for the terminals panel. Resolves with the host
54
+ * `MainAreaWidget` so the caller can place it.
76
55
  */
77
56
  export async function launchInTerminal(
78
57
  commands: CommandRegistry,
79
58
  options: {
80
59
  cwd?: string;
81
- /**
82
- * The literal command line typed into the fresh terminal.
83
- */
84
60
  invocation: string;
85
- /**
86
- * Tab/title label applied once the command is sent.
87
- */
88
61
  label: string;
89
62
  onSession?: (sessionName: string) => void;
90
63
  }
@@ -93,10 +66,8 @@ export async function launchInTerminal(
93
66
  const main = (await commands.execute('terminal:create-new', {
94
67
  cwd
95
68
  })) as MainAreaWidget<ITerminal.ITerminal>;
96
- // `MainAreaWidget.revealed` chains off the `reveal` promise the terminal
97
- // extension passes in (the xterm.js widget's own `ready` promise), so
98
- // awaiting it guarantees the Terminal widget has finished its constructor
99
- // and connected its session listeners.
69
+ // `revealed` chains off the terminal's `ready` promise, so awaiting it
70
+ // guarantees the widget finished construction and connected its listeners.
100
71
  await main.revealed;
101
72
 
102
73
  const session = main.content.session;
@@ -114,21 +85,13 @@ export async function launchInTerminal(
114
85
  type: 'stdin',
115
86
  content: [invocation + '\r']
116
87
  });
117
- // Give the tab and the status-bar list a meaningful default label. The
118
- // XTerm widget's `_initialConnection` listener overwrites the title with
119
- // `Terminal {N}` when the WebSocket first reports `connected`; we connect
120
- // *after* that listener (and call `applyLabel` after `sendCommand`), so by
121
- // the time we run the upstream listener has already had its turn. Programs
122
- // that publish a real xterm title escape sequence still win —
123
- // `XTerm.onTitleChange` resets the label whenever it fires.
88
+ // XTerm's `_initialConnection` resets the title on first connect; we run
89
+ // after it, so this label survives. A title escape still wins (`onTitleChange`).
124
90
  applyLabel();
125
91
  };
126
92
 
127
- // Match how the Terminal widget itself sends its `initialCommand`: either
128
- // fire immediately if the session is already connected, or wait for the next
129
- // `connectionStatusChanged` that flips it to `connected`. Without this the
130
- // stdin write races the websocket handshake and the command silently
131
- // disappears.
93
+ // Without waiting for `connected` the stdin write races the websocket
94
+ // handshake and the command silently disappears.
132
95
  if (session.connectionStatus === 'connected') {
133
96
  sendCommand();
134
97
  } else {
@@ -33,46 +33,40 @@ import type { IEditor } from './editors';
33
33
  import { agentCommandId } from './tokens';
34
34
 
35
35
  /**
36
- * Public construction options for the launcher dashboard widget. The
37
- * plugin supplies an `onAgentLaunch` callback that performs the same
38
- * shell placement the stock JupyterLab launcher does (swap the launcher
39
- * tab for the freshly created terminal). Keeping it as an opaque
40
- * callback means the widget itself does not import `app.shell`.
36
+ * Construction options for the launcher dashboard. `onAgentLaunch` is an
37
+ * opaque callback so the widget itself never imports `app.shell`.
41
38
  */
42
39
  interface ILauncherDashboardOptions {
40
+ /**
41
+ * The application command registry, used to launch terminals and run git
42
+ * commands.
43
+ */
43
44
  commands: CommandRegistry;
44
45
  /**
45
- * The agent list to render. Already filtered by availability + sorted by
46
- * rank; the widget renders them as-is.
46
+ * Agent list to render, already filtered by availability and sorted by rank.
47
47
  */
48
48
  agents: IAgent[];
49
49
  /**
50
- * The terminal editor to offer in the "Open" section (Neovim preferred over
51
- * Vim), or `null` when neither is installed. Resolved by the plugin from the
52
- * same availability probe that filters the agents.
50
+ * Terminal editor for the "Open" section, or `null` when none is installed.
53
51
  */
54
52
  editor: IEditor | null;
55
53
  /**
56
- * Shared launch-tag registry. When the editor tile launches, it records the
57
- * session here so the terminals panel badges it with the editor's logo right
58
- * away, the same as agent launches do. `null` when the provider plugin is
59
- * unavailable.
54
+ * Launch-tag registry so the terminals panel badges editor launches right
55
+ * away; `null` when the provider plugin is unavailable.
60
56
  */
61
57
  agentSessions: IAgentSessions | null;
62
58
  /**
63
- * Called after an agent command has returned its top-level widget. The
64
- * plugin uses this to swap the new terminal into the launcher's tab.
59
+ * Called with the launched widget; the plugin swaps it into the launcher's tab.
65
60
  */
66
61
  onAgentLaunch: (item: Widget) => void;
67
62
  /**
68
- * The git server-relative repo path used by the changes section. Empty
69
- * string means "use the JupyterLab server's root and let git resolve the
70
- * enclosing repo" — same convention as the git panel.
63
+ * Repo path for the changes section. Empty string lets git resolve the
64
+ * enclosing repo from the server root — same convention as the git panel.
71
65
  */
72
66
  repoPath: string;
73
67
  /**
74
- * The cwd forwarded to `terminal:create-new` when an agent is launched.
75
- * Empty string lets the terminal extension default to the server root.
68
+ * Cwd forwarded to `terminal:create-new`. Empty string defaults to the
69
+ * server root.
76
70
  */
77
71
  cwd: string;
78
72
  /**
@@ -83,9 +77,7 @@ interface ILauncherDashboardOptions {
83
77
  }
84
78
 
85
79
  /**
86
- * Lumino host for the React-based launcher dashboard. Mirrors the
87
- * `ReactWidget` pattern used by the git panel so the rest of the plugin
88
- * (layout placement, signals, lifecycle) stays consistent.
80
+ * Lumino host for the React-based launcher dashboard.
89
81
  */
90
82
  export class LauncherDashboard extends ReactWidget {
91
83
  constructor(options: ILauncherDashboardOptions) {
@@ -94,6 +86,9 @@ export class LauncherDashboard extends ReactWidget {
94
86
  this._options = options;
95
87
  }
96
88
 
89
+ /**
90
+ * Render the launcher dashboard component.
91
+ */
97
92
  protected render(): React.ReactElement {
98
93
  return <LauncherDashboardComponent {...this._options} />;
99
94
  }
@@ -101,24 +96,31 @@ export class LauncherDashboard extends ReactWidget {
101
96
  private _options: ILauncherDashboardOptions;
102
97
  }
103
98
 
99
+ /**
100
+ * State of the changes-section git status fetch.
101
+ */
104
102
  interface IGitState {
103
+ /**
104
+ * Whether the initial status fetch is still pending.
105
+ */
105
106
  loading: boolean;
107
+ /**
108
+ * The latest status result, or `null` before the first success or after a
109
+ * failure.
110
+ */
106
111
  result: IGitStatusResult | null;
112
+ /**
113
+ * The latest fetch error message, or `null` when the last fetch succeeded.
114
+ */
107
115
  error: string | null;
108
116
  }
109
117
 
110
118
  /**
111
- * Refresh interval for the changes list. A bit slower than the git panel
112
- * (which sits in the sidebar and is always visible) — the launcher tab is
113
- * only opened intentionally, and the grid does not need to be live.
119
+ * Changes-list refresh interval; slower than the git panel since the launcher
120
+ * tab is only opened intentionally.
114
121
  */
115
122
  const CHANGES_POLL_INTERVAL_MS = 8000;
116
123
 
117
- /**
118
- * Upper bound on the exponential backoff when the status request fails
119
- * repeatedly. Matches the rest of the plugin's polls so behavior is
120
- * consistent across the dashboard, the git panel, and the file tree.
121
- */
122
124
  const CHANGES_POLL_MAX_MS = 300_000;
123
125
 
124
126
  function LauncherDashboardComponent(
@@ -154,9 +156,6 @@ function LauncherDashboardComponent(
154
156
  }
155
157
  }, [repoPath]);
156
158
 
157
- // Driven by a Lumino `Poll` so the dashboard stops hitting the git
158
- // endpoint while the JupyterLab tab is hidden, and so repeated
159
- // failures back off exponentially instead of pegging the 8s cadence.
160
159
  React.useEffect(() => {
161
160
  const poll = new Poll({
162
161
  name: '@xtralab/launcher:gitChanges',
@@ -201,12 +200,8 @@ function LauncherDashboardComponent(
201
200
  }, [commands, cwd, onAgentLaunch]);
202
201
 
203
202
  const launchNotebook = React.useCallback(async () => {
204
- // Open a fresh notebook with no kernel attached — equivalent to clicking
205
- // "No Kernel" in the Select Kernel dialog. `notebook:create-new` always
206
- // routes through that dialog when no kernelName is given, so drive the
207
- // underlying docmanager steps directly and pass `shouldStart: false` to
208
- // suppress kernel selection. The user picks a kernel from the notebook
209
- // toolbar when they're ready.
203
+ // `notebook:create-new` always opens the Select Kernel dialog when no
204
+ // kernelName is given, so drive docmanager directly with `shouldStart: false`.
210
205
  const file = (await commands.execute('docmanager:new-untitled', {
211
206
  path: cwd,
212
207
  type: 'notebook'
@@ -220,8 +215,7 @@ function LauncherDashboardComponent(
220
215
  kernelPreference: { shouldStart: false, canStart: true }
221
216
  })) as IDocumentWidget | undefined;
222
217
  if (result) {
223
- // Match `notebook:create-new`'s post-open marker so the first manual
224
- // save offers a rename instead of overwriting the auto-generated name.
218
+ // Match `notebook:create-new`'s marker so the first save offers a rename.
225
219
  result.isUntitled = true;
226
220
  onAgentLaunch(result);
227
221
  }
@@ -240,10 +234,6 @@ function LauncherDashboardComponent(
240
234
  if (!editor) {
241
235
  return;
242
236
  }
243
- // An editor is "open a terminal and type its command" — the agent launch
244
- // minus the prompt — so it shares `launchInTerminal` rather than the native
245
- // `terminal:create-new` call the tiles above use, and tags the session the
246
- // same way so the terminals panel badges it with the editor's logo.
247
237
  const result = await launchInTerminal(commands, {
248
238
  cwd,
249
239
  invocation: editor.command,
@@ -301,10 +291,6 @@ function ChangesSection(props: {
301
291
  const { git, onOpen, onRefresh, trans } = props;
302
292
  const files = git.result ? expandStatusFiles(git.result.files) : [];
303
293
 
304
- // Native `<details>` rather than a hand-rolled toggle: we get the
305
- // disclosure triangle, keyboard handling, and aria-expanded semantics
306
- // for free, and the user's open/closed choice is tab-local state that
307
- // we don't need to persist.
308
294
  return (
309
295
  <details
310
296
  className="jp-xtralab-Launcher-section jp-xtralab-Launcher-changes-section"
@@ -330,8 +316,7 @@ function ChangesSection(props: {
330
316
  type="button"
331
317
  className="jp-xtralab-Launcher-section-action"
332
318
  aria-label={trans.__('Refresh changes')}
333
- // Stop the click from toggling the surrounding `<details>` —
334
- // refreshing while the section is open shouldn't collapse it.
319
+ // Keep the click from toggling the surrounding `<details>`.
335
320
  onClick={event => {
336
321
  event.preventDefault();
337
322
  event.stopPropagation();
@@ -417,9 +402,6 @@ function AgentSection(props: {
417
402
  const { agents, prompt, onPromptChange, onLaunch, trans } = props;
418
403
  const trimmed = prompt.trim();
419
404
  const promptActive = trimmed.length > 0;
420
- // The first prompt-capable agent is what Cmd/Ctrl+Enter fires; the hint
421
- // text below the textarea names it so the keyboard shortcut target
422
- // isn't a mystery.
423
405
  const primaryAgent = agents.find(agent => agent.promptArgs !== undefined);
424
406
  const hintId = 'jp-xtralab-Launcher-agent-hint';
425
407
 
@@ -501,24 +483,17 @@ function AgentSection(props: {
501
483
  }
502
484
 
503
485
  /**
504
- * Launcher agents whose CLI has a clean, non-interactive `mcp add` that takes
505
- * `<name> -- <command>`. The other agents (Goose, OpenCode, Kiro, Mistral
506
- * Vibe, Antigravity) register MCP through their own config files or UI, so
507
- * there is no one-line command to surface for them.
486
+ * Agents whose CLI has a non-interactive `mcp add <name> -- <command>`; the
487
+ * other agents register MCP through their own config, so no one-liner exists.
508
488
  */
509
489
  const MCP_ADD_AGENT_IDS = new Set(['claude', 'codex', 'copilot']);
510
490
 
511
- /**
512
- * The stdio proxy console script that bridges an agent to the MCP server.
513
- */
514
491
  const MCP_PROXY_COMMAND = 'jupyter-server-mcp-proxy';
515
492
 
516
493
  /**
517
- * Copy `text` to the clipboard via a hidden, selected `<textarea>` and the
518
- * legacy `execCommand('copy')`. Deprecated, but still the most reliable path
519
- * under a user gesture when the async Clipboard API is unavailable or blocked
520
- * (the desktop app's Electron window can deny clipboard writes by policy).
521
- * Returns whether the copy succeeded.
494
+ * Copy via a hidden `<textarea>` and the legacy `execCommand('copy')`:
495
+ * deprecated, but the reliable fallback when the async Clipboard API is
496
+ * missing or blocked (Electron can deny it by policy). Returns success.
522
497
  */
523
498
  function legacyCopy(text: string): boolean {
524
499
  try {
@@ -539,13 +514,10 @@ function legacyCopy(text: string): boolean {
539
514
  }
540
515
 
541
516
  /**
542
- * A collapsed-by-default hint listing the one-line command that registers
543
- * xtralab's running Jupyter MCP server with each installed agent that
544
- * supports `mcp add`. Copy-only: the user runs it in their own terminal,
545
- * where the server is reachable with no port to configure — the desktop
546
- * supervisor exports `JUPYTER_SERVER_MCP_URL`, and `pip` installs let the
547
- * proxy discover the server from its runtime file. Renders nothing when none
548
- * of those agents are on `$PATH`.
517
+ * Collapsed-by-default hint listing the `mcp add` one-liner per installed
518
+ * agent. Copy-only: run in the user's own terminal, where the proxy finds the
519
+ * server with no port (desktop exports `JUPYTER_SERVER_MCP_URL`; pip installs
520
+ * discover it from the runtime file). Renders nothing when no agent qualifies.
549
521
  */
550
522
  function McpSection(props: {
551
523
  agents: IAgent[];
@@ -569,9 +541,6 @@ function McpSection(props: {
569
541
  };
570
542
 
571
543
  const copy = (command: string, id: string): void => {
572
- // Prefer the async Clipboard API; fall back to `legacyCopy` when it's
573
- // missing or rejects (Electron can block it by policy). Flag "Copied" only
574
- // once a copy actually succeeds.
575
544
  const clipboard = navigator.clipboard;
576
545
  if (clipboard) {
577
546
  void clipboard.writeText(command).then(
@@ -638,16 +607,8 @@ function McpSection(props: {
638
607
  }
639
608
 
640
609
  /**
641
- * The non-agent launch tiles — a plain terminal, a new notebook, and a
642
- * new console. They sit in a separate section below the agent grid so
643
- * they don't compete with the AI agents visually, and they're always
644
- * available regardless of the agent prompt textarea (which doesn't
645
- * apply to them).
646
- *
647
- * A terminal editor (Neovim, else Vim) joins the row when one is installed —
648
- * it opens in a terminal like the agents do, so it lives here rather than in
649
- * the agent grid. When neither is on `$PATH`, `editor` is `null` and no tile
650
- * renders.
610
+ * The non-agent launch tiles: terminal, notebook, and console, plus the
611
+ * terminal editor tile when one is installed (`editor` is `null` otherwise).
651
612
  */
652
613
  function OpenSection(props: {
653
614
  editor: IEditor | null;
@@ -19,37 +19,28 @@ import {
19
19
  } from './schemaDefaults';
20
20
 
21
21
  /**
22
- * A read-only, observable view of the launcher's terminal editors: xtralab's
23
- * built-ins (Neovim, Vim) merged with the user's `editors` setting, sorted by
24
- * rank, with disabled entries removed.
22
+ * A read-only, observable view of the launcher's terminal editors: the
23
+ * built-ins merged with the user's `editors` setting.
25
24
  */
26
25
  export interface IEditorRegistry {
27
26
  /**
28
- * The full merged editor list (disabled entries already removed). The
29
- * terminals panel uses it to badge a row for any configured editor that is
30
- * running, and to tell the server which editor commands to detect.
27
+ * The merged editor list (disabled entries removed); the terminals panel
28
+ * uses it to badge running editors.
31
29
  */
32
30
  readonly editors: IEditor[];
33
31
 
34
32
  /**
35
- * The single editor tile the launcher shows: the first editor, by rank, whose
36
- * command is on `$PATH` (Neovim before Vim by default), or `null` when none
37
- * qualifies.
33
+ * The single launcher tile: the first editor, by rank, whose command is on
34
+ * `$PATH`, or `null` when none qualifies.
38
35
  */
39
36
  readonly current: IEditor | null;
40
37
 
41
38
  /**
42
- * Emitted whenever {@link editors} or {@link current} changes (the user
43
- * edited the `editors` setting). Consumers that read on demand can ignore it.
39
+ * Emitted whenever {@link editors} or {@link current} changes.
44
40
  */
45
41
  readonly changed: ISignal<IEditorRegistry, void>;
46
42
  }
47
43
 
48
- /**
49
- * DI token for {@link IEditorRegistry}. Provided by `xtralab:editor-registry`
50
- * and consumed — optionally — by `xtralab:launcher` (for its tile) and
51
- * `xtralab:terminals` (to badge running editors with their logo).
52
- */
53
44
  export const IEditorRegistry = new Token<IEditorRegistry>(
54
45
  'xtralab:IEditorRegistry',
55
46
  'A read-only, observable view of the launcher terminal editors, shared so the launcher tile and the terminals panel agree on the list and icons.'
@@ -57,23 +48,32 @@ export const IEditorRegistry = new Token<IEditorRegistry>(
57
48
 
58
49
  /**
59
50
  * Concrete {@link IEditorRegistry}. The provider plugin is the only writer, so
60
- * the write side ({@link set}) is kept off the shared token.
51
+ * {@link set} is kept off the shared token.
61
52
  */
62
53
  class EditorRegistry implements IEditorRegistry {
54
+ /**
55
+ * The merged editor list (disabled entries removed).
56
+ */
63
57
  get editors(): IEditor[] {
64
58
  return this._editors;
65
59
  }
66
60
 
61
+ /**
62
+ * The launcher-tile editor, or `null` when none qualifies.
63
+ */
67
64
  get current(): IEditor | null {
68
65
  return this._current;
69
66
  }
70
67
 
68
+ /**
69
+ * Emitted whenever {@link editors} or {@link current} changes.
70
+ */
71
71
  get changed(): ISignal<IEditorRegistry, void> {
72
72
  return this._changed;
73
73
  }
74
74
 
75
75
  /**
76
- * Replace both lists (recomputed together) and notify observers.
76
+ * Replace both lists and notify observers.
77
77
  */
78
78
  set(editors: IEditor[], current: IEditor | null): void {
79
79
  this._editors = editors;
@@ -87,12 +87,9 @@ class EditorRegistry implements IEditorRegistry {
87
87
  }
88
88
 
89
89
  /**
90
- * Provides {@link IEditorRegistry}: merges xtralab's built-in editors with the
91
- * user's `editors` setting, probes the server's `$PATH` to resolve the launcher
92
- * tile, and re-runs both on a settings change.
93
- *
94
- * `activate` awaits the first probe before returning, so a consumer reading
95
- * {@link IEditorRegistry.current} on activation sees a resolved tile.
90
+ * Provides {@link IEditorRegistry}: merges the built-in editors with the
91
+ * `editors` setting and probes the server's `$PATH`. `activate` awaits the
92
+ * first probe, so consumers see a resolved tile on activation.
96
93
  */
97
94
  export const editorRegistryPlugin: JupyterFrontEndPlugin<IEditorRegistry> = {
98
95
  id: 'xtralab:editor-registry',
@@ -109,9 +106,6 @@ export const editorRegistryPlugin: JupyterFrontEndPlugin<IEditorRegistry> = {
109
106
 
110
107
  const apply = async (overrides: IEditorSettings[]): Promise<void> => {
111
108
  const editors = mergeEditors(overrides);
112
- // Probe only the editors that opt into the availability check; entries
113
- // with `requireAvailable: false` (e.g. a shell alias) are shown
114
- // regardless and don't need a `which` lookup.
115
109
  const probe = Array.from(
116
110
  new Set(
117
111
  editors
@@ -146,8 +140,6 @@ export const editorRegistryPlugin: JupyterFrontEndPlugin<IEditorRegistry> = {
146
140
  });
147
141
  } catch (reason) {
148
142
  console.error('xtralab: failed to load editor settings', reason);
149
- // Settings load failed — fall back to the built-in editors so the
150
- // launcher tile still resolves instead of going silent.
151
143
  await apply([]);
152
144
  }
153
145
  } else {