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
@@ -22,38 +22,19 @@ import {
22
22
  } from './notebookDiff';
23
23
  import { resolveDiffTheme } from './diffTheme';
24
24
 
25
- /**
26
- * The CSS class added to the diff main-area widget. The selectors that
27
- * style the embedded `@pierre/diffs` viewer hang off this class.
28
- */
29
25
  export const DIFF_WIDGET_CSS_CLASS = 'jp-xtralab-DiffWidget';
30
26
 
31
- /**
32
- * `localStorage` key for the user's last-chosen split ratio, persisted so
33
- * the preference survives reloads (the library has no built-in way to
34
- * remember it).
35
- */
36
27
  const SPLIT_RATIO_STORAGE_KEY = 'xtralab:diff-split-ratio';
37
28
 
38
- /**
39
- * Bounds on the left-pane fraction. Keeping a few percent on either side
40
- * guarantees both panes remain visible — dragging to 0/100 would hide a
41
- * column and is almost never what the user wants.
42
- */
43
29
  const MIN_SPLIT_RATIO = 0.1;
44
30
  const MAX_SPLIT_RATIO = 0.9;
45
31
  const DEFAULT_SPLIT_RATIO = 0.5;
46
32
 
47
33
  /**
48
- * CSS injected via the `@pierre/diffs` `unsafeCSS` option. The library
49
- * places it inside the shadow root under `@layer unsafe`, which beats
50
- * the library's own `@layer base` rule that hardcodes
51
- * `grid-template-columns: 1fr 1fr` on the split-mode container.
52
- *
53
- * The override reads from `--xtralab-split-cols`, a custom property we set
54
- * on the host element via the `style` prop. Custom properties inherit
55
- * through the shadow DOM boundary, so adjusting it on the host instantly
56
- * resizes the columns without re-rendering the diff.
34
+ * Injected via the `unsafeCSS` option into the shadow root's `@layer
35
+ * unsafe`, beating the library's base rule that hardcodes `1fr 1fr`. Reads
36
+ * `--xtralab-split-cols` set on the host — custom properties cross the
37
+ * shadow boundary, so resizing needs no re-render.
57
38
  */
58
39
  const SPLIT_RESIZE_CSS = `pre[data-diff-type="split"][data-overflow="scroll"] {
59
40
  grid-template-columns: var(--xtralab-split-cols, 1fr 1fr);
@@ -74,8 +55,7 @@ function readStoredSplitRatio(): number {
74
55
  return parsed;
75
56
  }
76
57
  } catch {
77
- // localStorage may throw in privacy mode or sandboxed contexts. Falling
78
- // back to the default is fine — the user can resize again on this run.
58
+ // localStorage can throw in privacy mode or sandboxed contexts.
79
59
  }
80
60
  return DEFAULT_SPLIT_RATIO;
81
61
  }
@@ -88,15 +68,13 @@ function writeStoredSplitRatio(ratio: number): void {
88
68
  }
89
69
  }
90
70
 
91
- /**
92
- * Layout for textual/code diffs, forwarded to `@pierre/diffs` as its
93
- * `diffStyle` option. `split` is the side-by-side old|new view; `unified`
94
- * is the single-column inline view (what diffs.com calls "stacked").
95
- */
96
71
  export type DiffStyle = 'split' | 'unified';
97
72
 
98
73
  const DIFF_STYLE_STORAGE_KEY = 'xtralab:diff-style';
99
74
 
75
+ /**
76
+ * Read the persisted diff style, defaulting to `'split'`.
77
+ */
100
78
  export function readStoredDiffStyle(): DiffStyle {
101
79
  try {
102
80
  const raw = window.localStorage.getItem(DIFF_STYLE_STORAGE_KEY);
@@ -104,11 +82,14 @@ export function readStoredDiffStyle(): DiffStyle {
104
82
  return raw;
105
83
  }
106
84
  } catch {
107
- // See readStoredSplitRatio — privacy-mode / sandboxed contexts can throw.
85
+ // See readStoredSplitRatio.
108
86
  }
109
87
  return 'split';
110
88
  }
111
89
 
90
+ /**
91
+ * Persist the diff style to local storage (best-effort).
92
+ */
112
93
  export function writeStoredDiffStyle(style: DiffStyle): void {
113
94
  try {
114
95
  window.localStorage.setItem(DIFF_STYLE_STORAGE_KEY, style);
@@ -117,15 +98,13 @@ export function writeStoredDiffStyle(style: DiffStyle): void {
117
98
  }
118
99
  }
119
100
 
120
- /**
121
- * View modes available for `.ipynb` diffs. `notebook` is the cell-by-cell
122
- * rendered view; `json` is the raw nbformat JSON file diff, for inspecting
123
- * exactly which bytes changed.
124
- */
125
101
  export type NotebookDiffViewMode = 'notebook' | 'json';
126
102
 
127
103
  const NOTEBOOK_DIFF_VIEW_MODE_STORAGE_KEY = 'xtralab:notebook-diff-view-mode';
128
104
 
105
+ /**
106
+ * Read the persisted notebook view mode, defaulting to `'notebook'`.
107
+ */
129
108
  export function readStoredNotebookViewMode(): NotebookDiffViewMode {
130
109
  try {
131
110
  const raw = window.localStorage.getItem(
@@ -135,12 +114,14 @@ export function readStoredNotebookViewMode(): NotebookDiffViewMode {
135
114
  return raw;
136
115
  }
137
116
  } catch {
138
- // See readStoredSplitRatio — privacy-mode / sandboxed contexts can
139
- // throw on access. The notebook view is the better default anyway.
117
+ // See readStoredSplitRatio.
140
118
  }
141
119
  return 'notebook';
142
120
  }
143
121
 
122
+ /**
123
+ * Persist the notebook view mode to local storage (best-effort).
124
+ */
144
125
  export function writeStoredNotebookViewMode(mode: NotebookDiffViewMode): void {
145
126
  try {
146
127
  window.localStorage.setItem(NOTEBOOK_DIFF_VIEW_MODE_STORAGE_KEY, mode);
@@ -150,31 +131,26 @@ export function writeStoredNotebookViewMode(mode: NotebookDiffViewMode): void {
150
131
  }
151
132
 
152
133
  /**
153
- * Annotation payload threaded through the diff library back into the
154
- * `renderAnnotation` callback. Carries the index of the hunk a particular
155
- * action button targets.
134
+ * Annotation payload threaded back into `renderAnnotation`; carries the target hunk index.
156
135
  */
157
136
  interface IHunkActionAnnotation {
137
+ /**
138
+ * The index of the hunk the action targets.
139
+ */
158
140
  hunkIndex: number;
159
141
  }
160
142
 
161
143
  /**
162
- * Detect notebook files by extension. We only look at the suffix so the
163
- * caller doesn't need to plumb a content-type field through; mistaking
164
- * another `.ipynb`-named file for a notebook is harmless because
165
- * {@link buildNotebookDiff} validates the JSON shape and we fall back to
166
- * the file diff if parsing fails.
144
+ * Extension-only check; a false positive is harmless because
145
+ * {@link buildNotebookDiff} validates the JSON and the file diff is the fallback.
167
146
  */
168
147
  function isNotebookPath(path: string): boolean {
169
148
  return path.toLowerCase().endsWith('.ipynb');
170
149
  }
171
150
 
172
151
  /**
173
- * Decide whether the active JupyterLab theme is a dark theme. Prefers the
174
- * `IThemeManager` token when available, and falls back to sniffing the
175
- * `data-jp-theme-light` attribute that JupyterLab sets on `<body>` so the
176
- * component still works in stripped-down hosts where the token isn't
177
- * provided (lite, certain notebook configurations, …).
152
+ * Dark-theme check via `IThemeManager`, falling back to the
153
+ * `data-jp-theme-light` body attribute for hosts without the token.
178
154
  */
179
155
  export function isDarkTheme(themeManager: IThemeManager | null): boolean {
180
156
  if (themeManager !== null && themeManager.theme !== null) {
@@ -192,19 +168,27 @@ export function isPierreTheme(themeManager: IThemeManager | null): boolean {
192
168
  }
193
169
 
194
170
  /**
195
- * Optional per-hunk discard wiring. When supplied and `enabled`, each hunk
196
- * in a plain-text/code diff gets an inline "discard" button; clicking it
197
- * reverts that hunk and hands the rebuilt full-file text back to the host
198
- * via {@link IHunkDiscard.save}. The host owns the actual write (it knows
199
- * the on-disk path) and triggers any follow-up refresh in
200
- * {@link IHunkDiscard.onAfterSave}.
171
+ * Per-hunk discard wiring: discarding rebuilds the full file text and the
172
+ * host owns the write via `save`, then refreshes in `onAfterSave`.
201
173
  */
202
174
  interface IHunkDiscard {
175
+ /**
176
+ * Whether hunk discarding is available.
177
+ */
203
178
  enabled: boolean;
179
+ /**
180
+ * Persist the full post-discard file text.
181
+ */
204
182
  save: (fullText: string) => Promise<void>;
183
+ /**
184
+ * Called after a successful save so the host can refresh the diff.
185
+ */
205
186
  onAfterSave: () => void;
206
187
  }
207
188
 
189
+ /**
190
+ * The props for the {@link DiffSurface} component.
191
+ */
208
192
  interface IDiffSurfaceProps {
209
193
  /**
210
194
  * Whether the host is still resolving the file contents.
@@ -227,13 +211,11 @@ interface IDiffSurfaceProps {
227
211
  */
228
212
  newText: string;
229
213
  /**
230
- * New-side path. Drives notebook detection and the `@pierre/diffs` file
231
- * name on the additions side.
214
+ * New-side path; drives notebook detection and the highlighting filename.
232
215
  */
233
216
  newName: string;
234
217
  /**
235
- * Old-side path. Differs from {@link newName} only for renames; defaults
236
- * to {@link newName} when the host does not track a previous path.
218
+ * Old-side path; differs from {@link newName} only for renames.
237
219
  */
238
220
  oldName?: string;
239
221
  /**
@@ -245,35 +227,28 @@ interface IDiffSurfaceProps {
245
227
  */
246
228
  pierreTheme: boolean;
247
229
  /**
248
- * Rendermime registry used by the notebook view to render outputs and
249
- * markdown cells. May be `null` in stripped-down hosts; the notebook
250
- * diff falls back to a textual representation in that case.
230
+ * Used by the notebook view; `null` in stripped-down hosts, which forces a textual fallback.
251
231
  */
252
232
  rendermime: IRenderMimeRegistry | null;
253
233
  /**
254
- * Current rendered-vs-JSON choice for notebook diffs (host-controlled).
234
+ * Rendered-vs-JSON choice for notebook diffs (host-controlled).
255
235
  */
256
236
  notebookViewMode: NotebookDiffViewMode;
257
237
  /**
258
- * Split vs unified layout for the textual/code file diff (host-controlled).
238
+ * Split vs unified layout for the textual file diff (host-controlled).
259
239
  */
260
240
  diffStyle: DiffStyle;
261
241
  /**
262
- * Called whenever the availability of a rendered notebook view changes,
263
- * so the host can show/hide its Notebook/JSON toolbar toggle.
242
+ * Fires when rendered-notebook availability changes so the host can toggle its Notebook/JSON control.
264
243
  */
265
244
  onNotebookAvailabilityChange?: (available: boolean) => void;
266
245
  /**
267
- * Called whenever the textual/code file diff (the only view the split vs
268
- * unified choice affects) becomes the active view, so the host can
269
- * show/hide its Split/Unified toolbar toggle. False for image, binary,
270
- * rendered-notebook, loading and error states.
246
+ * Fires when the textual file diff becomes (in)active — the only view the split/unified choice affects.
271
247
  */
272
248
  onFileDiffActiveChange?: (active: boolean) => void;
273
249
  /**
274
- * Called after each (re)computation of the diff with the number of hunks
275
- * (or `null` when no textual diff exists). Hosts use this to implement
276
- * post-discard behaviors such as auto-closing an emptied diff.
250
+ * Fires after each diff computation with the hunk count (`null` when no
251
+ * textual diff); hosts use it to auto-close emptied diffs.
277
252
  */
278
253
  onMetadataChange?: (info: { hunkCount: number | null }) => void;
279
254
  /**
@@ -281,11 +256,8 @@ interface IDiffSurfaceProps {
281
256
  */
282
257
  hunkDiscard?: IHunkDiscard;
283
258
  /**
284
- * When provided, the textual/code file diff gets line selection: clicking
285
- * or dragging over the line numbers selects a range, and a gutter "+"
286
- * button appears on the hovered/selected lines. Clicking that button
287
- * invokes this callback with the selected range and the button's viewport
288
- * rectangle (to anchor a popup to), `null` when it cannot be measured.
259
+ * Enables line selection and the gutter "+" button; called with the
260
+ * selected range and the button's viewport rect (`null` when unmeasurable).
289
261
  */
290
262
  onLineAsk?: (range: SelectedLineRange, anchor: DOMRect | null) => void;
291
263
  /**
@@ -321,15 +293,11 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
321
293
 
322
294
  const hasContent = !loading && error === null && !isBinary;
323
295
 
324
- // Raster images take a dedicated `<img>`-based view instead of a
325
- // text/notebook diff; the host passes the two sides as base64 strings in
326
- // `oldText`/`newText` (the git server base64-encodes binary content).
296
+ // For raster images the host passes both sides as base64 in oldText/newText.
327
297
  const imageType = React.useMemo(() => imageDataType(newName), [newName]);
328
298
 
329
- // Pre-compute the line-oriented diff metadata. Owning it here lets us
330
- // pass it to {@link FileDiff} *and* thread the same instance through
331
- // {@link diffAcceptRejectHunk} when the user discards a hunk — so hunk
332
- // indexes line up between what the user clicked and what we mutate.
299
+ // Owning the metadata here keeps hunk indexes aligned between FileDiff
300
+ // and diffAcceptRejectHunk on discard.
333
301
  const metadata = React.useMemo<FileDiffMetadata | null>(() => {
334
302
  if (!hasContent || imageType !== null) {
335
303
  return null;
@@ -342,10 +310,6 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
342
310
  return parseDiffFromFile(oldFile, newFile);
343
311
  }, [hasContent, imageType, oldText, newText, oldName, newName]);
344
312
 
345
- // Cell-by-cell notebook diff for `.ipynb` files. When non-null the
346
- // renderer uses a notebook-aware view instead of the line-oriented file
347
- // diff — kept distinct from {@link metadata} so a notebook whose JSON we
348
- // cannot parse can fall back to the file diff path.
349
313
  const notebookDiff = React.useMemo<INotebookDiffResult | null>(() => {
350
314
  if (!hasContent || imageType !== null || !isNotebookPath(newName)) {
351
315
  return null;
@@ -353,43 +317,31 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
353
317
  return buildNotebookDiff({ oldText, newText });
354
318
  }, [hasContent, imageType, oldText, newText, newName]);
355
319
 
356
- // Tell the host whether a rendered notebook view is currently available
357
- // so it can show/hide its Notebook/JSON toggle.
358
320
  React.useEffect(() => {
359
321
  onNotebookAvailabilityChange?.(notebookDiff !== null);
360
322
  }, [onNotebookAvailabilityChange, notebookDiff]);
361
323
 
362
- // Report the diff shape so hosts can react (e.g. auto-close on empty).
363
324
  React.useEffect(() => {
364
325
  onMetadataChange?.({
365
326
  hunkCount: metadata !== null ? metadata.hunks.length : null
366
327
  });
367
328
  }, [onMetadataChange, metadata]);
368
329
 
369
- // Decide which view to render. The notebook view requires a successful
370
- // nbformat parse; if that failed, we only show the raw JSON file diff.
371
- // Computed before the early returns below so the file-diff-active effect
372
- // can depend on it without violating the rules of hooks.
330
+ // Computed before the early returns so the hooks below can depend on
331
+ // them without breaking the rules of hooks.
373
332
  const hasNotebookView = notebookDiff !== null;
374
333
  const showNotebookView = hasNotebookView && notebookViewMode === 'notebook';
375
334
  const showFileDiff = !showNotebookView && metadata !== null;
376
335
 
377
- // The split vs unified choice only affects the textual/code file diff, so
378
- // tell the host whether that view is active to drive its Split/Unified
379
- // toolbar toggle.
380
336
  React.useEffect(() => {
381
337
  onFileDiffActiveChange?.(showFileDiff);
382
338
  }, [onFileDiffActiveChange, showFileDiff]);
383
339
 
384
- // Split ratio for the diff columns: fraction of width given to the
385
- // deletions (left) pane. Persisted across sessions so the user only
386
- // dials in their layout once.
387
340
  const [leftRatio, setLeftRatio] = React.useState<number>(() =>
388
341
  readStoredSplitRatio()
389
342
  );
390
343
  const wrapperRef = React.useRef<HTMLDivElement | null>(null);
391
- // The drag handler reads the live ratio from a ref so the listeners we
392
- // attach on pointer-down don't capture a stale value across renders.
344
+ // Drag listeners read the live ratio from a ref to avoid stale captures.
393
345
  const leftRatioRef = React.useRef(leftRatio);
394
346
  React.useEffect(() => {
395
347
  leftRatioRef.current = leftRatio;
@@ -405,8 +357,6 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
405
357
  }
406
358
  return metadata.hunks.map((hunk, hunkIndex) => ({
407
359
  side: 'additions',
408
- // The first line of each hunk in the new file. We anchor the button
409
- // there so it sits at the top of the hunk's body.
410
360
  lineNumber: hunk.additionStart,
411
361
  metadata: { hunkIndex }
412
362
  }));
@@ -418,12 +368,8 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
418
368
  return;
419
369
  }
420
370
  const updated = diffAcceptRejectHunk(metadata, hunkIndex, 'reject');
421
- // `additionLines` after a 'reject' contains the full new file with
422
- // the hunk reverted back to the old content. The library splits
423
- // file contents with `/(?<=\n)/` (a lookbehind that keeps the
424
- // trailing `\n` on each line), so the entries already carry their
425
- // own line endings — joining with `''` rebuilds the original text
426
- // verbatim, while joining with `'\n'` would double every newline.
371
+ // additionLines holds the full new file with the hunk reverted; lines keep
372
+ // their trailing `\n` (lookbehind split), so join('') rebuilds it verbatim.
427
373
  const text = updated.additionLines.join('');
428
374
  try {
429
375
  await hunkDiscard.save(text);
@@ -469,9 +415,7 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
469
415
  if (onLineAsk === undefined) {
470
416
  return;
471
417
  }
472
- // Anchor to the gutter "+" button that was just clicked. It lives in
473
- // the `@pierre/diffs` shadow root (which is open), parked on the
474
- // hovered/selected line's number element.
418
+ // Anchor to the just-clicked gutter "+" button inside the library's open shadow root.
475
419
  const slot = wrapperRef.current
476
420
  ?.querySelector('diffs-container')
477
421
  ?.shadowRoot?.querySelector('[data-gutter-utility-slot]');
@@ -485,8 +429,6 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
485
429
 
486
430
  const handleResizerPointerDown = React.useCallback(
487
431
  (event: React.PointerEvent<HTMLDivElement>) => {
488
- // Only react to primary-button drags. Right-click / middle-click on
489
- // the handle should pass through to the browser default.
490
432
  if (event.button !== 0) {
491
433
  return;
492
434
  }
@@ -519,8 +461,6 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
519
461
  handle.removeEventListener('pointermove', onPointerMove);
520
462
  handle.removeEventListener('pointerup', onPointerEnd);
521
463
  handle.removeEventListener('pointercancel', onPointerEnd);
522
- // Only persist on drag-end so a quick drag isn't followed by a
523
- // burst of writes for every intermediate position.
524
464
  writeStoredSplitRatio(leftRatioRef.current);
525
465
  };
526
466
  handle.addEventListener('pointermove', onPointerMove);
@@ -589,9 +529,6 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
589
529
  );
590
530
  }
591
531
  if (imageType !== null) {
592
- // `oldText`/`newText` carry the two revisions as base64 here. The
593
- // image view owns its own layout and mode selector, so it does not
594
- // use the split resizer or the notebook/JSON toggle.
595
532
  return (
596
533
  <div className="jp-xtralab-DiffWidget-content">
597
534
  <ImageDiffView
@@ -611,10 +548,6 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
611
548
  );
612
549
  }
613
550
 
614
- // Custom property set on the diffs-container host. The library's
615
- // shadow-root rule consumes it via `var(--xtralab-split-cols, …)` (see
616
- // SPLIT_RESIZE_CSS) and recomputes the column tracks instantly. Only
617
- // relevant in the file-diff path; the notebook view doesn't use it.
618
551
  const leftPercent = leftRatio * 100;
619
552
  const hostStyle = {
620
553
  '--xtralab-split-cols': `${leftPercent}% ${100 - leftPercent}%`
@@ -638,30 +571,16 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
638
571
  lineAnnotations={lineAnnotations}
639
572
  renderAnnotation={renderAnnotation}
640
573
  style={hostStyle}
641
- // Disable the worker pool: JupyterLab's webpack federation
642
- // pipeline doesn't ship the library's
643
- // `@pierre/diffs/worker/worker.js` file at a URL the worker
644
- // bootstrap can resolve from, so leaving the pool enabled
645
- // crashes on instantiation. Running on the main thread is
646
- // fine for the small files git diffs typically operate on.
574
+ // JupyterLab's federation pipeline can't serve the worker bundle
575
+ // at a resolvable URL, so the pool crashes; run on the main thread.
647
576
  disableWorkerPool={true}
648
577
  options={{
649
578
  diffStyle,
650
- // Drop the file header — the tab title already shows the
651
- // file name and the panel header carries the change
652
- // context.
653
579
  disableFileHeader: true,
654
580
  theme: resolveDiffTheme(dark, pierreTheme),
655
581
  themeType: dark ? 'dark' : 'light',
656
- // Inject the column-resize override into the shadow root
657
- // via the library's `@layer unsafe` channel. Keeping the
658
- // string constant lets the library short-circuit the
659
- // re-render path it uses when this option actually
660
- // changes.
582
+ // Constant string lets the library skip its unsafeCSS re-render path.
661
583
  unsafeCSS: SPLIT_RESIZE_CSS,
662
- // Line selection + the gutter "+" button feed the
663
- // ask-agent popup; only wired up when a handler exists so
664
- // a plain diff keeps its passive gutter.
665
584
  ...(onLineAsk !== undefined
666
585
  ? {
667
586
  enableLineSelection: true,
@@ -698,10 +617,8 @@ export function DiffSurface(props: IDiffSurfaceProps): React.ReactElement {
698
617
  }
699
618
 
700
619
  /**
701
- * Segmented Notebook/JSON selector. Hosts mount this into whatever toolbar
702
- * they own (the launcher's `MainAreaWidget` toolbar, or the
703
- * `jupyterlab-git`-provided diff toolbar) and drive its value/visibility
704
- * from the same state they pass to {@link DiffSurface}.
620
+ * Segmented Notebook/JSON selector. Hosts mount it into their own toolbar
621
+ * and drive value/visibility from the state they pass to {@link DiffSurface}.
705
622
  */
706
623
  export function NotebookViewModeControl(props: {
707
624
  mode: NotebookDiffViewMode;
@@ -744,11 +661,9 @@ export function NotebookViewModeControl(props: {
744
661
  }
745
662
 
746
663
  /**
747
- * Split / unified glyphs, inlined from the `@pierre/diffs` icon sprite
748
- * (`diffs-icon-diff-split` / `diffs-icon-diff-unified`) so the toggle looks
749
- * like the one on the library's own docs site without depending on the
750
- * sprite sheet being injected into the document. `currentColor` lets the
751
- * segmented-button styling drive the fill (including the active state).
664
+ * Split / unified glyphs inlined from the `@pierre/diffs` icon sprite so
665
+ * the toggle matches the library's docs site without the sprite sheet;
666
+ * `currentColor` lets the button styling drive the fill.
752
667
  */
753
668
  function DiffSplitIcon(): React.ReactElement {
754
669
  return (
@@ -781,11 +696,8 @@ function DiffUnifiedIcon(): React.ReactElement {
781
696
  }
782
697
 
783
698
  /**
784
- * Segmented Split/Unified selector. Mirrors {@link NotebookViewModeControl}:
785
- * hosts mount it into their own toolbar and drive its value/visibility from
786
- * the same state they pass to {@link DiffSurface}. Only meaningful while the
787
- * textual/code file diff is the active view, so `available` mirrors the
788
- * surface's `onFileDiffActiveChange`.
699
+ * Segmented Split/Unified selector; mirrors {@link NotebookViewModeControl}.
700
+ * `available` mirrors the surface's `onFileDiffActiveChange`.
789
701
  */
790
702
  export function DiffStyleControl(props: {
791
703
  diffStyle: DiffStyle;