xtralab 0.4.0 → 0.6.0

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 (96) hide show
  1. package/README.md +110 -73
  2. package/lib/agentSessions.d.ts +50 -0
  3. package/lib/agentSessions.js +45 -0
  4. package/lib/fileBrowser/index.js +5 -1
  5. package/lib/git/commands.d.ts +12 -13
  6. package/lib/git/commands.js +7 -28
  7. package/lib/git/diffProvider.d.ts +8 -0
  8. package/lib/git/diffProvider.js +314 -0
  9. package/lib/git/diffSurface.d.ts +115 -0
  10. package/lib/git/diffSurface.js +342 -0
  11. package/lib/git/diffWidget.d.ts +8 -13
  12. package/lib/git/diffWidget.js +65 -311
  13. package/lib/git/imageDiff.d.ts +23 -0
  14. package/lib/git/imageDiff.js +256 -0
  15. package/lib/git/index.d.ts +10 -8
  16. package/lib/git/index.js +37 -52
  17. package/lib/index.d.ts +4 -2
  18. package/lib/index.js +14 -8
  19. package/lib/launcher/agents.d.ts +1 -1
  20. package/lib/launcher/agents.js +22 -13
  21. package/lib/launcher/commands.d.ts +32 -6
  22. package/lib/launcher/commands.js +81 -57
  23. package/lib/launcher/dashboard.d.ts +15 -0
  24. package/lib/launcher/dashboard.js +119 -5
  25. package/lib/launcher/editorRegistry.d.ts +43 -0
  26. package/lib/launcher/editorRegistry.js +96 -0
  27. package/lib/launcher/editors.d.ts +78 -0
  28. package/lib/launcher/editors.js +110 -0
  29. package/lib/launcher/icons.d.ts +25 -6
  30. package/lib/launcher/icons.js +116 -19
  31. package/lib/launcher/index.d.ts +2 -22
  32. package/lib/launcher/index.js +48 -25
  33. package/lib/launcher/registry.d.ts +21 -0
  34. package/lib/launcher/registry.js +28 -0
  35. package/lib/launcher/tokens.d.ts +41 -0
  36. package/lib/launcher/tokens.js +17 -0
  37. package/lib/terminals/detection.d.ts +11 -0
  38. package/lib/terminals/detection.js +50 -0
  39. package/lib/terminals/index.d.ts +27 -0
  40. package/lib/terminals/index.js +194 -0
  41. package/lib/terminals/model.d.ts +191 -0
  42. package/lib/terminals/model.js +418 -0
  43. package/lib/terminals/widget.d.ts +72 -0
  44. package/lib/terminals/widget.js +93 -0
  45. package/lib/topBar/icons.d.ts +13 -0
  46. package/lib/topBar/icons.js +27 -0
  47. package/lib/topBar/index.d.ts +16 -0
  48. package/lib/topBar/index.js +86 -0
  49. package/package.json +52 -35
  50. package/schema/launcher.json +56 -1
  51. package/src/agentSessions.ts +94 -0
  52. package/src/fileBrowser/index.ts +5 -1
  53. package/src/git/commands.ts +12 -38
  54. package/src/git/diffProvider.tsx +462 -0
  55. package/src/git/diffSurface.tsx +588 -0
  56. package/src/git/diffWidget.tsx +105 -465
  57. package/src/git/imageDiff.tsx +469 -0
  58. package/src/git/index.ts +38 -68
  59. package/src/index.ts +14 -8
  60. package/src/launcher/agents.ts +23 -14
  61. package/src/launcher/commands.ts +98 -60
  62. package/src/launcher/dashboard.tsx +205 -3
  63. package/src/launcher/editorRegistry.ts +149 -0
  64. package/src/launcher/editors.ts +183 -0
  65. package/src/launcher/icons.ts +120 -20
  66. package/src/launcher/index.ts +62 -27
  67. package/src/launcher/registry.ts +33 -0
  68. package/src/launcher/tokens.ts +51 -0
  69. package/src/terminals/detection.ts +65 -0
  70. package/src/terminals/index.ts +222 -0
  71. package/src/terminals/model.ts +497 -0
  72. package/src/terminals/widget.tsx +240 -0
  73. package/src/topBar/icons.ts +30 -0
  74. package/src/topBar/index.ts +121 -0
  75. package/style/git.css +267 -266
  76. package/style/index.css +2 -1
  77. package/style/index.js +2 -1
  78. package/style/launcher.css +124 -2
  79. package/style/terminals.css +235 -0
  80. package/style/topBar.css +38 -0
  81. package/lib/git/icons.d.ts +0 -14
  82. package/lib/git/icons.js +0 -19
  83. package/lib/git/panel.d.ts +0 -29
  84. package/lib/git/panel.js +0 -42
  85. package/lib/git/panelComponent.d.ts +0 -28
  86. package/lib/git/panelComponent.js +0 -320
  87. package/lib/statusBar/index.d.ts +0 -14
  88. package/lib/statusBar/index.js +0 -89
  89. package/lib/statusBar/widget.d.ts +0 -121
  90. package/lib/statusBar/widget.js +0 -237
  91. package/src/git/icons.ts +0 -20
  92. package/src/git/panel.tsx +0 -56
  93. package/src/git/panelComponent.tsx +0 -575
  94. package/src/statusBar/index.ts +0 -115
  95. package/src/statusBar/widget.tsx +0 -318
  96. package/style/statusBar.css +0 -95
@@ -0,0 +1,342 @@
1
+ import * as React from 'react';
2
+ import { undoIcon } from '@jupyterlab/ui-components';
3
+ import { FileDiff } from '@pierre/diffs/react';
4
+ import { diffAcceptRejectHunk, parseDiffFromFile } from '@pierre/diffs';
5
+ import { ImageDiffView, imageDataType } from './imageDiff';
6
+ import { buildNotebookDiff, NotebookDiffView } from './notebookDiff';
7
+ /**
8
+ * Shared diff rendering surface used by both diff hosts in this extension:
9
+ *
10
+ * - the launcher dashboard's own `xtralab:git:open-diff` command
11
+ * (via {@link DiffContentWidget} in `diffWidget.tsx`), and
12
+ * - the `jupyterlab-git` diff providers registered into the upstream git
13
+ * panel (via `XtralabDiffWidget` in `diffProvider.tsx`).
14
+ *
15
+ * The surface is deliberately content-source agnostic: callers resolve the
16
+ * old/new text however they like (xtralab's REST helper, or a
17
+ * `Git.Diff.IModel`'s `content()` getters) and hand the resolved strings in.
18
+ * Everything visual — the `@pierre/diffs` split view, the cell-by-cell
19
+ * notebook view, the draggable column resizer, and the per-hunk discard
20
+ * affordance — lives here so the two hosts stay pixel-identical.
21
+ */
22
+ /**
23
+ * The CSS class added to the diff main-area widget. The selectors that
24
+ * style the embedded `@pierre/diffs` viewer hang off this class.
25
+ */
26
+ export const DIFF_WIDGET_CSS_CLASS = 'jp-xtralab-DiffWidget';
27
+ /**
28
+ * `localStorage` key for the user's last-chosen split ratio, persisted so
29
+ * the preference survives reloads (the library has no built-in way to
30
+ * remember it).
31
+ */
32
+ const SPLIT_RATIO_STORAGE_KEY = 'xtralab:diff-split-ratio';
33
+ /**
34
+ * Bounds on the left-pane fraction. Keeping a few percent on either side
35
+ * guarantees both panes remain visible — dragging to 0/100 would hide a
36
+ * column and is almost never what the user wants.
37
+ */
38
+ const MIN_SPLIT_RATIO = 0.1;
39
+ const MAX_SPLIT_RATIO = 0.9;
40
+ const DEFAULT_SPLIT_RATIO = 0.5;
41
+ /**
42
+ * CSS injected via the `@pierre/diffs` `unsafeCSS` option. The library
43
+ * places it inside the shadow root under `@layer unsafe`, which beats
44
+ * the library's own `@layer base` rule that hardcodes
45
+ * `grid-template-columns: 1fr 1fr` on the split-mode container.
46
+ *
47
+ * The override reads from `--xtralab-split-cols`, a custom property we set
48
+ * on the host element via the `style` prop. Custom properties inherit
49
+ * through the shadow DOM boundary, so adjusting it on the host instantly
50
+ * resizes the columns without re-rendering the diff.
51
+ */
52
+ const SPLIT_RESIZE_CSS = `pre[data-diff-type="split"][data-overflow="scroll"] {
53
+ grid-template-columns: var(--xtralab-split-cols, 1fr 1fr);
54
+ }`;
55
+ function readStoredSplitRatio() {
56
+ try {
57
+ const raw = window.localStorage.getItem(SPLIT_RATIO_STORAGE_KEY);
58
+ if (raw === null) {
59
+ return DEFAULT_SPLIT_RATIO;
60
+ }
61
+ const parsed = Number.parseFloat(raw);
62
+ if (Number.isFinite(parsed) &&
63
+ parsed >= MIN_SPLIT_RATIO &&
64
+ parsed <= MAX_SPLIT_RATIO) {
65
+ return parsed;
66
+ }
67
+ }
68
+ catch (_a) {
69
+ // localStorage may throw in privacy mode or sandboxed contexts. Falling
70
+ // back to the default is fine — the user can resize again on this run.
71
+ }
72
+ return DEFAULT_SPLIT_RATIO;
73
+ }
74
+ function writeStoredSplitRatio(ratio) {
75
+ try {
76
+ window.localStorage.setItem(SPLIT_RATIO_STORAGE_KEY, ratio.toString());
77
+ }
78
+ catch (_a) {
79
+ // See readStoredSplitRatio — best-effort persistence.
80
+ }
81
+ }
82
+ const NOTEBOOK_DIFF_VIEW_MODE_STORAGE_KEY = 'xtralab:notebook-diff-view-mode';
83
+ export function readStoredNotebookViewMode() {
84
+ try {
85
+ const raw = window.localStorage.getItem(NOTEBOOK_DIFF_VIEW_MODE_STORAGE_KEY);
86
+ if (raw === 'json' || raw === 'notebook') {
87
+ return raw;
88
+ }
89
+ }
90
+ catch (_a) {
91
+ // See readStoredSplitRatio — privacy-mode / sandboxed contexts can
92
+ // throw on access. The notebook view is the better default anyway.
93
+ }
94
+ return 'notebook';
95
+ }
96
+ export function writeStoredNotebookViewMode(mode) {
97
+ try {
98
+ window.localStorage.setItem(NOTEBOOK_DIFF_VIEW_MODE_STORAGE_KEY, mode);
99
+ }
100
+ catch (_a) {
101
+ // Best-effort.
102
+ }
103
+ }
104
+ /**
105
+ * Detect notebook files by extension. We only look at the suffix so the
106
+ * caller doesn't need to plumb a content-type field through; mistaking
107
+ * another `.ipynb`-named file for a notebook is harmless because
108
+ * {@link buildNotebookDiff} validates the JSON shape and we fall back to
109
+ * the file diff if parsing fails.
110
+ */
111
+ function isNotebookPath(path) {
112
+ return path.toLowerCase().endsWith('.ipynb');
113
+ }
114
+ /**
115
+ * Decide whether the active JupyterLab theme is a dark theme. Prefers the
116
+ * `IThemeManager` token when available, and falls back to sniffing the
117
+ * `data-jp-theme-light` attribute that JupyterLab sets on `<body>` so the
118
+ * component still works in stripped-down hosts where the token isn't
119
+ * provided (lite, certain notebook configurations, …).
120
+ */
121
+ export function isDarkTheme(themeManager) {
122
+ if (themeManager !== null && themeManager.theme !== null) {
123
+ return !themeManager.isLight(themeManager.theme);
124
+ }
125
+ return document.body.dataset.jpThemeLight === 'false';
126
+ }
127
+ /**
128
+ * The shared, content-source-agnostic diff renderer. See the module
129
+ * docstring for the division of labor between this surface and its hosts.
130
+ */
131
+ export function DiffSurface(props) {
132
+ const { loading, error, isBinary, oldText, newText, newName, oldName, dark, rendermime, notebookViewMode, onNotebookAvailabilityChange, onMetadataChange, hunkDiscard } = props;
133
+ const hasContent = !loading && error === null && !isBinary;
134
+ // Raster images take a dedicated `<img>`-based view instead of a
135
+ // text/notebook diff; the host passes the two sides as base64 strings in
136
+ // `oldText`/`newText` (the git server base64-encodes binary content).
137
+ const imageType = React.useMemo(() => imageDataType(newName), [newName]);
138
+ // Pre-compute the line-oriented diff metadata. Owning it here lets us
139
+ // pass it to {@link FileDiff} *and* thread the same instance through
140
+ // {@link diffAcceptRejectHunk} when the user discards a hunk — so hunk
141
+ // indexes line up between what the user clicked and what we mutate.
142
+ const metadata = React.useMemo(() => {
143
+ if (!hasContent || imageType !== null) {
144
+ return null;
145
+ }
146
+ const oldFile = {
147
+ name: oldName !== null && oldName !== void 0 ? oldName : newName,
148
+ contents: oldText
149
+ };
150
+ const newFile = { name: newName, contents: newText };
151
+ return parseDiffFromFile(oldFile, newFile);
152
+ }, [hasContent, imageType, oldText, newText, oldName, newName]);
153
+ // Cell-by-cell notebook diff for `.ipynb` files. When non-null the
154
+ // renderer uses a notebook-aware view instead of the line-oriented file
155
+ // diff — kept distinct from {@link metadata} so a notebook whose JSON we
156
+ // cannot parse can fall back to the file diff path.
157
+ const notebookDiff = React.useMemo(() => {
158
+ if (!hasContent || imageType !== null || !isNotebookPath(newName)) {
159
+ return null;
160
+ }
161
+ return buildNotebookDiff({ oldText, newText });
162
+ }, [hasContent, imageType, oldText, newText, newName]);
163
+ // Tell the host whether a rendered notebook view is currently available
164
+ // so it can show/hide its Notebook/JSON toggle.
165
+ React.useEffect(() => {
166
+ onNotebookAvailabilityChange === null || onNotebookAvailabilityChange === void 0 ? void 0 : onNotebookAvailabilityChange(notebookDiff !== null);
167
+ }, [onNotebookAvailabilityChange, notebookDiff]);
168
+ // Report the diff shape so hosts can react (e.g. auto-close on empty).
169
+ React.useEffect(() => {
170
+ onMetadataChange === null || onMetadataChange === void 0 ? void 0 : onMetadataChange({
171
+ hunkCount: metadata !== null ? metadata.hunks.length : null
172
+ });
173
+ }, [onMetadataChange, metadata]);
174
+ // Split ratio for the diff columns: fraction of width given to the
175
+ // deletions (left) pane. Persisted across sessions so the user only
176
+ // dials in their layout once.
177
+ const [leftRatio, setLeftRatio] = React.useState(() => readStoredSplitRatio());
178
+ const wrapperRef = React.useRef(null);
179
+ // The drag handler reads the live ratio from a ref so the listeners we
180
+ // attach on pointer-down don't capture a stale value across renders.
181
+ const leftRatioRef = React.useRef(leftRatio);
182
+ React.useEffect(() => {
183
+ leftRatioRef.current = leftRatio;
184
+ }, [leftRatio]);
185
+ const canDiscardHunk = (hunkDiscard === null || hunkDiscard === void 0 ? void 0 : hunkDiscard.enabled) === true;
186
+ const lineAnnotations = React.useMemo(() => {
187
+ if (!canDiscardHunk || metadata === null) {
188
+ return [];
189
+ }
190
+ return metadata.hunks.map((hunk, hunkIndex) => ({
191
+ side: 'additions',
192
+ // The first line of each hunk in the new file. We anchor the button
193
+ // there so it sits at the top of the hunk's body.
194
+ lineNumber: hunk.additionStart,
195
+ metadata: { hunkIndex }
196
+ }));
197
+ }, [canDiscardHunk, metadata]);
198
+ const handleDiscardHunk = React.useCallback(async (hunkIndex) => {
199
+ if (metadata === null || hunkDiscard === undefined) {
200
+ return;
201
+ }
202
+ const updated = diffAcceptRejectHunk(metadata, hunkIndex, 'reject');
203
+ // `additionLines` after a 'reject' contains the full new file with
204
+ // the hunk reverted back to the old content. The library splits
205
+ // file contents with `/(?<=\n)/` (a lookbehind that keeps the
206
+ // trailing `\n` on each line), so the entries already carry their
207
+ // own line endings — joining with `''` rebuilds the original text
208
+ // verbatim, while joining with `'\n'` would double every newline.
209
+ const text = updated.additionLines.join('');
210
+ try {
211
+ await hunkDiscard.save(text);
212
+ hunkDiscard.onAfterSave();
213
+ }
214
+ catch (err) {
215
+ console.error('xtralab: failed to discard hunk', err);
216
+ }
217
+ }, [hunkDiscard, metadata]);
218
+ const renderAnnotation = React.useCallback((annotation) => {
219
+ if (annotation.metadata === undefined) {
220
+ return null;
221
+ }
222
+ const { hunkIndex } = annotation.metadata;
223
+ return (React.createElement("div", { className: "jp-xtralab-DiffWidget-hunkAnnotation" },
224
+ React.createElement("button", { type: "button", className: "jp-xtralab-DiffWidget-hunkButton", title: "Discard this hunk's changes", "aria-label": "Discard hunk", onClick: () => void handleDiscardHunk(hunkIndex) },
225
+ React.createElement(undoIcon.react, { tag: "span", className: "jp-xtralab-DiffWidget-hunkButton-icon", elementSize: "normal" }))));
226
+ }, [handleDiscardHunk]);
227
+ const handleResizerPointerDown = React.useCallback((event) => {
228
+ // Only react to primary-button drags. Right-click / middle-click on
229
+ // the handle should pass through to the browser default.
230
+ if (event.button !== 0) {
231
+ return;
232
+ }
233
+ const wrapper = wrapperRef.current;
234
+ if (wrapper === null) {
235
+ return;
236
+ }
237
+ event.preventDefault();
238
+ const handle = event.currentTarget;
239
+ handle.setPointerCapture(event.pointerId);
240
+ handle.dataset.dragging = 'true';
241
+ const rect = wrapper.getBoundingClientRect();
242
+ const startX = event.clientX;
243
+ const startRatio = leftRatioRef.current;
244
+ const onPointerMove = (ev) => {
245
+ if (rect.width <= 0) {
246
+ return;
247
+ }
248
+ const next = startRatio + (ev.clientX - startX) / rect.width;
249
+ const clamped = Math.max(MIN_SPLIT_RATIO, Math.min(MAX_SPLIT_RATIO, next));
250
+ setLeftRatio(clamped);
251
+ };
252
+ const onPointerEnd = (ev) => {
253
+ handle.releasePointerCapture(ev.pointerId);
254
+ delete handle.dataset.dragging;
255
+ handle.removeEventListener('pointermove', onPointerMove);
256
+ handle.removeEventListener('pointerup', onPointerEnd);
257
+ handle.removeEventListener('pointercancel', onPointerEnd);
258
+ // Only persist on drag-end so a quick drag isn't followed by a
259
+ // burst of writes for every intermediate position.
260
+ writeStoredSplitRatio(leftRatioRef.current);
261
+ };
262
+ handle.addEventListener('pointermove', onPointerMove);
263
+ handle.addEventListener('pointerup', onPointerEnd);
264
+ handle.addEventListener('pointercancel', onPointerEnd);
265
+ }, []);
266
+ const handleResizerDoubleClick = React.useCallback(() => {
267
+ setLeftRatio(DEFAULT_SPLIT_RATIO);
268
+ writeStoredSplitRatio(DEFAULT_SPLIT_RATIO);
269
+ }, []);
270
+ if (loading) {
271
+ return React.createElement("div", { className: "jp-xtralab-DiffWidget-status" }, "Loading diff\u2026");
272
+ }
273
+ if (error !== null) {
274
+ return (React.createElement("div", { className: "jp-xtralab-DiffWidget-status", "data-error": "true" }, error));
275
+ }
276
+ if (isBinary) {
277
+ return (React.createElement("div", { className: "jp-xtralab-DiffWidget-status" }, "Binary file \u2014 diff not supported."));
278
+ }
279
+ if (imageType !== null) {
280
+ // `oldText`/`newText` carry the two revisions as base64 here. The
281
+ // image view owns its own layout and mode selector, so it does not
282
+ // use the split resizer or the notebook/JSON toggle.
283
+ return (React.createElement("div", { className: "jp-xtralab-DiffWidget-content" },
284
+ React.createElement(ImageDiffView, { reference: oldText, challenger: newText, fileType: imageType })));
285
+ }
286
+ // Decide which view to render. The notebook view requires a successful
287
+ // nbformat parse; if that failed, we only show the raw JSON file diff.
288
+ const hasNotebookView = notebookDiff !== null;
289
+ const showNotebookView = hasNotebookView && notebookViewMode === 'notebook';
290
+ const showFileDiff = !showNotebookView && metadata !== null;
291
+ if (!showNotebookView && !showFileDiff) {
292
+ return (React.createElement("div", { className: "jp-xtralab-DiffWidget-status" }, "No content to diff."));
293
+ }
294
+ // Custom property set on the diffs-container host. The library's
295
+ // shadow-root rule consumes it via `var(--xtralab-split-cols, …)` (see
296
+ // SPLIT_RESIZE_CSS) and recomputes the column tracks instantly. Only
297
+ // relevant in the file-diff path; the notebook view doesn't use it.
298
+ const leftPercent = leftRatio * 100;
299
+ const hostStyle = {
300
+ '--xtralab-split-cols': `${leftPercent}% ${100 - leftPercent}%`
301
+ };
302
+ return (React.createElement("div", { className: "jp-xtralab-DiffWidget-content" },
303
+ React.createElement("div", { ref: wrapperRef, className: "jp-xtralab-DiffWidget-body" },
304
+ React.createElement("div", { className: "jp-xtralab-DiffWidget-scroll" }, showNotebookView && notebookDiff !== null ? (React.createElement(NotebookDiffView, { diff: notebookDiff, dark: dark, rendermime: rendermime })) : showFileDiff && metadata !== null ? (React.createElement(FileDiff, { fileDiff: metadata, lineAnnotations: lineAnnotations, renderAnnotation: renderAnnotation, style: hostStyle,
305
+ // Disable the worker pool: JupyterLab's webpack federation
306
+ // pipeline doesn't ship the library's
307
+ // `@pierre/diffs/worker/worker.js` file at a URL the worker
308
+ // bootstrap can resolve from, so leaving the pool enabled
309
+ // crashes on instantiation. Running on the main thread is
310
+ // fine for the small files git diffs typically operate on.
311
+ disableWorkerPool: true, options: {
312
+ diffStyle: 'split',
313
+ // Drop the file header — the tab title already shows the
314
+ // file name and the panel header carries the change
315
+ // context.
316
+ disableFileHeader: true,
317
+ theme: dark ? 'pierre-dark' : 'pierre-light',
318
+ themeType: dark ? 'dark' : 'light',
319
+ // Inject the column-resize override into the shadow root
320
+ // via the library's `@layer unsafe` channel. Keeping the
321
+ // string constant lets the library short-circuit the
322
+ // re-render path it uses when this option actually
323
+ // changes.
324
+ unsafeCSS: SPLIT_RESIZE_CSS
325
+ } })) : null),
326
+ showFileDiff ? (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), title: "Drag to resize the diff panes (double-click to reset)", onPointerDown: handleResizerPointerDown, onDoubleClick: handleResizerDoubleClick })) : null)));
327
+ }
328
+ /**
329
+ * Segmented Notebook/JSON selector. Hosts mount this into whatever toolbar
330
+ * they own (the launcher's `MainAreaWidget` toolbar, or the
331
+ * `jupyterlab-git`-provided diff toolbar) and drive its value/visibility
332
+ * from the same state they pass to {@link DiffSurface}.
333
+ */
334
+ export function NotebookViewModeControl(props) {
335
+ const { mode, available, onChange } = props;
336
+ if (!available) {
337
+ return React.createElement(React.Fragment, null);
338
+ }
339
+ return (React.createElement("div", { className: "jp-xtralab-DiffWidget-segmented", role: "tablist", "aria-label": "Notebook diff view mode" },
340
+ React.createElement("button", { type: "button", role: "tab", "aria-selected": mode === 'notebook', "data-active": mode === 'notebook', className: "jp-xtralab-DiffWidget-segmentedButton", onClick: () => onChange('notebook') }, "Notebook"),
341
+ React.createElement("button", { type: "button", role: "tab", "aria-selected": mode === 'json', "data-active": mode === 'json', className: "jp-xtralab-DiffWidget-segmentedButton", onClick: () => onChange('json') }, "JSON")));
342
+ }
@@ -4,21 +4,11 @@ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
4
4
  import { Contents } from '@jupyterlab/services';
5
5
  import { ReactWidget } from '@jupyterlab/ui-components';
6
6
  import { ISignal } from '@lumino/signaling';
7
+ import { DIFF_WIDGET_CSS_CLASS, type NotebookDiffViewMode } from './diffSurface';
7
8
  import { GitReference, IFileChange } from './tokens';
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
- export declare const DIFF_WIDGET_CSS_CLASS = "jp-xtralab-DiffWidget";
9
+ export { DIFF_WIDGET_CSS_CLASS };
13
10
  export declare const PREVIEW_DIFF_WIDGET_ID = "xtralab:diff:preview";
14
11
  export declare function pinnedDiffWidgetId(change: IFileChange): string;
15
- /**
16
- * View modes available for `.ipynb` diffs. `notebook` is the cell-by-cell
17
- * rendered view; `json` falls back to the raw nbformat JSON file diff so
18
- * the user can inspect exactly which bytes changed (handy for nbformat
19
- * spelunking, seeing execution_count drift, etc.).
20
- */
21
- type NotebookDiffViewMode = 'notebook' | 'json';
22
12
  /**
23
13
  * Build the deterministic widget id used for a diff against a particular
24
14
  * file/group pair. Reused across plugin and command code so reopening a
@@ -69,6 +59,12 @@ export interface IGitDiffWidgetOptions {
69
59
  * usual document chrome — toolbar slot, focus tracker integration, and
70
60
  * spinner-while-loading hooks — and matches the pattern used for other
71
61
  * read-only main-area widgets in the JupyterLab ecosystem.
62
+ *
63
+ * This is the launcher dashboard's own diff path
64
+ * (`xtralab:git:open-diff`), kept independent of `jupyterlab-git`. The
65
+ * actual diff rendering is delegated to the shared {@link DiffSurface};
66
+ * `jupyterlab-git`'s panel reaches the same surface through the providers
67
+ * registered in `diffProvider.tsx`.
72
68
  */
73
69
  export declare function createGitDiffWidget(options: IGitDiffWidgetOptions): MainAreaWidget<DiffContentWidget>;
74
70
  export declare function updateGitDiffWidget(widget: MainAreaWidget<DiffContentWidget>, change: IFileChange): void;
@@ -116,4 +112,3 @@ export declare class DiffContentWidget extends ReactWidget {
116
112
  private _pinned;
117
113
  private _pinnedChanged;
118
114
  }
119
- export {};