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
@@ -5,35 +5,16 @@ import { diffAcceptRejectHunk, parseDiffFromFile } from '@pierre/diffs';
5
5
  import { ImageDiffView, imageDataType } from './imageDiff';
6
6
  import { buildNotebookDiff, NotebookDiffView } from './notebookDiff';
7
7
  import { resolveDiffTheme } from './diffTheme';
8
- /**
9
- * The CSS class added to the diff main-area widget. The selectors that
10
- * style the embedded `@pierre/diffs` viewer hang off this class.
11
- */
12
8
  export const DIFF_WIDGET_CSS_CLASS = 'jp-xtralab-DiffWidget';
13
- /**
14
- * `localStorage` key for the user's last-chosen split ratio, persisted so
15
- * the preference survives reloads (the library has no built-in way to
16
- * remember it).
17
- */
18
9
  const SPLIT_RATIO_STORAGE_KEY = 'xtralab:diff-split-ratio';
19
- /**
20
- * Bounds on the left-pane fraction. Keeping a few percent on either side
21
- * guarantees both panes remain visible — dragging to 0/100 would hide a
22
- * column and is almost never what the user wants.
23
- */
24
10
  const MIN_SPLIT_RATIO = 0.1;
25
11
  const MAX_SPLIT_RATIO = 0.9;
26
12
  const DEFAULT_SPLIT_RATIO = 0.5;
27
13
  /**
28
- * CSS injected via the `@pierre/diffs` `unsafeCSS` option. The library
29
- * places it inside the shadow root under `@layer unsafe`, which beats
30
- * the library's own `@layer base` rule that hardcodes
31
- * `grid-template-columns: 1fr 1fr` on the split-mode container.
32
- *
33
- * The override reads from `--xtralab-split-cols`, a custom property we set
34
- * on the host element via the `style` prop. Custom properties inherit
35
- * through the shadow DOM boundary, so adjusting it on the host instantly
36
- * resizes the columns without re-rendering the diff.
14
+ * Injected via the `unsafeCSS` option into the shadow root's `@layer
15
+ * unsafe`, beating the library's base rule that hardcodes `1fr 1fr`. Reads
16
+ * `--xtralab-split-cols` set on the host — custom properties cross the
17
+ * shadow boundary, so resizing needs no re-render.
37
18
  */
38
19
  const SPLIT_RESIZE_CSS = `pre[data-diff-type="split"][data-overflow="scroll"] {
39
20
  grid-template-columns: var(--xtralab-split-cols, 1fr 1fr);
@@ -52,8 +33,7 @@ function readStoredSplitRatio() {
52
33
  }
53
34
  }
54
35
  catch (_a) {
55
- // localStorage may throw in privacy mode or sandboxed contexts. Falling
56
- // back to the default is fine — the user can resize again on this run.
36
+ // localStorage can throw in privacy mode or sandboxed contexts.
57
37
  }
58
38
  return DEFAULT_SPLIT_RATIO;
59
39
  }
@@ -66,6 +46,9 @@ function writeStoredSplitRatio(ratio) {
66
46
  }
67
47
  }
68
48
  const DIFF_STYLE_STORAGE_KEY = 'xtralab:diff-style';
49
+ /**
50
+ * Read the persisted diff style, defaulting to `'split'`.
51
+ */
69
52
  export function readStoredDiffStyle() {
70
53
  try {
71
54
  const raw = window.localStorage.getItem(DIFF_STYLE_STORAGE_KEY);
@@ -74,10 +57,13 @@ export function readStoredDiffStyle() {
74
57
  }
75
58
  }
76
59
  catch (_a) {
77
- // See readStoredSplitRatio — privacy-mode / sandboxed contexts can throw.
60
+ // See readStoredSplitRatio.
78
61
  }
79
62
  return 'split';
80
63
  }
64
+ /**
65
+ * Persist the diff style to local storage (best-effort).
66
+ */
81
67
  export function writeStoredDiffStyle(style) {
82
68
  try {
83
69
  window.localStorage.setItem(DIFF_STYLE_STORAGE_KEY, style);
@@ -87,6 +73,9 @@ export function writeStoredDiffStyle(style) {
87
73
  }
88
74
  }
89
75
  const NOTEBOOK_DIFF_VIEW_MODE_STORAGE_KEY = 'xtralab:notebook-diff-view-mode';
76
+ /**
77
+ * Read the persisted notebook view mode, defaulting to `'notebook'`.
78
+ */
90
79
  export function readStoredNotebookViewMode() {
91
80
  try {
92
81
  const raw = window.localStorage.getItem(NOTEBOOK_DIFF_VIEW_MODE_STORAGE_KEY);
@@ -95,11 +84,13 @@ export function readStoredNotebookViewMode() {
95
84
  }
96
85
  }
97
86
  catch (_a) {
98
- // See readStoredSplitRatio — privacy-mode / sandboxed contexts can
99
- // throw on access. The notebook view is the better default anyway.
87
+ // See readStoredSplitRatio.
100
88
  }
101
89
  return 'notebook';
102
90
  }
91
+ /**
92
+ * Persist the notebook view mode to local storage (best-effort).
93
+ */
103
94
  export function writeStoredNotebookViewMode(mode) {
104
95
  try {
105
96
  window.localStorage.setItem(NOTEBOOK_DIFF_VIEW_MODE_STORAGE_KEY, mode);
@@ -109,21 +100,15 @@ export function writeStoredNotebookViewMode(mode) {
109
100
  }
110
101
  }
111
102
  /**
112
- * Detect notebook files by extension. We only look at the suffix so the
113
- * caller doesn't need to plumb a content-type field through; mistaking
114
- * another `.ipynb`-named file for a notebook is harmless because
115
- * {@link buildNotebookDiff} validates the JSON shape and we fall back to
116
- * the file diff if parsing fails.
103
+ * Extension-only check; a false positive is harmless because
104
+ * {@link buildNotebookDiff} validates the JSON and the file diff is the fallback.
117
105
  */
118
106
  function isNotebookPath(path) {
119
107
  return path.toLowerCase().endsWith('.ipynb');
120
108
  }
121
109
  /**
122
- * Decide whether the active JupyterLab theme is a dark theme. Prefers the
123
- * `IThemeManager` token when available, and falls back to sniffing the
124
- * `data-jp-theme-light` attribute that JupyterLab sets on `<body>` so the
125
- * component still works in stripped-down hosts where the token isn't
126
- * provided (lite, certain notebook configurations, …).
110
+ * Dark-theme check via `IThemeManager`, falling back to the
111
+ * `data-jp-theme-light` body attribute for hosts without the token.
127
112
  */
128
113
  export function isDarkTheme(themeManager) {
129
114
  if (themeManager !== null && themeManager.theme !== null) {
@@ -145,14 +130,10 @@ export function isPierreTheme(themeManager) {
145
130
  export function DiffSurface(props) {
146
131
  const { loading, error, isBinary, oldText, newText, newName, oldName, dark, pierreTheme, rendermime, notebookViewMode, diffStyle, onNotebookAvailabilityChange, onFileDiffActiveChange, onMetadataChange, hunkDiscard, onLineAsk, trans } = props;
147
132
  const hasContent = !loading && error === null && !isBinary;
148
- // Raster images take a dedicated `<img>`-based view instead of a
149
- // text/notebook diff; the host passes the two sides as base64 strings in
150
- // `oldText`/`newText` (the git server base64-encodes binary content).
133
+ // For raster images the host passes both sides as base64 in oldText/newText.
151
134
  const imageType = React.useMemo(() => imageDataType(newName), [newName]);
152
- // Pre-compute the line-oriented diff metadata. Owning it here lets us
153
- // pass it to {@link FileDiff} *and* thread the same instance through
154
- // {@link diffAcceptRejectHunk} when the user discards a hunk — so hunk
155
- // indexes line up between what the user clicked and what we mutate.
135
+ // Owning the metadata here keeps hunk indexes aligned between FileDiff
136
+ // and diffAcceptRejectHunk on discard.
156
137
  const metadata = React.useMemo(() => {
157
138
  if (!hasContent || imageType !== null) {
158
139
  return null;
@@ -164,47 +145,31 @@ export function DiffSurface(props) {
164
145
  const newFile = { name: newName, contents: newText };
165
146
  return parseDiffFromFile(oldFile, newFile);
166
147
  }, [hasContent, imageType, oldText, newText, oldName, newName]);
167
- // Cell-by-cell notebook diff for `.ipynb` files. When non-null the
168
- // renderer uses a notebook-aware view instead of the line-oriented file
169
- // diff — kept distinct from {@link metadata} so a notebook whose JSON we
170
- // cannot parse can fall back to the file diff path.
171
148
  const notebookDiff = React.useMemo(() => {
172
149
  if (!hasContent || imageType !== null || !isNotebookPath(newName)) {
173
150
  return null;
174
151
  }
175
152
  return buildNotebookDiff({ oldText, newText });
176
153
  }, [hasContent, imageType, oldText, newText, newName]);
177
- // Tell the host whether a rendered notebook view is currently available
178
- // so it can show/hide its Notebook/JSON toggle.
179
154
  React.useEffect(() => {
180
155
  onNotebookAvailabilityChange === null || onNotebookAvailabilityChange === void 0 ? void 0 : onNotebookAvailabilityChange(notebookDiff !== null);
181
156
  }, [onNotebookAvailabilityChange, notebookDiff]);
182
- // Report the diff shape so hosts can react (e.g. auto-close on empty).
183
157
  React.useEffect(() => {
184
158
  onMetadataChange === null || onMetadataChange === void 0 ? void 0 : onMetadataChange({
185
159
  hunkCount: metadata !== null ? metadata.hunks.length : null
186
160
  });
187
161
  }, [onMetadataChange, metadata]);
188
- // Decide which view to render. The notebook view requires a successful
189
- // nbformat parse; if that failed, we only show the raw JSON file diff.
190
- // Computed before the early returns below so the file-diff-active effect
191
- // can depend on it without violating the rules of hooks.
162
+ // Computed before the early returns so the hooks below can depend on
163
+ // them without breaking the rules of hooks.
192
164
  const hasNotebookView = notebookDiff !== null;
193
165
  const showNotebookView = hasNotebookView && notebookViewMode === 'notebook';
194
166
  const showFileDiff = !showNotebookView && metadata !== null;
195
- // The split vs unified choice only affects the textual/code file diff, so
196
- // tell the host whether that view is active to drive its Split/Unified
197
- // toolbar toggle.
198
167
  React.useEffect(() => {
199
168
  onFileDiffActiveChange === null || onFileDiffActiveChange === void 0 ? void 0 : onFileDiffActiveChange(showFileDiff);
200
169
  }, [onFileDiffActiveChange, showFileDiff]);
201
- // Split ratio for the diff columns: fraction of width given to the
202
- // deletions (left) pane. Persisted across sessions so the user only
203
- // dials in their layout once.
204
170
  const [leftRatio, setLeftRatio] = React.useState(() => readStoredSplitRatio());
205
171
  const wrapperRef = React.useRef(null);
206
- // The drag handler reads the live ratio from a ref so the listeners we
207
- // attach on pointer-down don't capture a stale value across renders.
172
+ // Drag listeners read the live ratio from a ref to avoid stale captures.
208
173
  const leftRatioRef = React.useRef(leftRatio);
209
174
  React.useEffect(() => {
210
175
  leftRatioRef.current = leftRatio;
@@ -216,8 +181,6 @@ export function DiffSurface(props) {
216
181
  }
217
182
  return metadata.hunks.map((hunk, hunkIndex) => ({
218
183
  side: 'additions',
219
- // The first line of each hunk in the new file. We anchor the button
220
- // there so it sits at the top of the hunk's body.
221
184
  lineNumber: hunk.additionStart,
222
185
  metadata: { hunkIndex }
223
186
  }));
@@ -227,12 +190,8 @@ export function DiffSurface(props) {
227
190
  return;
228
191
  }
229
192
  const updated = diffAcceptRejectHunk(metadata, hunkIndex, 'reject');
230
- // `additionLines` after a 'reject' contains the full new file with
231
- // the hunk reverted back to the old content. The library splits
232
- // file contents with `/(?<=\n)/` (a lookbehind that keeps the
233
- // trailing `\n` on each line), so the entries already carry their
234
- // own line endings — joining with `''` rebuilds the original text
235
- // verbatim, while joining with `'\n'` would double every newline.
193
+ // additionLines holds the full new file with the hunk reverted; lines keep
194
+ // their trailing `\n` (lookbehind split), so join('') rebuilds it verbatim.
236
195
  const text = updated.additionLines.join('');
237
196
  try {
238
197
  await hunkDiscard.save(text);
@@ -256,15 +215,11 @@ export function DiffSurface(props) {
256
215
  if (onLineAsk === undefined) {
257
216
  return;
258
217
  }
259
- // Anchor to the gutter "+" button that was just clicked. It lives in
260
- // the `@pierre/diffs` shadow root (which is open), parked on the
261
- // hovered/selected line's number element.
218
+ // Anchor to the just-clicked gutter "+" button inside the library's open shadow root.
262
219
  const slot = (_c = (_b = (_a = wrapperRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('diffs-container')) === null || _b === void 0 ? void 0 : _b.shadowRoot) === null || _c === void 0 ? void 0 : _c.querySelector('[data-gutter-utility-slot]');
263
220
  onLineAsk(range, slot instanceof Element ? slot.getBoundingClientRect() : null);
264
221
  }, [onLineAsk]);
265
222
  const handleResizerPointerDown = React.useCallback((event) => {
266
- // Only react to primary-button drags. Right-click / middle-click on
267
- // the handle should pass through to the browser default.
268
223
  if (event.button !== 0) {
269
224
  return;
270
225
  }
@@ -293,8 +248,6 @@ export function DiffSurface(props) {
293
248
  handle.removeEventListener('pointermove', onPointerMove);
294
249
  handle.removeEventListener('pointerup', onPointerEnd);
295
250
  handle.removeEventListener('pointercancel', onPointerEnd);
296
- // Only persist on drag-end so a quick drag isn't followed by a
297
- // burst of writes for every intermediate position.
298
251
  writeStoredSplitRatio(leftRatioRef.current);
299
252
  };
300
253
  handle.addEventListener('pointermove', onPointerMove);
@@ -340,19 +293,12 @@ export function DiffSurface(props) {
340
293
  return (React.createElement("div", { className: "jp-xtralab-DiffWidget-status" }, trans.__('Binary file — diff not supported.')));
341
294
  }
342
295
  if (imageType !== null) {
343
- // `oldText`/`newText` carry the two revisions as base64 here. The
344
- // image view owns its own layout and mode selector, so it does not
345
- // use the split resizer or the notebook/JSON toggle.
346
296
  return (React.createElement("div", { className: "jp-xtralab-DiffWidget-content" },
347
297
  React.createElement(ImageDiffView, { reference: oldText, challenger: newText, fileType: imageType, trans: trans })));
348
298
  }
349
299
  if (!showNotebookView && !showFileDiff) {
350
300
  return (React.createElement("div", { className: "jp-xtralab-DiffWidget-status" }, trans.__('No content to diff.')));
351
301
  }
352
- // Custom property set on the diffs-container host. The library's
353
- // shadow-root rule consumes it via `var(--xtralab-split-cols, …)` (see
354
- // SPLIT_RESIZE_CSS) and recomputes the column tracks instantly. Only
355
- // relevant in the file-diff path; the notebook view doesn't use it.
356
302
  const leftPercent = leftRatio * 100;
357
303
  const hostStyle = {
358
304
  '--xtralab-split-cols': `${leftPercent}% ${100 - leftPercent}%`
@@ -360,29 +306,15 @@ export function DiffSurface(props) {
360
306
  return (React.createElement("div", { className: "jp-xtralab-DiffWidget-content" },
361
307
  React.createElement("div", { ref: wrapperRef, className: "jp-xtralab-DiffWidget-body" },
362
308
  React.createElement("div", { className: "jp-xtralab-DiffWidget-scroll" }, showNotebookView && notebookDiff !== null ? (React.createElement(NotebookDiffView, { diff: notebookDiff, dark: dark, pierreTheme: pierreTheme, rendermime: rendermime, trans: trans })) : showFileDiff && metadata !== null ? (React.createElement(FileDiff, { fileDiff: metadata, lineAnnotations: lineAnnotations, renderAnnotation: renderAnnotation, style: hostStyle,
363
- // Disable the worker pool: JupyterLab's webpack federation
364
- // pipeline doesn't ship the library's
365
- // `@pierre/diffs/worker/worker.js` file at a URL the worker
366
- // bootstrap can resolve from, so leaving the pool enabled
367
- // crashes on instantiation. Running on the main thread is
368
- // fine for the small files git diffs typically operate on.
309
+ // JupyterLab's federation pipeline can't serve the worker bundle
310
+ // at a resolvable URL, so the pool crashes; run on the main thread.
369
311
  disableWorkerPool: true, options: {
370
312
  diffStyle,
371
- // Drop the file header — the tab title already shows the
372
- // file name and the panel header carries the change
373
- // context.
374
313
  disableFileHeader: true,
375
314
  theme: resolveDiffTheme(dark, pierreTheme),
376
315
  themeType: dark ? 'dark' : 'light',
377
- // Inject the column-resize override into the shadow root
378
- // via the library's `@layer unsafe` channel. Keeping the
379
- // string constant lets the library short-circuit the
380
- // re-render path it uses when this option actually
381
- // changes.
316
+ // Constant string lets the library skip its unsafeCSS re-render path.
382
317
  unsafeCSS: SPLIT_RESIZE_CSS,
383
- // Line selection + the gutter "+" button feed the
384
- // ask-agent popup; only wired up when a handler exists so
385
- // a plain diff keeps its passive gutter.
386
318
  ...(onLineAsk !== undefined
387
319
  ? {
388
320
  enableLineSelection: true,
@@ -394,10 +326,8 @@ export function DiffSurface(props) {
394
326
  showFileDiff && diffStyle === 'split' ? (React.createElement("div", { className: "jp-xtralab-DiffWidget-resizer", style: { left: `${leftPercent}%` }, role: "separator", "aria-orientation": "vertical", "aria-valuenow": Math.round(leftPercent), "aria-valuemin": Math.round(MIN_SPLIT_RATIO * 100), "aria-valuemax": Math.round(MAX_SPLIT_RATIO * 100), "aria-label": trans.__('Resize the diff panes'), tabIndex: 0, title: trans.__('Drag to resize the diff panes (double-click to reset)'), onPointerDown: handleResizerPointerDown, onDoubleClick: handleResizerDoubleClick, onKeyDown: handleResizerKeyDown })) : null)));
395
327
  }
396
328
  /**
397
- * Segmented Notebook/JSON selector. Hosts mount this into whatever toolbar
398
- * they own (the launcher's `MainAreaWidget` toolbar, or the
399
- * `jupyterlab-git`-provided diff toolbar) and drive its value/visibility
400
- * from the same state they pass to {@link DiffSurface}.
329
+ * Segmented Notebook/JSON selector. Hosts mount it into their own toolbar
330
+ * and drive value/visibility from the state they pass to {@link DiffSurface}.
401
331
  */
402
332
  export function NotebookViewModeControl(props) {
403
333
  const { mode, available, onChange, trans } = props;
@@ -409,11 +339,9 @@ export function NotebookViewModeControl(props) {
409
339
  React.createElement("button", { type: "button", role: "tab", "aria-selected": mode === 'json', "data-active": mode === 'json', className: "jp-xtralab-DiffWidget-segmentedButton", onClick: () => onChange('json') }, trans.__('JSON'))));
410
340
  }
411
341
  /**
412
- * Split / unified glyphs, inlined from the `@pierre/diffs` icon sprite
413
- * (`diffs-icon-diff-split` / `diffs-icon-diff-unified`) so the toggle looks
414
- * like the one on the library's own docs site without depending on the
415
- * sprite sheet being injected into the document. `currentColor` lets the
416
- * segmented-button styling drive the fill (including the active state).
342
+ * Split / unified glyphs inlined from the `@pierre/diffs` icon sprite so
343
+ * the toggle matches the library's docs site without the sprite sheet;
344
+ * `currentColor` lets the button styling drive the fill.
417
345
  */
418
346
  function DiffSplitIcon() {
419
347
  return (React.createElement("svg", { viewBox: "0 0 16 16", fill: "currentColor", "aria-hidden": "true" },
@@ -426,11 +354,8 @@ function DiffUnifiedIcon() {
426
354
  React.createElement("path", { fillRule: "evenodd", d: "M14 0a2 2 0 0 1 2 2v5.5H0V2a2 2 0 0 1 2-2zM6.5 3.5a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1z", clipRule: "evenodd", opacity: ".4" })));
427
355
  }
428
356
  /**
429
- * Segmented Split/Unified selector. Mirrors {@link NotebookViewModeControl}:
430
- * hosts mount it into their own toolbar and drive its value/visibility from
431
- * the same state they pass to {@link DiffSurface}. Only meaningful while the
432
- * textual/code file diff is the active view, so `available` mirrors the
433
- * surface's `onFileDiffActiveChange`.
357
+ * Segmented Split/Unified selector; mirrors {@link NotebookViewModeControl}.
358
+ * `available` mirrors the surface's `onFileDiffActiveChange`.
434
359
  */
435
360
  export function DiffStyleControl(props) {
436
361
  const { diffStyle, available, onChange, trans } = props;
@@ -14,22 +14,44 @@ export { DIFF_WIDGET_CSS_CLASS };
14
14
  * `Git.Diff.IModel` plus optional xtralab-only metadata.
15
15
  */
16
16
  export interface IXtralabDiffModel extends Git.Diff.IModel {
17
+ /**
18
+ * Whether the file is binary, so no text contents are fetched.
19
+ */
17
20
  isBinary?: boolean;
21
+ /**
22
+ * Whether hunks may be discarded; when absent, a working-tree heuristic decides.
23
+ */
18
24
  canDiscard?: boolean;
25
+ /**
26
+ * The old-side path when the diff represents a rename.
27
+ */
19
28
  oldFilename?: string;
20
29
  }
21
30
  /**
22
31
  * Application-level services the diff model does not carry.
23
32
  */
24
33
  export interface IXtralabDiffContext {
34
+ /**
35
+ * The contents manager used to save hunk-discard results.
36
+ */
25
37
  contentsManager: Contents.IManager;
38
+ /**
39
+ * The rendermime registry for rendered notebook diffs, or `null` to
40
+ * force the textual fallback.
41
+ */
26
42
  rendermime: IRenderMimeRegistry | null;
43
+ /**
44
+ * The theme manager used to track theme changes, or `null` to watch
45
+ * the body attribute instead.
46
+ */
27
47
  themeManager: IThemeManager | null;
28
48
  /**
29
- * The ask-agent popup; when available, diff line selections get an
30
- * "ask an agent about these lines" gutter button.
49
+ * When available, diff line selections get an "ask an agent" gutter button.
31
50
  */
32
51
  askAgent: IAskAgent | null;
52
+ /**
53
+ * The application translation bundle.
54
+ */
33
55
  trans: TranslationBundle;
34
56
  }
35
57
  /**
@@ -37,6 +59,9 @@ export interface IXtralabDiffContext {
37
59
  */
38
60
  export declare class XtralabDiffWidget extends ReactWidget implements Git.Diff.IDiffWidget {
39
61
  constructor(model: Git.Diff.IModel, context: IXtralabDiffContext);
62
+ /**
63
+ * The git diff model being rendered.
64
+ */
40
65
  get model(): Git.Diff.IModel;
41
66
  /**
42
67
  * Swap the rendered model when the launcher reuses its preview tab.
@@ -54,30 +79,81 @@ export declare class XtralabDiffWidget extends ReactWidget implements Git.Diff.I
54
79
  * Re-pull both sides of the model and re-render.
55
80
  */
56
81
  refresh(): Promise<void>;
82
+ /**
83
+ * The current rendered-vs-JSON choice for notebook diffs.
84
+ */
57
85
  get notebookViewMode(): NotebookDiffViewMode;
86
+ /**
87
+ * Set the notebook view mode and persist it.
88
+ */
58
89
  setNotebookViewMode(mode: NotebookDiffViewMode): void;
90
+ /**
91
+ * A signal emitted when the notebook view mode changes.
92
+ */
59
93
  get notebookViewModeChanged(): ISignal<this, NotebookDiffViewMode>;
94
+ /**
95
+ * Whether a rendered notebook view is currently available.
96
+ */
60
97
  get hasNotebookView(): boolean;
98
+ /**
99
+ * Set whether a rendered notebook view is available.
100
+ */
61
101
  setHasNotebookView(value: boolean): void;
102
+ /**
103
+ * A signal emitted when rendered-notebook availability changes.
104
+ */
62
105
  get hasNotebookViewChanged(): ISignal<this, boolean>;
106
+ /**
107
+ * The current split/unified layout for textual file diffs.
108
+ */
63
109
  get diffStyle(): DiffStyle;
110
+ /**
111
+ * Set the diff style and persist it.
112
+ */
64
113
  setDiffStyle(style: DiffStyle): void;
114
+ /**
115
+ * A signal emitted when the diff style changes.
116
+ */
65
117
  get diffStyleChanged(): ISignal<this, DiffStyle>;
66
118
  /**
67
119
  * Whether the Split/Unified toolbar selector currently applies.
68
120
  */
69
121
  get fileDiffActive(): boolean;
122
+ /**
123
+ * Set whether the textual file diff is the active view.
124
+ */
70
125
  setFileDiffActive(value: boolean): void;
126
+ /**
127
+ * A signal emitted when {@link fileDiffActive} changes.
128
+ */
71
129
  get fileDiffActiveChanged(): ISignal<this, boolean>;
72
130
  /**
73
131
  * Emitted when a post-discard reload leaves the diff with no hunks.
74
132
  */
75
133
  get emptied(): ISignal<this, void>;
134
+ /**
135
+ * Emit the {@link emptied} signal.
136
+ */
76
137
  notifyEmptied(): void;
138
+ /**
139
+ * Resolve and clear the pending `refresh()` promise, if any.
140
+ */
77
141
  settleRefresh(): void;
142
+ /**
143
+ * Dispose of the resources held by the widget.
144
+ */
78
145
  dispose(): void;
146
+ /**
147
+ * A counter incremented on each `refresh()` to trigger a content re-fetch.
148
+ */
79
149
  get reloadNonce(): number;
150
+ /**
151
+ * The application-level services used by the rendered diff.
152
+ */
80
153
  get context(): IXtralabDiffContext;
154
+ /**
155
+ * Render the diff view for the current model.
156
+ */
81
157
  protected render(): React.ReactElement;
82
158
  private _model;
83
159
  private _context;
@@ -97,6 +173,9 @@ export declare class XtralabDiffWidget extends ReactWidget implements Git.Diff.I
97
173
  * Structural toolbar type shared across host package boundaries.
98
174
  */
99
175
  interface IDiffToolbar {
176
+ /**
177
+ * Add an item to the toolbar under the given name.
178
+ */
100
179
  addItem(name: string, widget: Widget): boolean;
101
180
  }
102
181
  /**