xtralab 0.15.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/README.md +10 -0
  2. package/lib/about/icons.d.ts +0 -5
  3. package/lib/about/icons.js +0 -5
  4. package/lib/about/index.d.ts +4 -8
  5. package/lib/about/index.js +6 -17
  6. package/lib/agentSessions.d.ts +9 -29
  7. package/lib/agentSessions.js +2 -7
  8. package/lib/askAgent/editorSelection.d.ts +20 -26
  9. package/lib/askAgent/editorSelection.js +11 -26
  10. package/lib/askAgent/icons.d.ts +2 -3
  11. package/lib/askAgent/icons.js +2 -3
  12. package/lib/askAgent/index.d.ts +4 -26
  13. package/lib/askAgent/index.js +54 -158
  14. package/lib/askAgent/popup.d.ts +12 -16
  15. package/lib/askAgent/popup.js +18 -37
  16. package/lib/askAgent/prompt.d.ts +3 -5
  17. package/lib/askAgent/prompt.js +6 -19
  18. package/lib/askAgent/queue.d.ts +11 -21
  19. package/lib/askAgent/queue.js +9 -19
  20. package/lib/askAgent/queuePanel.d.ts +13 -18
  21. package/lib/askAgent/queuePanel.js +15 -37
  22. package/lib/askAgent/targetPicker.d.ts +2 -9
  23. package/lib/askAgent/targetPicker.js +6 -11
  24. package/lib/askAgent/tokens.d.ts +32 -46
  25. package/lib/askAgent/tokens.js +4 -6
  26. package/lib/commandBar/index.d.ts +4 -13
  27. package/lib/commandBar/index.js +21 -39
  28. package/lib/customPanel/index.js +15 -0
  29. package/lib/editorBreadcrumbs/index.d.ts +3 -6
  30. package/lib/editorBreadcrumbs/index.js +10 -6
  31. package/lib/editorBreadcrumbs/widget.d.ts +17 -4
  32. package/lib/editorBreadcrumbs/widget.js +11 -17
  33. package/lib/editorIndent/index.d.ts +4 -12
  34. package/lib/editorIndent/index.js +8 -23
  35. package/lib/fileBrowser/commands.d.ts +28 -19
  36. package/lib/fileBrowser/commands.js +39 -98
  37. package/lib/fileBrowser/contents.d.ts +3 -0
  38. package/lib/fileBrowser/dragAndDrop.d.ts +17 -4
  39. package/lib/fileBrowser/dragAndDrop.js +2 -7
  40. package/lib/fileBrowser/fileBrowser.d.ts +19 -9
  41. package/lib/fileBrowser/fileBrowser.js +68 -216
  42. package/lib/fileBrowser/gitStatus.d.ts +4 -9
  43. package/lib/fileBrowser/gitStatus.js +7 -17
  44. package/lib/fileBrowser/gitignore.d.ts +4 -17
  45. package/lib/fileBrowser/gitignore.js +4 -25
  46. package/lib/fileBrowser/icons.d.ts +7 -17
  47. package/lib/fileBrowser/icons.js +25 -80
  48. package/lib/fileBrowser/index.js +2 -4
  49. package/lib/fileBrowser/widget.d.ts +126 -48
  50. package/lib/fileBrowser/widget.js +89 -4
  51. package/lib/fileTypeIcons/index.js +0 -10
  52. package/lib/git/api.d.ts +5 -13
  53. package/lib/git/api.js +10 -36
  54. package/lib/git/askRequest.d.ts +4 -8
  55. package/lib/git/askRequest.js +6 -20
  56. package/lib/git/commands.d.ts +40 -5
  57. package/lib/git/commands.js +16 -10
  58. package/lib/git/diffModel.js +4 -0
  59. package/lib/git/diffSurface.d.ts +41 -55
  60. package/lib/git/diffSurface.js +42 -117
  61. package/lib/git/diffWidget.d.ts +81 -2
  62. package/lib/git/diffWidget.js +62 -12
  63. package/lib/git/imageDiff.d.ts +5 -6
  64. package/lib/git/imageDiff.js +18 -48
  65. package/lib/git/index.d.ts +0 -3
  66. package/lib/git/index.js +3 -8
  67. package/lib/git/notebookDiff.d.ts +110 -33
  68. package/lib/git/notebookDiff.js +52 -195
  69. package/lib/git/tokens.d.ts +73 -31
  70. package/lib/git/tokens.js +2 -3
  71. package/lib/highlight/index.d.ts +4 -10
  72. package/lib/highlight/index.js +10 -41
  73. package/lib/index.d.ts +0 -9
  74. package/lib/index.js +0 -9
  75. package/lib/launcher/agents.d.ts +55 -51
  76. package/lib/launcher/agents.js +15 -57
  77. package/lib/launcher/availability.d.ts +3 -9
  78. package/lib/launcher/availability.js +3 -9
  79. package/lib/launcher/commands.d.ts +7 -32
  80. package/lib/launcher/commands.js +14 -45
  81. package/lib/launcher/dashboard.d.ts +19 -24
  82. package/lib/launcher/dashboard.js +21 -67
  83. package/lib/launcher/editorRegistry.d.ts +10 -22
  84. package/lib/launcher/editorRegistry.js +14 -18
  85. package/lib/launcher/editors.d.ts +39 -45
  86. package/lib/launcher/editors.js +8 -34
  87. package/lib/launcher/icons.d.ts +0 -12
  88. package/lib/launcher/icons.js +5 -39
  89. package/lib/launcher/index.js +12 -55
  90. package/lib/launcher/invocation.d.ts +4 -10
  91. package/lib/launcher/invocation.js +7 -22
  92. package/lib/launcher/registry.d.ts +9 -7
  93. package/lib/launcher/registry.js +9 -7
  94. package/lib/launcher/tokens.d.ts +10 -22
  95. package/lib/launcher/tokens.js +5 -8
  96. package/lib/menuBar/index.d.ts +3 -10
  97. package/lib/menuBar/index.js +11 -28
  98. package/lib/menus/index.d.ts +3 -9
  99. package/lib/menus/index.js +14 -30
  100. package/lib/omnibox/files.d.ts +2 -4
  101. package/lib/omnibox/files.js +5 -11
  102. package/lib/omnibox/index.d.ts +4 -14
  103. package/lib/omnibox/index.js +9 -27
  104. package/lib/omnibox/model.d.ts +38 -10
  105. package/lib/omnibox/model.js +15 -33
  106. package/lib/omnibox/recents.d.ts +15 -17
  107. package/lib/omnibox/recents.js +12 -17
  108. package/lib/omnibox/tokens.d.ts +5 -10
  109. package/lib/omnibox/tokens.js +2 -6
  110. package/lib/omnibox/widget.d.ts +11 -7
  111. package/lib/omnibox/widget.js +8 -16
  112. package/lib/searchReplace/index.d.ts +4 -20
  113. package/lib/searchReplace/index.js +7 -27
  114. package/lib/showOutput/index.d.ts +3 -12
  115. package/lib/showOutput/index.js +6 -22
  116. package/lib/sidebar/index.d.ts +4 -7
  117. package/lib/sidebar/index.js +5 -12
  118. package/lib/terminalNotifications/index.d.ts +4 -10
  119. package/lib/terminalNotifications/index.js +6 -20
  120. package/lib/terminals/agentTerminals.d.ts +12 -5
  121. package/lib/terminals/agentTerminals.js +25 -44
  122. package/lib/terminals/detection.d.ts +4 -8
  123. package/lib/terminals/detection.js +4 -8
  124. package/lib/terminals/index.d.ts +0 -35
  125. package/lib/terminals/index.js +12 -101
  126. package/lib/terminals/model.d.ts +64 -139
  127. package/lib/terminals/model.js +80 -220
  128. package/lib/terminals/tokens.d.ts +11 -22
  129. package/lib/terminals/tokens.js +2 -2
  130. package/lib/terminals/widget.d.ts +43 -13
  131. package/lib/terminals/widget.js +33 -18
  132. package/lib/topBar/icons.d.ts +3 -8
  133. package/lib/topBar/icons.js +3 -8
  134. package/lib/topBar/index.d.ts +4 -11
  135. package/lib/topBar/index.js +10 -38
  136. package/lib/walkthrough/index.d.ts +4 -9
  137. package/lib/walkthrough/index.js +6 -20
  138. package/lib/walkthrough/panel.d.ts +40 -9
  139. package/lib/walkthrough/panel.js +4 -10
  140. package/package.json +2 -2
  141. package/src/about/icons.ts +0 -5
  142. package/src/about/index.tsx +6 -17
  143. package/src/agentSessions.ts +9 -29
  144. package/src/askAgent/editorSelection.ts +22 -29
  145. package/src/askAgent/icons.ts +2 -3
  146. package/src/askAgent/index.ts +54 -158
  147. package/src/askAgent/popup.tsx +23 -45
  148. package/src/askAgent/prompt.ts +6 -19
  149. package/src/askAgent/queue.ts +13 -24
  150. package/src/askAgent/queuePanel.tsx +21 -46
  151. package/src/askAgent/targetPicker.tsx +5 -15
  152. package/src/askAgent/tokens.ts +32 -46
  153. package/src/commandBar/index.ts +24 -39
  154. package/src/customPanel/index.ts +15 -0
  155. package/src/editorBreadcrumbs/index.ts +10 -6
  156. package/src/editorBreadcrumbs/widget.ts +23 -17
  157. package/src/editorIndent/index.ts +17 -23
  158. package/src/fileBrowser/commands.ts +57 -98
  159. package/src/fileBrowser/contents.ts +3 -0
  160. package/src/fileBrowser/dragAndDrop.ts +17 -7
  161. package/src/fileBrowser/fileBrowser.tsx +84 -223
  162. package/src/fileBrowser/gitStatus.ts +7 -17
  163. package/src/fileBrowser/gitignore.ts +4 -25
  164. package/src/fileBrowser/icons.ts +25 -80
  165. package/src/fileBrowser/index.ts +2 -4
  166. package/src/fileBrowser/widget.tsx +149 -50
  167. package/src/fileTypeIcons/index.ts +0 -10
  168. package/src/git/api.ts +10 -36
  169. package/src/git/askRequest.ts +6 -20
  170. package/src/git/commands.ts +70 -10
  171. package/src/git/diffModel.ts +25 -0
  172. package/src/git/diffSurface.tsx +71 -159
  173. package/src/git/diffWidget.tsx +104 -14
  174. package/src/git/imageDiff.tsx +45 -51
  175. package/src/git/index.ts +3 -8
  176. package/src/git/notebookDiff.tsx +211 -238
  177. package/src/git/tokens.ts +73 -31
  178. package/src/highlight/index.ts +10 -41
  179. package/src/index.ts +0 -9
  180. package/src/launcher/agents.ts +64 -95
  181. package/src/launcher/availability.ts +3 -9
  182. package/src/launcher/commands.ts +14 -51
  183. package/src/launcher/dashboard.tsx +49 -88
  184. package/src/launcher/editorRegistry.ts +21 -29
  185. package/src/launcher/editors.ts +39 -58
  186. package/src/launcher/icons.ts +5 -39
  187. package/src/launcher/index.ts +12 -55
  188. package/src/launcher/invocation.ts +7 -22
  189. package/src/launcher/registry.ts +9 -7
  190. package/src/launcher/tokens.ts +10 -22
  191. package/src/menuBar/index.ts +11 -28
  192. package/src/menus/index.ts +14 -30
  193. package/src/omnibox/files.ts +10 -14
  194. package/src/omnibox/index.ts +9 -27
  195. package/src/omnibox/model.ts +50 -38
  196. package/src/omnibox/recents.ts +17 -22
  197. package/src/omnibox/tokens.ts +5 -10
  198. package/src/omnibox/widget.tsx +14 -19
  199. package/src/searchReplace/index.ts +7 -27
  200. package/src/showOutput/index.ts +6 -22
  201. package/src/sidebar/index.ts +20 -12
  202. package/src/terminalNotifications/index.ts +42 -25
  203. package/src/terminals/agentTerminals.ts +25 -49
  204. package/src/terminals/detection.ts +4 -8
  205. package/src/terminals/index.ts +12 -101
  206. package/src/terminals/model.ts +130 -246
  207. package/src/terminals/tokens.ts +11 -22
  208. package/src/terminals/widget.tsx +48 -31
  209. package/src/topBar/icons.ts +3 -8
  210. package/src/topBar/index.ts +21 -44
  211. package/src/walkthrough/index.ts +6 -20
  212. package/src/walkthrough/panel.ts +40 -10
  213. package/style/about.css +2 -7
  214. package/style/askAgent.css +8 -40
  215. package/style/base.css +9 -45
  216. package/style/chrome.css +108 -308
  217. package/style/commandBar.css +4 -20
  218. package/style/customPanel.css +1 -4
  219. package/style/git.css +22 -184
  220. package/style/highlight.css +1 -3
  221. package/style/launcher.css +6 -70
  222. package/style/omnibox.css +2 -14
  223. package/style/showOutput.css +0 -6
  224. package/style/sidebar.css +29 -9
  225. package/style/tabs.css +5 -13
  226. package/style/terminals.css +6 -53
  227. package/style/topBar.css +2 -22
  228. package/style/walkthrough.css +0 -8
@@ -3,33 +3,28 @@ import { IThemeManager } from '@jupyterlab/apputils';
3
3
  import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
4
4
  import type { TranslationBundle } from '@jupyterlab/translation';
5
5
  import { type SelectedLineRange } from '@pierre/diffs';
6
- /**
7
- * The CSS class added to the diff main-area widget. The selectors that
8
- * style the embedded `@pierre/diffs` viewer hang off this class.
9
- */
10
6
  export declare const DIFF_WIDGET_CSS_CLASS = "jp-xtralab-DiffWidget";
7
+ export type DiffStyle = 'split' | 'unified';
11
8
  /**
12
- * Layout for textual/code diffs, forwarded to `@pierre/diffs` as its
13
- * `diffStyle` option. `split` is the side-by-side old|new view; `unified`
14
- * is the single-column inline view (what diffs.com calls "stacked").
9
+ * Read the persisted diff style, defaulting to `'split'`.
15
10
  */
16
- export type DiffStyle = 'split' | 'unified';
17
11
  export declare function readStoredDiffStyle(): DiffStyle;
18
- export declare function writeStoredDiffStyle(style: DiffStyle): void;
19
12
  /**
20
- * View modes available for `.ipynb` diffs. `notebook` is the cell-by-cell
21
- * rendered view; `json` is the raw nbformat JSON file diff, for inspecting
22
- * exactly which bytes changed.
13
+ * Persist the diff style to local storage (best-effort).
23
14
  */
15
+ export declare function writeStoredDiffStyle(style: DiffStyle): void;
24
16
  export type NotebookDiffViewMode = 'notebook' | 'json';
17
+ /**
18
+ * Read the persisted notebook view mode, defaulting to `'notebook'`.
19
+ */
25
20
  export declare function readStoredNotebookViewMode(): NotebookDiffViewMode;
21
+ /**
22
+ * Persist the notebook view mode to local storage (best-effort).
23
+ */
26
24
  export declare function writeStoredNotebookViewMode(mode: NotebookDiffViewMode): void;
27
25
  /**
28
- * Decide whether the active JupyterLab theme is a dark theme. Prefers the
29
- * `IThemeManager` token when available, and falls back to sniffing the
30
- * `data-jp-theme-light` attribute that JupyterLab sets on `<body>` so the
31
- * component still works in stripped-down hosts where the token isn't
32
- * provided (lite, certain notebook configurations, …).
26
+ * Dark-theme check via `IThemeManager`, falling back to the
27
+ * `data-jp-theme-light` body attribute for hosts without the token.
33
28
  */
34
29
  export declare function isDarkTheme(themeManager: IThemeManager | null): boolean;
35
30
  /**
@@ -37,18 +32,26 @@ export declare function isDarkTheme(themeManager: IThemeManager | null): boolean
37
32
  */
38
33
  export declare function isPierreTheme(themeManager: IThemeManager | null): boolean;
39
34
  /**
40
- * Optional per-hunk discard wiring. When supplied and `enabled`, each hunk
41
- * in a plain-text/code diff gets an inline "discard" button; clicking it
42
- * reverts that hunk and hands the rebuilt full-file text back to the host
43
- * via {@link IHunkDiscard.save}. The host owns the actual write (it knows
44
- * the on-disk path) and triggers any follow-up refresh in
45
- * {@link IHunkDiscard.onAfterSave}.
35
+ * Per-hunk discard wiring: discarding rebuilds the full file text and the
36
+ * host owns the write via `save`, then refreshes in `onAfterSave`.
46
37
  */
47
38
  interface IHunkDiscard {
39
+ /**
40
+ * Whether hunk discarding is available.
41
+ */
48
42
  enabled: boolean;
43
+ /**
44
+ * Persist the full post-discard file text.
45
+ */
49
46
  save: (fullText: string) => Promise<void>;
47
+ /**
48
+ * Called after a successful save so the host can refresh the diff.
49
+ */
50
50
  onAfterSave: () => void;
51
51
  }
52
+ /**
53
+ * The props for the {@link DiffSurface} component.
54
+ */
52
55
  interface IDiffSurfaceProps {
53
56
  /**
54
57
  * Whether the host is still resolving the file contents.
@@ -71,13 +74,11 @@ interface IDiffSurfaceProps {
71
74
  */
72
75
  newText: string;
73
76
  /**
74
- * New-side path. Drives notebook detection and the `@pierre/diffs` file
75
- * name on the additions side.
77
+ * New-side path; drives notebook detection and the highlighting filename.
76
78
  */
77
79
  newName: string;
78
80
  /**
79
- * Old-side path. Differs from {@link newName} only for renames; defaults
80
- * to {@link newName} when the host does not track a previous path.
81
+ * Old-side path; differs from {@link newName} only for renames.
81
82
  */
82
83
  oldName?: string;
83
84
  /**
@@ -89,35 +90,28 @@ interface IDiffSurfaceProps {
89
90
  */
90
91
  pierreTheme: boolean;
91
92
  /**
92
- * Rendermime registry used by the notebook view to render outputs and
93
- * markdown cells. May be `null` in stripped-down hosts; the notebook
94
- * diff falls back to a textual representation in that case.
93
+ * Used by the notebook view; `null` in stripped-down hosts, which forces a textual fallback.
95
94
  */
96
95
  rendermime: IRenderMimeRegistry | null;
97
96
  /**
98
- * Current rendered-vs-JSON choice for notebook diffs (host-controlled).
97
+ * Rendered-vs-JSON choice for notebook diffs (host-controlled).
99
98
  */
100
99
  notebookViewMode: NotebookDiffViewMode;
101
100
  /**
102
- * Split vs unified layout for the textual/code file diff (host-controlled).
101
+ * Split vs unified layout for the textual file diff (host-controlled).
103
102
  */
104
103
  diffStyle: DiffStyle;
105
104
  /**
106
- * Called whenever the availability of a rendered notebook view changes,
107
- * so the host can show/hide its Notebook/JSON toolbar toggle.
105
+ * Fires when rendered-notebook availability changes so the host can toggle its Notebook/JSON control.
108
106
  */
109
107
  onNotebookAvailabilityChange?: (available: boolean) => void;
110
108
  /**
111
- * Called whenever the textual/code file diff (the only view the split vs
112
- * unified choice affects) becomes the active view, so the host can
113
- * show/hide its Split/Unified toolbar toggle. False for image, binary,
114
- * rendered-notebook, loading and error states.
109
+ * Fires when the textual file diff becomes (in)active — the only view the split/unified choice affects.
115
110
  */
116
111
  onFileDiffActiveChange?: (active: boolean) => void;
117
112
  /**
118
- * Called after each (re)computation of the diff with the number of hunks
119
- * (or `null` when no textual diff exists). Hosts use this to implement
120
- * post-discard behaviors such as auto-closing an emptied diff.
113
+ * Fires after each diff computation with the hunk count (`null` when no
114
+ * textual diff); hosts use it to auto-close emptied diffs.
121
115
  */
122
116
  onMetadataChange?: (info: {
123
117
  hunkCount: number | null;
@@ -127,11 +121,8 @@ interface IDiffSurfaceProps {
127
121
  */
128
122
  hunkDiscard?: IHunkDiscard;
129
123
  /**
130
- * When provided, the textual/code file diff gets line selection: clicking
131
- * or dragging over the line numbers selects a range, and a gutter "+"
132
- * button appears on the hovered/selected lines. Clicking that button
133
- * invokes this callback with the selected range and the button's viewport
134
- * rectangle (to anchor a popup to), `null` when it cannot be measured.
124
+ * Enables line selection and the gutter "+" button; called with the
125
+ * selected range and the button's viewport rect (`null` when unmeasurable).
135
126
  */
136
127
  onLineAsk?: (range: SelectedLineRange, anchor: DOMRect | null) => void;
137
128
  /**
@@ -144,10 +135,8 @@ interface IDiffSurfaceProps {
144
135
  */
145
136
  export declare function DiffSurface(props: IDiffSurfaceProps): React.ReactElement;
146
137
  /**
147
- * Segmented Notebook/JSON selector. Hosts mount this into whatever toolbar
148
- * they own (the launcher's `MainAreaWidget` toolbar, or the
149
- * `jupyterlab-git`-provided diff toolbar) and drive its value/visibility
150
- * from the same state they pass to {@link DiffSurface}.
138
+ * Segmented Notebook/JSON selector. Hosts mount it into their own toolbar
139
+ * and drive value/visibility from the state they pass to {@link DiffSurface}.
151
140
  */
152
141
  export declare function NotebookViewModeControl(props: {
153
142
  mode: NotebookDiffViewMode;
@@ -156,11 +145,8 @@ export declare function NotebookViewModeControl(props: {
156
145
  trans: TranslationBundle;
157
146
  }): React.ReactElement;
158
147
  /**
159
- * Segmented Split/Unified selector. Mirrors {@link NotebookViewModeControl}:
160
- * hosts mount it into their own toolbar and drive its value/visibility from
161
- * the same state they pass to {@link DiffSurface}. Only meaningful while the
162
- * textual/code file diff is the active view, so `available` mirrors the
163
- * surface's `onFileDiffActiveChange`.
148
+ * Segmented Split/Unified selector; mirrors {@link NotebookViewModeControl}.
149
+ * `available` mirrors the surface's `onFileDiffActiveChange`.
164
150
  */
165
151
  export declare function DiffStyleControl(props: {
166
152
  diffStyle: DiffStyle;
@@ -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;