xtralab 0.14.1 → 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 (236) 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.d.ts +7 -0
  29. package/lib/customPanel/index.js +89 -0
  30. package/lib/editorBreadcrumbs/index.d.ts +3 -6
  31. package/lib/editorBreadcrumbs/index.js +10 -6
  32. package/lib/editorBreadcrumbs/widget.d.ts +17 -4
  33. package/lib/editorBreadcrumbs/widget.js +11 -17
  34. package/lib/editorIndent/index.d.ts +4 -12
  35. package/lib/editorIndent/index.js +8 -23
  36. package/lib/fileBrowser/commands.d.ts +28 -19
  37. package/lib/fileBrowser/commands.js +51 -98
  38. package/lib/fileBrowser/contents.d.ts +14 -0
  39. package/lib/fileBrowser/contents.js +28 -0
  40. package/lib/fileBrowser/dragAndDrop.d.ts +79 -0
  41. package/lib/fileBrowser/dragAndDrop.js +110 -0
  42. package/lib/fileBrowser/fileBrowser.d.ts +21 -9
  43. package/lib/fileBrowser/fileBrowser.js +211 -405
  44. package/lib/fileBrowser/gitStatus.d.ts +4 -9
  45. package/lib/fileBrowser/gitStatus.js +7 -17
  46. package/lib/fileBrowser/gitignore.d.ts +4 -17
  47. package/lib/fileBrowser/gitignore.js +4 -25
  48. package/lib/fileBrowser/icons.d.ts +7 -17
  49. package/lib/fileBrowser/icons.js +25 -80
  50. package/lib/fileBrowser/index.js +41 -10
  51. package/lib/fileBrowser/widget.d.ts +170 -53
  52. package/lib/fileBrowser/widget.js +242 -18
  53. package/lib/fileTypeIcons/index.js +0 -10
  54. package/lib/git/api.d.ts +5 -13
  55. package/lib/git/api.js +10 -36
  56. package/lib/git/askRequest.d.ts +4 -8
  57. package/lib/git/askRequest.js +6 -20
  58. package/lib/git/commands.d.ts +40 -5
  59. package/lib/git/commands.js +16 -10
  60. package/lib/git/diffModel.js +4 -0
  61. package/lib/git/diffSurface.d.ts +41 -55
  62. package/lib/git/diffSurface.js +42 -117
  63. package/lib/git/diffWidget.d.ts +81 -2
  64. package/lib/git/diffWidget.js +62 -12
  65. package/lib/git/imageDiff.d.ts +5 -6
  66. package/lib/git/imageDiff.js +18 -48
  67. package/lib/git/index.d.ts +0 -3
  68. package/lib/git/index.js +3 -8
  69. package/lib/git/notebookDiff.d.ts +110 -33
  70. package/lib/git/notebookDiff.js +52 -195
  71. package/lib/git/tokens.d.ts +73 -31
  72. package/lib/git/tokens.js +2 -3
  73. package/lib/highlight/index.d.ts +4 -10
  74. package/lib/highlight/index.js +10 -41
  75. package/lib/index.d.ts +0 -9
  76. package/lib/index.js +2 -9
  77. package/lib/launcher/agents.d.ts +55 -51
  78. package/lib/launcher/agents.js +25 -54
  79. package/lib/launcher/availability.d.ts +3 -9
  80. package/lib/launcher/availability.js +3 -9
  81. package/lib/launcher/commands.d.ts +7 -32
  82. package/lib/launcher/commands.js +14 -45
  83. package/lib/launcher/dashboard.d.ts +19 -24
  84. package/lib/launcher/dashboard.js +21 -67
  85. package/lib/launcher/editorRegistry.d.ts +10 -22
  86. package/lib/launcher/editorRegistry.js +14 -18
  87. package/lib/launcher/editors.d.ts +39 -45
  88. package/lib/launcher/editors.js +8 -34
  89. package/lib/launcher/icons.d.ts +0 -12
  90. package/lib/launcher/icons.js +14 -37
  91. package/lib/launcher/index.js +12 -55
  92. package/lib/launcher/invocation.d.ts +4 -10
  93. package/lib/launcher/invocation.js +7 -22
  94. package/lib/launcher/registry.d.ts +9 -7
  95. package/lib/launcher/registry.js +9 -7
  96. package/lib/launcher/schemaDefaults.d.ts +2 -9
  97. package/lib/launcher/schemaDefaults.js +37 -13
  98. package/lib/launcher/tokens.d.ts +10 -22
  99. package/lib/launcher/tokens.js +5 -8
  100. package/lib/menuBar/index.d.ts +3 -10
  101. package/lib/menuBar/index.js +11 -28
  102. package/lib/menus/index.d.ts +3 -9
  103. package/lib/menus/index.js +14 -30
  104. package/lib/omnibox/files.d.ts +2 -4
  105. package/lib/omnibox/files.js +5 -11
  106. package/lib/omnibox/index.d.ts +4 -14
  107. package/lib/omnibox/index.js +9 -27
  108. package/lib/omnibox/model.d.ts +38 -10
  109. package/lib/omnibox/model.js +15 -33
  110. package/lib/omnibox/recents.d.ts +15 -17
  111. package/lib/omnibox/recents.js +12 -17
  112. package/lib/omnibox/tokens.d.ts +5 -10
  113. package/lib/omnibox/tokens.js +2 -6
  114. package/lib/omnibox/widget.d.ts +11 -7
  115. package/lib/omnibox/widget.js +8 -16
  116. package/lib/searchReplace/index.d.ts +4 -20
  117. package/lib/searchReplace/index.js +7 -27
  118. package/lib/showOutput/index.d.ts +3 -12
  119. package/lib/showOutput/index.js +6 -22
  120. package/lib/sidebar/index.d.ts +4 -13
  121. package/lib/sidebar/index.js +73 -46
  122. package/lib/terminalNotifications/index.d.ts +4 -10
  123. package/lib/terminalNotifications/index.js +6 -20
  124. package/lib/terminals/agentTerminals.d.ts +12 -5
  125. package/lib/terminals/agentTerminals.js +25 -44
  126. package/lib/terminals/detection.d.ts +4 -8
  127. package/lib/terminals/detection.js +4 -8
  128. package/lib/terminals/index.d.ts +0 -33
  129. package/lib/terminals/index.js +24 -102
  130. package/lib/terminals/model.d.ts +64 -139
  131. package/lib/terminals/model.js +80 -220
  132. package/lib/terminals/tokens.d.ts +11 -22
  133. package/lib/terminals/tokens.js +2 -2
  134. package/lib/terminals/widget.d.ts +72 -38
  135. package/lib/terminals/widget.js +173 -73
  136. package/lib/topBar/icons.d.ts +3 -8
  137. package/lib/topBar/icons.js +3 -8
  138. package/lib/topBar/index.d.ts +4 -11
  139. package/lib/topBar/index.js +10 -38
  140. package/lib/walkthrough/index.d.ts +4 -9
  141. package/lib/walkthrough/index.js +6 -20
  142. package/lib/walkthrough/panel.d.ts +40 -9
  143. package/lib/walkthrough/panel.js +4 -10
  144. package/package.json +2 -2
  145. package/schema/launcher.json +2 -2
  146. package/schema/sidebar.json +47 -7
  147. package/src/about/icons.ts +0 -5
  148. package/src/about/index.tsx +6 -17
  149. package/src/agentSessions.ts +9 -29
  150. package/src/askAgent/editorSelection.ts +22 -29
  151. package/src/askAgent/icons.ts +2 -3
  152. package/src/askAgent/index.ts +54 -158
  153. package/src/askAgent/popup.tsx +23 -45
  154. package/src/askAgent/prompt.ts +6 -19
  155. package/src/askAgent/queue.ts +13 -24
  156. package/src/askAgent/queuePanel.tsx +21 -46
  157. package/src/askAgent/targetPicker.tsx +5 -15
  158. package/src/askAgent/tokens.ts +32 -46
  159. package/src/commandBar/index.ts +24 -39
  160. package/src/customPanel/index.ts +119 -0
  161. package/src/editorBreadcrumbs/index.ts +10 -6
  162. package/src/editorBreadcrumbs/widget.ts +23 -17
  163. package/src/editorIndent/index.ts +17 -23
  164. package/src/fileBrowser/commands.ts +70 -98
  165. package/src/fileBrowser/contents.ts +33 -0
  166. package/src/fileBrowser/dragAndDrop.ts +188 -0
  167. package/src/fileBrowser/fileBrowser.tsx +259 -440
  168. package/src/fileBrowser/gitStatus.ts +7 -17
  169. package/src/fileBrowser/gitignore.ts +4 -25
  170. package/src/fileBrowser/icons.ts +25 -80
  171. package/src/fileBrowser/index.ts +43 -10
  172. package/src/fileBrowser/widget.tsx +344 -64
  173. package/src/fileTypeIcons/index.ts +0 -10
  174. package/src/git/api.ts +10 -36
  175. package/src/git/askRequest.ts +6 -20
  176. package/src/git/commands.ts +70 -10
  177. package/src/git/diffModel.ts +25 -0
  178. package/src/git/diffSurface.tsx +71 -159
  179. package/src/git/diffWidget.tsx +104 -14
  180. package/src/git/imageDiff.tsx +45 -51
  181. package/src/git/index.ts +3 -8
  182. package/src/git/notebookDiff.tsx +211 -238
  183. package/src/git/tokens.ts +73 -31
  184. package/src/highlight/index.ts +10 -41
  185. package/src/index.ts +2 -9
  186. package/src/launcher/agents.ts +74 -92
  187. package/src/launcher/availability.ts +3 -9
  188. package/src/launcher/commands.ts +14 -51
  189. package/src/launcher/dashboard.tsx +49 -88
  190. package/src/launcher/editorRegistry.ts +21 -29
  191. package/src/launcher/editors.ts +39 -58
  192. package/src/launcher/icons.ts +15 -37
  193. package/src/launcher/index.ts +12 -55
  194. package/src/launcher/invocation.ts +7 -22
  195. package/src/launcher/registry.ts +9 -7
  196. package/src/launcher/schemaDefaults.ts +43 -13
  197. package/src/launcher/tokens.ts +10 -22
  198. package/src/menuBar/index.ts +11 -28
  199. package/src/menus/index.ts +14 -30
  200. package/src/omnibox/files.ts +10 -14
  201. package/src/omnibox/index.ts +9 -27
  202. package/src/omnibox/model.ts +50 -38
  203. package/src/omnibox/recents.ts +17 -22
  204. package/src/omnibox/tokens.ts +5 -10
  205. package/src/omnibox/widget.tsx +14 -19
  206. package/src/searchReplace/index.ts +7 -27
  207. package/src/showOutput/index.ts +6 -22
  208. package/src/sidebar/index.ts +103 -52
  209. package/src/terminalNotifications/index.ts +42 -25
  210. package/src/terminals/agentTerminals.ts +25 -49
  211. package/src/terminals/detection.ts +4 -8
  212. package/src/terminals/index.ts +31 -102
  213. package/src/terminals/model.ts +130 -246
  214. package/src/terminals/tokens.ts +11 -22
  215. package/src/terminals/widget.tsx +229 -113
  216. package/src/topBar/icons.ts +3 -8
  217. package/src/topBar/index.ts +21 -44
  218. package/src/walkthrough/index.ts +6 -20
  219. package/src/walkthrough/panel.ts +40 -10
  220. package/style/about.css +2 -7
  221. package/style/askAgent.css +8 -40
  222. package/style/base.css +25 -73
  223. package/style/chrome.css +108 -308
  224. package/style/commandBar.css +4 -20
  225. package/style/customPanel.css +6 -0
  226. package/style/git.css +22 -184
  227. package/style/highlight.css +1 -3
  228. package/style/index.js +1 -0
  229. package/style/launcher.css +6 -70
  230. package/style/omnibox.css +2 -14
  231. package/style/showOutput.css +0 -6
  232. package/style/sidebar.css +29 -9
  233. package/style/tabs.css +5 -13
  234. package/style/terminals.css +18 -123
  235. package/style/topBar.css +2 -22
  236. package/style/walkthrough.css +0 -8
@@ -12,16 +12,13 @@ export interface IAgentTerminalSession {
12
12
  name: string;
13
13
 
14
14
  /**
15
- * The agent identifier server-side detection reported for the session:
16
- * the configured `command`, or the canonical agent `id` when the command
17
- * is an alias (e.g. `ccm` spawning `claude`). Matches an `IAgent` by
18
- * either field.
15
+ * The agent identifier detection reported: the configured `command`, or the
16
+ * canonical `id` when the command is an alias (e.g. `ccm` spawning `claude`).
19
17
  */
20
18
  command: string;
21
19
 
22
20
  /**
23
- * Display label — the session's real title (the launcher's agent name, or
24
- * whatever the running program published via an xterm escape sequence).
21
+ * Display label — the session's real (program-published) title.
25
22
  */
26
23
  label: string;
27
24
 
@@ -35,12 +32,9 @@ export interface IAgentTerminalSession {
35
32
  /**
36
33
  * Running agent terminals, and a way to send a prompt into one.
37
34
  *
38
- * `sessions` only lists sessions where server-side process detection has
39
- * *confirmed* a running coding agent (editors and plain shells are excluded,
40
- * as are optimistic just-launched tags): every listed session is one whose
41
- * TUI can meaningfully receive pasted prompt text. `sendPrompt` re-validates
42
- * against the server before writing, since pasting prose into a shell prompt
43
- * would execute it.
35
+ * Only sessions with a detection-*confirmed* running agent are listed — never
36
+ * optimistic launch tags: pasted prompt text must land in an agent's TUI, not
37
+ * a shell prompt where it would be executed.
44
38
  */
45
39
  export interface IAgentTerminals {
46
40
  /**
@@ -54,22 +48,17 @@ export interface IAgentTerminals {
54
48
  readonly changed: ISignal<IAgentTerminals, void>;
55
49
 
56
50
  /**
57
- * Paste `prompt` into the named session's terminal and press Enter.
58
- *
59
- * Delivery is deliberately background: the text is written straight to
60
- * the session's websocket, bracketed-paste-wrapped so a multi-line prompt
61
- * arrives as one block in the agent's input box, without revealing or
62
- * focusing the terminal's tab — the user keeps working where they are,
63
- * and the agent CLIs queue the prompt themselves when they are busy.
64
- * Rejects when the session is gone or no agent is running in it anymore.
51
+ * Paste `prompt` (bracketed-paste-wrapped) into the named session and press
52
+ * Enter, writing straight to its websocket without revealing or focusing
53
+ * its tab. Rejects when the session is gone or no agent runs in it anymore.
65
54
  */
66
55
  sendPrompt(name: string, prompt: string): Promise<void>;
67
56
  }
68
57
 
69
58
  /**
70
59
  * DI token for {@link IAgentTerminals}. Provided by `xtralab:terminals`;
71
- * consumed — optionally, so each side works without the other — by
72
- * `xtralab:ask-agent` to offer running agent terminals as prompt targets.
60
+ * consumed optionally by `xtralab:ask-agent`, so each side works without
61
+ * the other.
73
62
  */
74
63
  export const IAgentTerminals = new Token<IAgentTerminals>(
75
64
  'xtralab:IAgentTerminals',
@@ -1,3 +1,8 @@
1
+ import {
2
+ IMovableSectionDestination,
3
+ IMovableSectionSource,
4
+ ISectionEntry
5
+ } from '@jupyterlab/apputils';
1
6
  import {
2
7
  type TranslationBundle,
3
8
  nullTranslator
@@ -6,106 +11,226 @@ import {
6
11
  addIcon,
7
12
  closeIcon,
8
13
  LabIcon,
14
+ PanelWithToolbar,
9
15
  ReactWidget,
16
+ SidePanel,
10
17
  stopIcon,
11
18
  terminalIcon,
19
+ ToolbarButton,
12
20
  UseSignal
13
21
  } from '@jupyterlab/ui-components';
22
+ import { ISignal, Signal } from '@lumino/signaling';
23
+ import { AccordionPanel, Widget } from '@lumino/widgets';
14
24
  import * as React from 'react';
15
25
 
16
26
  import { SessionRegistry } from './model';
17
27
 
28
+ const RUNNING_TERMINALS_ID = 'xtralab-running-terminals';
29
+
18
30
  /**
19
- * Id of the panel widget. Used for layout restoration and as the handle
20
- * the sidebar visibility toggle would target.
31
+ * Persisted by the movable-sections plugin — must stay stable across releases.
21
32
  */
22
- const RUNNING_TERMINALS_ID = 'xtralab-running-terminals';
33
+ const TERMINALS_SECTION_ID = 'xtralab-terminals-section';
23
34
 
24
35
  /**
25
- * Left-sidebar panel listing every running terminal session. It is the
26
- * sidebar counterpart of the launcher's terminal tiles: where the
27
- * launcher *starts* sessions, this panel surfaces the ones already
28
- * running so the user can jump back to a backgrounded agent — including
29
- * sessions whose tab has been closed but which are still alive on the
30
- * server. Each row is labelled with the session's title and, for a session
31
- * running a coding agent, a smaller line below it showing the agent's latest
32
- * line of output (from {@link SessionRegistry.activityFor}).
33
- *
34
- * Backed by {@link SessionRegistry}: a `UseSignal` re-renders the list
35
- * whenever the registry emits `stateChanged`, so it tracks
36
- * `runningChanged` and the agent-title cache without any local state.
37
- * Click behavior (activate vs. reopen, shutdown, new terminal) is
38
- * delegated to plugin-supplied callbacks so the widget never imports
39
- * `app`.
36
+ * Left-sidebar panel listing every running terminal session, including the
37
+ * sessions whose tab is closed but which are still alive on the server. The
38
+ * list is a single movable "Terminals" section.
40
39
  */
41
- export class RunningTerminals extends ReactWidget {
40
+ export class RunningTerminals
41
+ extends SidePanel
42
+ implements IMovableSectionSource, IMovableSectionDestination
43
+ {
42
44
  constructor(options: RunningTerminals.IOptions) {
43
45
  super();
44
46
  this._registry = options.registry;
45
- this._trans = options.trans ?? nullTranslator.load('jupyterlab');
46
- this._iconForCommand = options.iconForCommand;
47
- this._onActivate = options.onActivate;
48
- this._onShutdown = options.onShutdown;
49
- this._onShutdownAll = options.onShutdownAll;
50
- this._onCreate = options.onCreate;
47
+ const trans = options.trans ?? nullTranslator.load('jupyterlab');
51
48
 
52
49
  this.id = RUNNING_TERMINALS_ID;
53
50
  this.title.icon = terminalIcon;
54
- this.title.caption = this._trans.__('Running Terminals');
51
+ this.title.caption = trans.__('Running Terminals');
55
52
  this.addClass('jp-xtralab-Terminals');
53
+
54
+ const section = (this._section = new PanelWithToolbar());
55
+ section.id = TERMINALS_SECTION_ID;
56
+ section.title.label = trans.__('Terminals');
57
+ section.addClass('jp-xtralab-Terminals-section');
58
+
59
+ const shutdownAll = new ToolbarButton({
60
+ icon: stopIcon,
61
+ onClick: options.onShutdownAll,
62
+ tooltip: trans.__('Shut Down All Terminals'),
63
+ enabled: this._registry.sessionNames().length > 0
64
+ });
65
+ this._registry.stateChanged.connect(() => {
66
+ shutdownAll.enabled = this._registry.sessionNames().length > 0;
67
+ });
68
+ section.toolbar.addItem('shutdown-all', shutdownAll);
69
+
70
+ const newTerminal = new ToolbarButton({
71
+ icon: addIcon,
72
+ onClick: () => {
73
+ const rect = newTerminal.node.getBoundingClientRect();
74
+ options.onCreate({ x: rect.left, y: rect.bottom });
75
+ },
76
+ tooltip: trans.__('New Terminal')
77
+ });
78
+ section.toolbar.addItem('new-terminal', newTerminal);
79
+
80
+ section.addWidget(
81
+ new TerminalsListing({
82
+ registry: this._registry,
83
+ trans,
84
+ iconForCommand: options.iconForCommand,
85
+ onActivate: options.onActivate,
86
+ onShutdown: options.onShutdown
87
+ })
88
+ );
89
+
90
+ this.addWidget(section);
91
+ }
92
+
93
+ /**
94
+ * The accordion panel hosting the sections; read by the move plugin.
95
+ */
96
+ get accordionPanel(): AccordionPanel {
97
+ return this.content as AccordionPanel;
98
+ }
99
+
100
+ /**
101
+ * A signal emitted when a section is announced to the move plugin.
102
+ */
103
+ get sectionAdded(): ISignal<this, ISectionEntry> {
104
+ return this._sectionAdded;
105
+ }
106
+
107
+ /**
108
+ * The hosted section widgets, excluding the panel's own Terminals section.
109
+ */
110
+ get sections(): ReadonlyArray<Widget> {
111
+ return this.accordionPanel.widgets.filter(w => w !== this._section);
112
+ }
113
+
114
+ /**
115
+ * Get the movable sections: just the Terminals section, while attached here.
116
+ */
117
+ getSections(): ReadonlyArray<ISectionEntry> {
118
+ const entry = this._sectionEntry();
119
+ return entry ? [entry] : [];
56
120
  }
57
121
 
122
+ /**
123
+ * Detach the Terminals section for the move plugin and return it; `null`
124
+ * for an unknown id or when the section is already hosted elsewhere.
125
+ */
126
+ removeSectionById(sectionId: string): Widget | null {
127
+ if (
128
+ sectionId !== TERMINALS_SECTION_ID ||
129
+ this._section.parent !== this.content
130
+ ) {
131
+ return null;
132
+ }
133
+ this._section.parent = null;
134
+ return this._section;
135
+ }
136
+
137
+ /**
138
+ * Re-attach the Terminals section after it moves back to this panel.
139
+ */
140
+ reinsertSection(widget: Widget): void {
141
+ this.addWidget(widget);
142
+ }
143
+
144
+ /**
145
+ * Host a section moved in from another sidebar panel.
146
+ */
147
+ addSection(widget: Widget): void {
148
+ this.addWidget(widget);
149
+ }
150
+
151
+ /**
152
+ * Detach a hosted section when it moves back to its own panel.
153
+ */
154
+ removeSectionWidget(widget: Widget): void {
155
+ if (widget.parent === this.content) {
156
+ widget.parent = null;
157
+ }
158
+ }
159
+
160
+ /**
161
+ * The layout restorer stores a section as collapsed whenever its panel is off
162
+ * screen at save time, so it can come back collapsed without the user asking.
163
+ */
164
+ expandOwnSection(): void {
165
+ const accordion = this.accordionPanel;
166
+ const index = Array.from(accordion.widgets).indexOf(this._section);
167
+ if (index >= 0) {
168
+ accordion.expand(index);
169
+ }
170
+ }
171
+
172
+ /**
173
+ * The move plugin reconciles persisted moves only against sections announced
174
+ * after `registerSource`, and this panel builds its section in the constructor.
175
+ */
176
+ announceSections(): void {
177
+ const entry = this._sectionEntry();
178
+ if (entry) {
179
+ this._sectionAdded.emit(entry);
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Dispose of the panel and the registry it owns.
185
+ */
58
186
  dispose(): void {
59
187
  if (this.isDisposed) {
60
188
  return;
61
189
  }
62
- // The panel owns the registry, so tear down its upstream
63
- // subscriptions before the React tree goes away.
190
+ // The panel owns the registry; tear down its subscriptions first.
64
191
  this._registry.dispose();
65
192
  super.dispose();
66
193
  }
67
194
 
68
- protected render(): React.ReactElement {
69
- return (
70
- <UseSignal signal={this._registry.stateChanged}>
71
- {() => (
72
- <RunningTerminalsComponent
73
- registry={this._registry}
74
- trans={this._trans}
75
- iconForCommand={this._iconForCommand}
76
- onActivate={this._onActivate}
77
- onShutdown={this._onShutdown}
78
- onShutdownAll={this._onShutdownAll}
79
- onCreate={this._onCreate}
80
- />
81
- )}
82
- </UseSignal>
83
- );
195
+ private _sectionEntry(): ISectionEntry | null {
196
+ const accordion = this.accordionPanel;
197
+ const index = Array.from(accordion.widgets).indexOf(this._section);
198
+ const titleNode = accordion.titles[index] as HTMLElement | undefined;
199
+ if (index < 0 || !titleNode) {
200
+ return null;
201
+ }
202
+ return {
203
+ id: TERMINALS_SECTION_ID,
204
+ titleNode,
205
+ widget: this._section
206
+ };
84
207
  }
85
208
 
86
209
  private _registry: SessionRegistry;
87
- private _trans: TranslationBundle;
88
- private _iconForCommand: (command: string | null) => LabIcon;
89
- private _onActivate: (sessionName: string) => void;
90
- private _onShutdown: (sessionName: string) => void;
91
- private _onShutdownAll: () => void;
92
- private _onCreate: (anchor: { x: number; y: number }) => void;
210
+ private _section: PanelWithToolbar;
211
+ private _sectionAdded = new Signal<this, ISectionEntry>(this);
93
212
  }
94
213
 
95
214
  export namespace RunningTerminals {
215
+ /**
216
+ * The instantiation options for a {@link RunningTerminals} panel.
217
+ */
96
218
  export interface IOptions {
219
+ /**
220
+ * The session registry the panel renders and takes ownership of.
221
+ */
97
222
  registry: SessionRegistry;
223
+ /**
224
+ * The translation bundle for the panel's labels; untranslated if omitted.
225
+ */
98
226
  trans?: TranslationBundle;
99
227
  /**
100
- * Resolve the running-agent command for a row (from
101
- * `registry.agentCommandFor`) to the icon to show before its label —
102
- * the agent's logo, or the plain terminal icon. Supplied by the plugin
103
- * so the widget never imports the agent list.
228
+ * Resolve a row's running-agent command to its icon; supplied by the
229
+ * plugin so the widget never imports the agent list.
104
230
  */
105
231
  iconForCommand: (command: string | null) => LabIcon;
106
232
  /**
107
- * Activate the named session's open tab, or reopen it in a fresh
108
- * terminal widget if no tab is currently attached.
233
+ * Activate the named session's open tab, or reopen it in a fresh widget.
109
234
  */
110
235
  onActivate: (sessionName: string) => void;
111
236
  /**
@@ -113,78 +238,74 @@ export namespace RunningTerminals {
113
238
  */
114
239
  onShutdown: (sessionName: string) => void;
115
240
  /**
116
- * Shut down every running terminal at once. The plugin is expected to
117
- * confirm with the user first, since it tears down all live sessions.
241
+ * Shut down every running terminal; the plugin confirms with the user first.
118
242
  */
119
243
  onShutdownAll: () => void;
120
244
  /**
121
- * Activate the "+" button, anchored at the given viewport coordinates
122
- * (the bottom-left of the button). The plugin decides what to show
123
- * there — a menu of agents plus a plain terminal, or just a new
124
- * terminal when no agents are available — so the panel only reports
125
- * where the button is and never imports the command/menu machinery.
245
+ * Handle the "+" button, anchored at its bottom-left in viewport
246
+ * coordinates; the plugin decides what to show there.
126
247
  */
127
248
  onCreate: (anchor: { x: number; y: number }) => void;
128
249
  }
129
250
  }
130
251
 
252
+ /**
253
+ * The body of the Terminals section.
254
+ */
255
+ class TerminalsListing extends ReactWidget {
256
+ constructor(options: {
257
+ registry: SessionRegistry;
258
+ trans: TranslationBundle;
259
+ iconForCommand: (command: string | null) => LabIcon;
260
+ onActivate: (sessionName: string) => void;
261
+ onShutdown: (sessionName: string) => void;
262
+ }) {
263
+ super();
264
+ this._options = options;
265
+ this.addClass('jp-xtralab-Terminals-listing');
266
+ }
267
+
268
+ /**
269
+ * Render the listing, re-rendering on every registry state change.
270
+ */
271
+ protected render(): React.ReactElement {
272
+ return (
273
+ <UseSignal signal={this._options.registry.stateChanged}>
274
+ {() => (
275
+ <RunningTerminalsComponent
276
+ registry={this._options.registry}
277
+ trans={this._options.trans}
278
+ iconForCommand={this._options.iconForCommand}
279
+ onActivate={this._options.onActivate}
280
+ onShutdown={this._options.onShutdown}
281
+ />
282
+ )}
283
+ </UseSignal>
284
+ );
285
+ }
286
+
287
+ private _options: {
288
+ registry: SessionRegistry;
289
+ trans: TranslationBundle;
290
+ iconForCommand: (command: string | null) => LabIcon;
291
+ onActivate: (sessionName: string) => void;
292
+ onShutdown: (sessionName: string) => void;
293
+ };
294
+ }
295
+
131
296
  function RunningTerminalsComponent(props: {
132
297
  registry: SessionRegistry;
133
298
  trans: TranslationBundle;
134
299
  iconForCommand: (command: string | null) => LabIcon;
135
300
  onActivate: (sessionName: string) => void;
136
301
  onShutdown: (sessionName: string) => void;
137
- onShutdownAll: () => void;
138
- onCreate: (anchor: { x: number; y: number }) => void;
139
302
  }): React.ReactElement {
140
- const {
141
- registry,
142
- trans,
143
- iconForCommand,
144
- onActivate,
145
- onShutdown,
146
- onShutdownAll,
147
- onCreate
148
- } = props;
303
+ const { registry, trans, iconForCommand, onActivate, onShutdown } = props;
149
304
  const names = registry.sessionNames();
150
- // The session whose terminal is the current widget in the main area, so its
151
- // row can be highlighted. `null` when the current tab is a notebook or any
152
- // other non-terminal widget.
153
305
  const currentName = registry.currentSessionName();
154
306
 
155
307
  return (
156
308
  <div className="jp-xtralab-Terminals-body">
157
- <div className="jp-xtralab-Terminals-header">
158
- <h2 className="jp-xtralab-Terminals-title">{trans.__('Terminals')}</h2>
159
- <div className="jp-xtralab-Terminals-actions">
160
- <button
161
- type="button"
162
- className="jp-xtralab-Terminals-action jp-xtralab-Terminals-shutdown-all"
163
- onClick={onShutdownAll}
164
- disabled={names.length === 0}
165
- title={trans.__('Shut Down All Terminals')}
166
- aria-label={trans.__('Shut Down All Terminals')}
167
- >
168
- <stopIcon.react tag="span" verticalAlign="middle" />
169
- </button>
170
- <button
171
- type="button"
172
- className="jp-xtralab-Terminals-action jp-xtralab-Terminals-new"
173
- onClick={event => {
174
- // Anchor whatever the plugin shows (usually an agent menu) to
175
- // the button's bottom-left, in viewport coordinates — what
176
- // `Menu.open(x, y)` expects.
177
- const rect = event.currentTarget.getBoundingClientRect();
178
- onCreate({ x: rect.left, y: rect.bottom });
179
- }}
180
- title={trans.__('New Terminal')}
181
- aria-label={trans.__('New Terminal')}
182
- aria-haspopup="menu"
183
- >
184
- <addIcon.react tag="span" verticalAlign="middle" />
185
- </button>
186
- </div>
187
- </div>
188
309
  {names.length === 0 ? (
189
310
  <p className="jp-xtralab-Terminals-empty">
190
311
  {trans.__('No running terminals.')}
@@ -197,15 +318,10 @@ function RunningTerminalsComponent(props: {
197
318
  const tooltip = hasWidget
198
319
  ? trans.__('Activate %1', label)
199
320
  : trans.__('Reopen %1', label);
200
- // The running agent's logo (e.g. Claude), or the plain terminal
201
- // icon when nothing recognised is running in the session.
202
321
  const RowIcon = iconForCommand(
203
322
  registry.agentCommandFor(name)
204
323
  ).react;
205
324
  const isCurrent = name === currentName;
206
- // The latest line of output from the session's agent, shown as a
207
- // smaller line under the title; `null` for rows with nothing to
208
- // surface (no agent running, or no open tab to read a live buffer).
209
325
  const activity = registry.activityFor(name);
210
326
  return (
211
327
  <li
@@ -1,14 +1,9 @@
1
1
  import { LabIcon } from '@jupyterlab/ui-components';
2
2
 
3
3
  /**
4
- * Mirror-symmetric "toggle sidebar" marks for the top-bar buttons, in the
5
- * same visual language as the macOS / VS Code / svgrepo sidebar icons
6
- * (https://www.svgrepo.com/vectors/sidebar/): a rounded window outline split
7
- * by a divider, with the controlled side shaded so left vs. right reads at a
8
- * glance. The artwork is hand-authored from basic geometry (rounded rect +
9
- * divider + a shaded panel) so there is no third-party licensing to track,
10
- * and every stroke/fill uses `currentColor` so the icons inherit the top
11
- * bar's text color across the light and dark themes.
4
+ * Mirror-symmetric "toggle sidebar" marks in the macOS / VS Code visual
5
+ * language. Hand-authored from basic geometry (no third-party licensing);
6
+ * every stroke/fill uses `currentColor` to follow the top bar's text color.
12
7
  */
13
8
 
14
9
  export const leftSidebarIcon = new LabIcon({
@@ -10,16 +10,12 @@ import { leftSidebarIcon, rightSidebarIcon } from './icons';
10
10
 
11
11
  const PLUGIN_ID = 'xtralab:top-bar';
12
12
 
13
- /**
14
- * Upstream commands from `@jupyterlab/application-extension` that collapse or
15
- * expand the side areas. We bind the buttons to these rather than
16
- * reimplementing the toggle, so behavior (and the `isToggled`/`isEnabled`
17
- * state the buttons reflect) stays in lockstep with the View menu entries
18
- * and their keyboard shortcuts.
19
- */
20
13
  const TOGGLE_LEFT_AREA = 'application:toggle-left-area';
21
14
  const TOGGLE_RIGHT_AREA = 'application:toggle-right-area';
22
15
 
16
+ /**
17
+ * A specification for one sidebar toggle button in the top area.
18
+ */
23
19
  interface IButtonSpec {
24
20
  /**
25
21
  * Stable widget id (required by `LabShell.add`).
@@ -29,15 +25,17 @@ interface IButtonSpec {
29
25
  * Command the button triggers and mirrors the state of.
30
26
  */
31
27
  command: string;
28
+ /**
29
+ * The icon rendered on the button.
30
+ */
32
31
  icon: LabIcon;
32
+ /**
33
+ * The localized tooltip of the button.
34
+ */
33
35
  caption: (trans: ReturnType<ITranslator['load']>) => string;
34
36
  /**
35
- * Rank within the `top` area. The main menu bar is added at rank 100, so a
36
- * negative rank lands the left button at the leading edge before it; a large
37
- * rank puts the right button after the menu, where `margin-left: auto`
38
- * (style/topBar.css) floats it to the far edge. xtralab's shipped config
39
- * disables the upstream Jupyter logo that otherwise occupies rank 0, so the
40
- * leading edge is free.
37
+ * Rank in the `top` area: the menu bar sits at 100 and the rank-0 upstream
38
+ * logo is disabled; `margin-left: auto` (topBar.css) floats the right button.
41
39
  */
42
40
  rank: number;
43
41
  /**
@@ -66,17 +64,10 @@ const BUTTONS: IButtonSpec[] = [
66
64
  ];
67
65
 
68
66
  /**
69
- * Add two icon buttons to the top bar that toggle the left and right side
70
- * areas, mirroring the macOS-style sidebar buttons: a left-sidebar button at
71
- * the leading edge and a right-sidebar button at the far edge.
72
- *
73
- * Each is a `CommandToolbarButton` wrapping an existing
74
- * `application:toggle-{left,right}-area` command, so clicking it runs the
75
- * same toggle as the View menu and the button reflects the command's state —
76
- * pressed while the matching sidebar is open, and disabled when the area is
77
- * empty. Note xtralab ships nothing in the right area by default (the
78
- * property inspector, table of contents and debugger are all disabled), so
79
- * the right button stays disabled until a widget is moved or added there.
67
+ * Add macOS-style sidebar toggle buttons to the top bar: left at the leading
68
+ * edge, right at the far edge. Each wraps the upstream toggle command and
69
+ * reflects its state — pressed while the sidebar is open, disabled while the
70
+ * area is empty (as the right area is by default).
80
71
  */
81
72
  const plugin: JupyterFrontEndPlugin<void> = {
82
73
  id: PLUGIN_ID,
@@ -92,40 +83,26 @@ const plugin: JupyterFrontEndPlugin<void> = {
92
83
  const { commands } = app;
93
84
  const trans = (translator ?? nullTranslator).load('jupyterlab');
94
85
 
95
- // The upstream toggle-{left,right}-area commands derive `isEnabled` from
96
- // `!labShell.isEmpty(side)` but don't fire `commandChanged` when widgets
97
- // move in or out of a side area. `CommandToolbarButton` only re-renders on
98
- // `commandChanged` for its own id, so the buttons need an explicit notify
99
- // whenever the shell layout shifts — `layoutModified` covers each side
100
- // handler's `_updated` (add/remove, expand/collapse), keeping both
101
- // `isEnabled` and `isToggled` in sync.
86
+ // The upstream toggle commands don't fire `commandChanged` on layout
87
+ // shifts, and `CommandToolbarButton` only re-renders on that signal.
102
88
  labShell.layoutModified.connect(() => {
103
89
  for (const spec of BUTTONS) {
104
90
  commands.notifyCommandChanged(spec.command);
105
91
  }
106
92
  });
107
93
 
108
- // Defer to `app.restored` so the upstream toggle commands are already in
109
- // the registry: `CommandToolbarButton` renders nothing until its command
110
- // exists and only re-renders on `commandChanged` of type `changed` /
111
- // `many-changed` (not `added`), so mounting earlier could leave a button
112
- // blank until the next unrelated command change.
94
+ // Defer to `app.restored`: `CommandToolbarButton` renders nothing until
95
+ // its command exists and doesn't re-render on the `added` change type.
113
96
  void app.restored.then(() => {
114
97
  for (const spec of BUTTONS) {
115
98
  const button = new CommandToolbarButton({
116
99
  commands,
117
100
  id: spec.command,
118
101
  icon: spec.icon,
119
- // Empty label keeps the button icon-only: the override is forwarded
120
- // verbatim and the component skips the label span when it is falsy.
121
102
  label: '',
122
103
  caption: spec.caption(trans),
123
- // Toggle on mousedown without focusing the button. Otherwise
124
- // ToolbarButtonComponent calls `event.target.focus()` on click,
125
- // leaving the stealth button showing a focus fill/outline (the
126
- // stray "border") until you click elsewhere — unwanted chrome for a
127
- // title-bar control. Keyboard Tab focus still works, so this stays
128
- // accessible.
104
+ // ToolbarButtonComponent otherwise focuses the button on click,
105
+ // leaving a stray focus fill on this title-bar control; Tab still works.
129
106
  noFocusOnClick: true
130
107
  });
131
108
  button.id = spec.id;