collabmd 0.1.31 → 0.1.32

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 (93) hide show
  1. package/dist/client/assets/{drawio-editor-B4sHHwWJ.js → drawio-editor-Br4qyh3r.js} +1 -1
  2. package/dist/client/assets/drawioEditor-ClmnTx5J.js +2 -0
  3. package/dist/client/assets/{editor-session-BKHEYkbY.js → editor-session-Cf-iV5-3.js} +2 -2
  4. package/dist/client/assets/embedded-editor-base-CgEkjNYn.css +1 -0
  5. package/dist/client/assets/{excalidraw-editor-CHbeNZu6.js → excalidraw-editor-CXAxVhlw.js} +2 -2
  6. package/dist/client/assets/{excalidrawEditor-BhaTX2Ko.js → excalidrawEditor-DoH_kMdu.js} +2 -2
  7. package/dist/client/assets/exportDocument-9wTrBZNS.css +1 -0
  8. package/dist/client/assets/{index-kPgEYvEX.js → index-C8QZYHvV.js} +2 -2
  9. package/dist/client/assets/index-CxSbUTs2.css +1 -0
  10. package/dist/client/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  11. package/dist/client/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  12. package/dist/client/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  13. package/dist/client/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  14. package/dist/client/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  15. package/dist/client/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  16. package/dist/client/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  17. package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  18. package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  19. package/dist/client/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  20. package/dist/client/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  21. package/dist/client/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  22. package/dist/client/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  23. package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  24. package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  25. package/dist/client/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  26. package/dist/client/assets/main-BjHHMlv0.js +1249 -0
  27. package/dist/client/assets/vault-api-client-Bf9tB_GW.js +1 -0
  28. package/dist/client/assets/vault-api-client-D7Ck0CvR.css +1 -0
  29. package/dist/client/drawio-editor.html +1 -1
  30. package/dist/client/excalidraw-editor.html +1 -1
  31. package/dist/client/export-document.html +2 -2
  32. package/dist/client/index.html +2 -2
  33. package/package.json +4 -1
  34. package/src/client/app/main-entry.js +2 -0
  35. package/src/client/application/app-shell/git-feature.js +28 -36
  36. package/src/client/application/app-shell/presence-feature.js +3 -1
  37. package/src/client/application/diagram-preview-hydrator.js +24 -15
  38. package/src/client/application/mermaid-preview-hydrator.js +121 -4
  39. package/src/client/application/plantuml-preview-hydrator.js +6 -16
  40. package/src/client/application/preview-renderer.js +11 -2
  41. package/src/client/bootstrap/collabmd-app-shell.js +176 -50
  42. package/src/client/domain/excalidraw-document-switch.js +3 -0
  43. package/src/client/excalidraw-editor.js +187 -84
  44. package/src/client/infrastructure/auth-client.js +4 -4
  45. package/src/client/infrastructure/backlinks-api-client.js +18 -0
  46. package/src/client/infrastructure/comment-thread-store.js +4 -0
  47. package/src/client/infrastructure/editor-session.js +2 -0
  48. package/src/client/infrastructure/git-api-client.js +134 -0
  49. package/src/client/infrastructure/plantuml-api-client.js +21 -0
  50. package/src/client/infrastructure/vault-api-client.js +67 -0
  51. package/src/client/presentation/backlinks-panel.js +6 -9
  52. package/src/client/presentation/bases-preview-controller.js +26 -7
  53. package/src/client/presentation/excalidraw-embed-controller.js +251 -15
  54. package/src/client/presentation/excalidraw-embed-reconciler.js +0 -1
  55. package/src/client/presentation/file-action-controller.js +97 -0
  56. package/src/client/presentation/file-explorer-controller.js +2 -0
  57. package/src/client/presentation/file-explorer-view.js +368 -0
  58. package/src/client/presentation/file-history-view-controller.js +41 -40
  59. package/src/client/presentation/git-diff-view-controller.js +70 -53
  60. package/src/client/presentation/git-panel-controller.js +12 -30
  61. package/src/client/styles/export-document.css +45 -2
  62. package/src/client/styles/features/auth-gate.css +13 -24
  63. package/src/client/styles/features/comment-card.css +6 -0
  64. package/src/client/styles/features/comments-drawer.css +38 -18
  65. package/src/client/styles/features/diagram-preview.css +33 -0
  66. package/src/client/styles/features/embedded-preview.css +37 -11
  67. package/src/client/styles/features/preview-markdown.css +103 -28
  68. package/src/client/styles/foundation/themes.css +4 -0
  69. package/src/client/styles/foundation/tokens.css +4 -3
  70. package/src/client/styles/layout/sidebar.css +72 -0
  71. package/src/server/auth/auth-common.js +224 -0
  72. package/src/server/auth/auth-constants.js +30 -0
  73. package/src/server/auth/auth-strategies.js +320 -0
  74. package/src/server/auth/create-auth-service.js +24 -545
  75. package/src/server/domain/bases/base-definition.js +255 -0
  76. package/src/server/domain/bases/base-expression-runtime.js +1159 -0
  77. package/src/server/domain/bases/base-index-snapshot-store.js +418 -0
  78. package/src/server/domain/bases/base-query-results.js +205 -0
  79. package/src/server/domain/bases/base-query-service.js +68 -1965
  80. package/src/server/domain/collaboration/collaboration-room.js +36 -1
  81. package/src/server/domain/collaboration/room-client-state-store.js +1 -0
  82. package/src/server/domain/collaboration/room-persistence-controller.js +1 -1
  83. package/src/server/infrastructure/http/create-vault-api-query-handler.js +109 -1
  84. package/src/server/infrastructure/http/http-response.js +56 -0
  85. package/src/server/infrastructure/persistence/vault-file-store.js +110 -0
  86. package/dist/client/assets/drawioEditor-DGpo2hcx.js +0 -2
  87. package/dist/client/assets/embedded-editor-base-DgmRcsR8.css +0 -1
  88. package/dist/client/assets/exportDocument-DmV7Gngb.css +0 -1
  89. package/dist/client/assets/index-D69HOZHr.css +0 -1
  90. package/dist/client/assets/main-DSxNTtD-.js +0 -1243
  91. package/dist/client/assets/vault-api-client-BzHQseMN.js +0 -1
  92. package/dist/client/assets/vault-api-client-De7kMvrE.css +0 -1
  93. /package/dist/client/assets/{exportDocument-CtZKWE2c.js → exportDocument-Bia2hI25.js} +0 -0
@@ -1,5 +1,4 @@
1
1
  import { escapeHtml } from '../domain/vault-utils.js';
2
- import { resolveApiUrl } from '../domain/runtime-paths.js';
3
2
  import { buttonClassNames } from './components/ui/button.js';
4
3
 
5
4
  const HISTORY_PAGE_SIZE = 30;
@@ -62,6 +61,7 @@ function createEmptyHistoryState() {
62
61
  export class FileHistoryViewController {
63
62
  constructor({
64
63
  diffRenderer = null,
64
+ gitApiClient = null,
65
65
  onOpenCommitDiff = () => {},
66
66
  onOpenFile = () => {},
67
67
  onOpenPreview = () => {},
@@ -69,6 +69,7 @@ export class FileHistoryViewController {
69
69
  toastController = null,
70
70
  } = {}) {
71
71
  this.diffRenderer = diffRenderer;
72
+ this.gitApiClient = gitApiClient;
72
73
  this.onOpenCommitDiff = onOpenCommitDiff;
73
74
  this.onOpenFile = onOpenFile;
74
75
  this.onOpenPreview = onOpenPreview;
@@ -89,6 +90,7 @@ export class FileHistoryViewController {
89
90
  this.nextButton = document.getElementById('diffNextBtn');
90
91
  this.layoutToggle = document.getElementById('diffLayoutToggle');
91
92
  this.modeButtons = Array.from(document.querySelectorAll('[data-diff-mode]'));
93
+ this.isActive = false;
92
94
  this.currentFilePath = null;
93
95
  this.localChanges = null;
94
96
  this.history = createEmptyHistoryState();
@@ -104,6 +106,9 @@ export class FileHistoryViewController {
104
106
 
105
107
  initialize() {
106
108
  this.openEditorButton?.addEventListener('click', () => {
109
+ if (!this.isActive) {
110
+ return;
111
+ }
107
112
  if (!this.currentFilePath) {
108
113
  return;
109
114
  }
@@ -113,6 +118,9 @@ export class FileHistoryViewController {
113
118
 
114
119
  this.modeButtons.forEach((button) => {
115
120
  button.addEventListener('click', () => {
121
+ if (!this.isActive) {
122
+ return;
123
+ }
116
124
  const nextMode = button.getAttribute('data-diff-mode');
117
125
  if (!nextMode || nextMode === this.diffMode) {
118
126
  return;
@@ -123,6 +131,9 @@ export class FileHistoryViewController {
123
131
  });
124
132
 
125
133
  this.content?.addEventListener('click', (event) => {
134
+ if (!this.isActive) {
135
+ return;
136
+ }
126
137
  const selectionButton = event.target instanceof Element
127
138
  ? event.target.closest('[data-file-history-select]')
128
139
  : null;
@@ -160,6 +171,7 @@ export class FileHistoryViewController {
160
171
  }
161
172
 
162
173
  hide() {
174
+ this.isActive = false;
163
175
  this.page?.classList.add('hidden');
164
176
  this.content?.classList.remove('diff-content--history');
165
177
  if (this.content) {
@@ -175,7 +187,6 @@ export class FileHistoryViewController {
175
187
  this.historyListElement = null;
176
188
  this.historyListScrollTop = 0;
177
189
  this.selectionRequestToken = 0;
178
- this.syncToolbar();
179
190
  }
180
191
 
181
192
  getEntries() {
@@ -229,6 +240,7 @@ export class FileHistoryViewController {
229
240
  }
230
241
 
231
242
  async openFileHistory({ filePath }) {
243
+ this.isActive = true;
232
244
  this.currentFilePath = String(filePath ?? '').trim() || null;
233
245
  this.localChanges = null;
234
246
  this.history = {
@@ -289,16 +301,11 @@ export class FileHistoryViewController {
289
301
  return null;
290
302
  }
291
303
 
292
- const query = new URLSearchParams();
293
- query.set('metaOnly', 'true');
294
- query.set('path', this.currentFilePath);
295
- query.set('scope', 'all');
296
- const response = await fetch(resolveApiUrl(`/git/diff?${query.toString()}`));
297
- const data = await response.json();
298
- if (!response.ok) {
299
- throw new Error(data.error || 'Failed to load local file changes');
300
- }
301
-
304
+ const data = await this.gitApiClient.readDiff({
305
+ metaOnly: true,
306
+ path: this.currentFilePath,
307
+ scope: 'all',
308
+ });
302
309
  const file = data.files?.[0];
303
310
  if (!file) {
304
311
  return null;
@@ -322,16 +329,11 @@ export class FileHistoryViewController {
322
329
  };
323
330
  }
324
331
 
325
- const query = new URLSearchParams();
326
- query.set('path', this.currentFilePath);
327
- query.set('limit', String(HISTORY_PAGE_SIZE));
328
- query.set('offset', String(offset));
329
- const response = await fetch(resolveApiUrl(`/git/file-history?${query.toString()}`));
330
- const data = await response.json();
331
- if (!response.ok) {
332
- throw new Error(data.error || 'Failed to load file history');
333
- }
334
-
332
+ const data = await this.gitApiClient.readFileHistory({
333
+ limit: HISTORY_PAGE_SIZE,
334
+ offset,
335
+ path: this.currentFilePath,
336
+ });
335
337
  const commits = Array.isArray(data.commits) ? data.commits : [];
336
338
  return {
337
339
  commits,
@@ -350,30 +352,20 @@ export class FileHistoryViewController {
350
352
  }
351
353
 
352
354
  if (entry.type === 'local') {
353
- const query = new URLSearchParams();
354
- query.set('path', this.currentFilePath || '');
355
- query.set('scope', 'all');
356
- const response = await fetch(resolveApiUrl(`/git/diff?${query.toString()}`));
357
- const data = await response.json();
358
- if (!response.ok) {
359
- throw new Error(data.error || 'Failed to load local diff');
360
- }
361
-
355
+ const data = await this.gitApiClient.readDiff({
356
+ path: this.currentFilePath || '',
357
+ scope: 'all',
358
+ });
362
359
  return {
363
360
  detail: data.files?.[0] ?? null,
364
361
  summary: data.summary ?? entry.summary ?? null,
365
362
  };
366
363
  }
367
364
 
368
- const query = new URLSearchParams();
369
- query.set('hash', entry.hash || '');
370
- query.set('path', entry.pathAtCommit || '');
371
- const response = await fetch(resolveApiUrl(`/git/commit?${query.toString()}`));
372
- const data = await response.json();
373
- if (!response.ok) {
374
- throw new Error(data.error || 'Failed to load commit diff');
375
- }
376
-
365
+ const data = await this.gitApiClient.readCommit({
366
+ hash: entry.hash || '',
367
+ path: entry.pathAtCommit || '',
368
+ });
377
369
  return {
378
370
  commit: data.commit ?? null,
379
371
  detail: data.files?.[0] ?? null,
@@ -564,6 +556,9 @@ export class FileHistoryViewController {
564
556
  }
565
557
 
566
558
  renderLoading() {
559
+ if (!this.isActive) {
560
+ return;
561
+ }
567
562
  this.page?.classList.remove('hidden');
568
563
  if (this.content) {
569
564
  this.content.classList.add('diff-content--history');
@@ -574,6 +569,9 @@ export class FileHistoryViewController {
574
569
  }
575
570
 
576
571
  renderEmpty(message) {
572
+ if (!this.isActive) {
573
+ return;
574
+ }
577
575
  this.page?.classList.remove('hidden');
578
576
  if (this.content) {
579
577
  this.content.classList.add('diff-content--history');
@@ -744,6 +742,9 @@ export class FileHistoryViewController {
744
742
  }
745
743
 
746
744
  render() {
745
+ if (!this.isActive) {
746
+ return;
747
+ }
747
748
  this.page?.classList.remove('hidden');
748
749
  this.captureListScroll();
749
750
  this.syncToolbar();
@@ -1,5 +1,4 @@
1
1
  import { escapeHtml } from '../domain/vault-utils.js';
2
- import { resolveApiUrl } from '../domain/runtime-paths.js';
3
2
  import { buttonClassNames } from './components/ui/button.js';
4
3
 
5
4
  function getPathLeaf(pathValue) {
@@ -146,6 +145,7 @@ function renderSplitRow(leftLine, rightLine) {
146
145
 
147
146
  export class GitDiffViewController {
148
147
  constructor({
148
+ gitApiClient = null,
149
149
  onBackToHistory = null,
150
150
  onCommitStaged = null,
151
151
  onOpenFile = null,
@@ -153,6 +153,7 @@ export class GitDiffViewController {
153
153
  onUnstageFile = null,
154
154
  toastController = null,
155
155
  } = {}) {
156
+ this.gitApiClient = gitApiClient;
156
157
  this.onBackToHistory = onBackToHistory;
157
158
  this.onCommitStaged = onCommitStaged;
158
159
  this.onOpenFile = onOpenFile;
@@ -176,6 +177,7 @@ export class GitDiffViewController {
176
177
  this.layoutToggle = document.getElementById('diffLayoutToggle');
177
178
  this.modeButtons = Array.from(document.querySelectorAll('[data-diff-mode]'));
178
179
  this.layoutButtons = Array.from(document.querySelectorAll('[data-diff-layout]'));
180
+ this.isActive = false;
179
181
  this.mode = 'unified';
180
182
  this.layoutMode = 'focused';
181
183
  this.source = 'workspace';
@@ -196,9 +198,22 @@ export class GitDiffViewController {
196
198
  }
197
199
 
198
200
  initialize() {
199
- this.prevButton?.addEventListener('click', () => this.navigateFile(-1));
200
- this.nextButton?.addEventListener('click', () => this.navigateFile(1));
201
+ this.prevButton?.addEventListener('click', () => {
202
+ if (!this.isActive) {
203
+ return;
204
+ }
205
+ this.navigateFile(-1);
206
+ });
207
+ this.nextButton?.addEventListener('click', () => {
208
+ if (!this.isActive) {
209
+ return;
210
+ }
211
+ this.navigateFile(1);
212
+ });
201
213
  this.backToHistoryButton?.addEventListener('click', () => {
214
+ if (!this.isActive) {
215
+ return;
216
+ }
202
217
  if (!this.commitMeta?.hash) {
203
218
  return;
204
219
  }
@@ -208,6 +223,9 @@ export class GitDiffViewController {
208
223
  });
209
224
  });
210
225
  this.openEditorButton?.addEventListener('click', () => {
226
+ if (!this.isActive) {
227
+ return;
228
+ }
211
229
  if (this.source !== 'workspace') {
212
230
  return;
213
231
  }
@@ -220,6 +238,9 @@ export class GitDiffViewController {
220
238
  this.onOpenFile?.(currentFile.path);
221
239
  });
222
240
  this.primaryActionButton?.addEventListener('click', () => {
241
+ if (!this.isActive) {
242
+ return;
243
+ }
223
244
  const action = this.getPrimaryAction();
224
245
  if (!action) {
225
246
  return;
@@ -228,9 +249,15 @@ export class GitDiffViewController {
228
249
  void this.handleFileAction(action);
229
250
  });
230
251
  this.commitButton?.addEventListener('click', () => {
252
+ if (!this.isActive) {
253
+ return;
254
+ }
231
255
  void this.handleFileAction('commit');
232
256
  });
233
257
  this.content?.addEventListener('click', (event) => {
258
+ if (!this.isActive) {
259
+ return;
260
+ }
234
261
  const loadButton = event.target instanceof Element
235
262
  ? event.target.closest('[data-load-full-diff]')
236
263
  : null;
@@ -265,6 +292,9 @@ export class GitDiffViewController {
265
292
  });
266
293
  this.modeButtons.forEach((button) => {
267
294
  button.addEventListener('click', () => {
295
+ if (!this.isActive) {
296
+ return;
297
+ }
268
298
  const nextMode = button.getAttribute('data-diff-mode');
269
299
  if (!nextMode || nextMode === this.mode) {
270
300
  return;
@@ -275,6 +305,9 @@ export class GitDiffViewController {
275
305
  });
276
306
  this.layoutButtons.forEach((button) => {
277
307
  button.addEventListener('click', () => {
308
+ if (!this.isActive) {
309
+ return;
310
+ }
278
311
  const nextLayout = button.getAttribute('data-diff-layout');
279
312
  if (!nextLayout || nextLayout === this.layoutMode) {
280
313
  return;
@@ -283,11 +316,15 @@ export class GitDiffViewController {
283
316
  });
284
317
  });
285
318
  this.scrollContainer?.addEventListener('scroll', () => {
319
+ if (!this.isActive) {
320
+ return;
321
+ }
286
322
  this.handleScrollSelection();
287
323
  });
288
324
  }
289
325
 
290
326
  hide() {
327
+ this.isActive = false;
291
328
  this.page?.classList.add('hidden');
292
329
  if (this.content) {
293
330
  this.content.innerHTML = '';
@@ -308,10 +345,10 @@ export class GitDiffViewController {
308
345
  this.historyFilePath = null;
309
346
  this.pendingAction = null;
310
347
  this.repoStatus = null;
311
- this.syncToolbar();
312
348
  }
313
349
 
314
350
  async openWorkspaceDiff({ filePath = null, scope = 'all' } = {}) {
351
+ this.isActive = true;
315
352
  this.source = 'workspace';
316
353
  this.layoutMode = 'focused';
317
354
  this.commitHash = null;
@@ -327,19 +364,11 @@ export class GitDiffViewController {
327
364
  this.renderLoading('Loading diff summary...');
328
365
 
329
366
  try {
330
- const query = new URLSearchParams();
331
- query.set('scope', scope);
332
- if (filePath) {
333
- query.set('path', filePath);
334
- }
335
- query.set('metaOnly', 'true');
336
-
337
- const response = await fetch(resolveApiUrl(`/git/diff?${query.toString()}`));
338
- const data = await response.json();
339
- if (!response.ok) {
340
- throw new Error(data.error || 'Failed to load git diff');
341
- }
342
-
367
+ const data = await this.gitApiClient.readDiff({
368
+ metaOnly: true,
369
+ path: filePath,
370
+ scope,
371
+ });
343
372
  this.data = data;
344
373
  const initialIndex = filePath
345
374
  ? Math.max(0, data.files.findIndex((file) => file.path === filePath))
@@ -368,6 +397,7 @@ export class GitDiffViewController {
368
397
  }
369
398
 
370
399
  async openCommitDiff({ hash, path = null, historyFilePath = null } = {}) {
400
+ this.isActive = true;
371
401
  this.source = 'commit';
372
402
  this.layoutMode = 'stacked';
373
403
  this.commitHash = String(hash ?? '').trim() || null;
@@ -383,16 +413,10 @@ export class GitDiffViewController {
383
413
  this.renderLoading('Loading commit summary...');
384
414
 
385
415
  try {
386
- const query = new URLSearchParams();
387
- query.set('hash', this.commitHash || '');
388
- query.set('metaOnly', 'true');
389
-
390
- const response = await fetch(resolveApiUrl(`/git/commit?${query.toString()}`));
391
- const data = await response.json();
392
- if (!response.ok) {
393
- throw new Error(data.error || 'Failed to load git commit');
394
- }
395
-
416
+ const data = await this.gitApiClient.readCommit({
417
+ hash: this.commitHash || '',
418
+ metaOnly: true,
419
+ });
396
420
  this.data = data;
397
421
  this.commitMeta = data.commit ?? null;
398
422
  const initialIndex = path
@@ -476,6 +500,9 @@ export class GitDiffViewController {
476
500
  }
477
501
 
478
502
  renderLoading(message = 'Loading git diff...') {
503
+ if (!this.isActive) {
504
+ return;
505
+ }
479
506
  this.page?.classList.remove('hidden');
480
507
  if (this.content) {
481
508
  this.content.innerHTML = `<div class="diff-empty-state">${escapeHtml(message)}</div>`;
@@ -485,6 +512,9 @@ export class GitDiffViewController {
485
512
  }
486
513
 
487
514
  renderEmpty(message) {
515
+ if (!this.isActive) {
516
+ return;
517
+ }
488
518
  this.page?.classList.remove('hidden');
489
519
  if (this.content) {
490
520
  this.content.innerHTML = `<div class="diff-empty-state">${escapeHtml(message)}</div>`;
@@ -1053,37 +1083,21 @@ export class GitDiffViewController {
1053
1083
  try {
1054
1084
  let detail;
1055
1085
  if (this.source === 'commit') {
1056
- const query = new URLSearchParams();
1057
- query.set('hash', this.commitHash || '');
1058
- query.set('path', filePath);
1059
- if (forceFullPatch) {
1060
- query.set('allowLargePatch', 'true');
1061
- }
1062
-
1063
- const response = await fetch(resolveApiUrl(`/git/commit?${query.toString()}`));
1064
- const data = await response.json();
1065
- if (!response.ok) {
1066
- throw new Error(data.error || 'Failed to load commit file diff');
1067
- }
1068
-
1086
+ const data = await this.gitApiClient.readCommit({
1087
+ allowLargePatch: forceFullPatch,
1088
+ hash: this.commitHash || '',
1089
+ path: filePath,
1090
+ });
1069
1091
  detail = {
1070
1092
  ...file,
1071
1093
  ...(data.files?.[0] ?? {}),
1072
1094
  };
1073
1095
  } else {
1074
- const query = new URLSearchParams();
1075
- query.set('scope', this.requestScope);
1076
- query.set('path', filePath);
1077
- if (forceFullPatch) {
1078
- query.set('allowLargePatch', 'true');
1079
- }
1080
-
1081
- const response = await fetch(resolveApiUrl(`/git/diff?${query.toString()}`));
1082
- const data = await response.json();
1083
- if (!response.ok) {
1084
- throw new Error(data.error || 'Failed to load file diff');
1085
- }
1086
-
1096
+ const data = await this.gitApiClient.readDiff({
1097
+ allowLargePatch: forceFullPatch,
1098
+ path: filePath,
1099
+ scope: this.requestScope,
1100
+ });
1087
1101
  detail = {
1088
1102
  ...file,
1089
1103
  ...(data.files?.[0] ?? {}),
@@ -1187,6 +1201,9 @@ export class GitDiffViewController {
1187
1201
  }
1188
1202
 
1189
1203
  render() {
1204
+ if (!this.isActive) {
1205
+ return;
1206
+ }
1190
1207
  this.page?.classList.remove('hidden');
1191
1208
 
1192
1209
  const files = this.data?.files ?? [];
@@ -1,5 +1,4 @@
1
1
  import { escapeHtml } from '../domain/vault-utils.js';
2
- import { resolveApiUrl } from '../domain/runtime-paths.js';
3
2
  import { buttonClassNames } from './components/ui/button.js';
4
3
  import { segmentedButtonClassNames, segmentedControlClassNames } from './components/ui/segmented-control.js';
5
4
 
@@ -94,6 +93,7 @@ function renderHistoryRowTitle(commit = {}) {
94
93
  export class GitPanelController {
95
94
  constructor({
96
95
  enabled = true,
96
+ gitApiClient = null,
97
97
  onCommitStaged = () => {},
98
98
  onOpenPullBackup = () => {},
99
99
  onPullBranch = () => {},
@@ -109,6 +109,7 @@ export class GitPanelController {
109
109
  toastController = null,
110
110
  } = {}) {
111
111
  this.enabled = enabled;
112
+ this.gitApiClient = gitApiClient;
112
113
  this.onCommitStaged = onCommitStaged;
113
114
  this.onOpenPullBackup = onOpenPullBackup;
114
115
  this.onPullBranch = onPullBranch;
@@ -404,21 +405,12 @@ export class GitPanelController {
404
405
  }
405
406
 
406
407
  try {
407
- const response = await fetch(resolveApiUrl(`/git/status${force ? '?force=true' : ''}`));
408
- const data = await response.json();
409
- if (!response.ok) {
410
- throw new Error(data.error || 'Failed to load git status');
411
- }
412
-
408
+ const data = await this.gitApiClient.readStatus({ force });
413
409
  this.status = data;
414
410
  this.pullBackups = [];
415
411
  if (data.isGitRepo) {
416
412
  try {
417
- const backupResponse = await fetch(resolveApiUrl('/git/pull-backups'));
418
- const backupData = await backupResponse.json();
419
- if (!backupResponse.ok) {
420
- throw new Error(backupData.error || 'Failed to load pull backups');
421
- }
413
+ const backupData = await this.gitApiClient.readPullBackups();
422
414
  this.pullBackups = Array.isArray(backupData.backups) ? backupData.backups : [];
423
415
  } catch (backupError) {
424
416
  console.error('[git-panel] Failed to load pull backups:', backupError);
@@ -501,15 +493,10 @@ export class GitPanelController {
501
493
  this.render();
502
494
 
503
495
  try {
504
- const query = new URLSearchParams();
505
- query.set('limit', String(HISTORY_PAGE_SIZE));
506
- query.set('offset', '0');
507
- const response = await fetch(resolveApiUrl(`/git/history?${query.toString()}`));
508
- const data = await response.json();
509
- if (!response.ok) {
510
- throw new Error(data.error || 'Failed to load git history');
511
- }
512
-
496
+ const data = await this.gitApiClient.readHistory({
497
+ limit: HISTORY_PAGE_SIZE,
498
+ offset: 0,
499
+ });
513
500
  this.history = {
514
501
  commits: Array.isArray(data.commits) ? data.commits : [],
515
502
  error: '',
@@ -553,15 +540,10 @@ export class GitPanelController {
553
540
  this.render();
554
541
 
555
542
  try {
556
- const query = new URLSearchParams();
557
- query.set('limit', String(HISTORY_PAGE_SIZE));
558
- query.set('offset', String(this.history.offset));
559
- const response = await fetch(resolveApiUrl(`/git/history?${query.toString()}`));
560
- const data = await response.json();
561
- if (!response.ok) {
562
- throw new Error(data.error || 'Failed to load git history');
563
- }
564
-
543
+ const data = await this.gitApiClient.readHistory({
544
+ limit: HISTORY_PAGE_SIZE,
545
+ offset: this.history.offset,
546
+ });
565
547
  const nextCommits = Array.isArray(data.commits) ? data.commits : [];
566
548
  this.history = {
567
549
  commits: [...this.history.commits, ...nextCommits],
@@ -2,17 +2,24 @@ html[data-surface='export-document'] {
2
2
  height: auto;
3
3
  min-height: 100%;
4
4
  overflow-x: hidden;
5
- overflow-y: auto;
5
+ overflow-y: hidden;
6
6
  overscroll-behavior: auto;
7
+ scrollbar-width: none;
7
8
  }
8
9
 
9
10
  body[data-surface='export-document'] {
10
11
  height: auto;
11
12
  overflow-x: hidden;
12
- overflow-y: auto;
13
+ overflow-y: hidden;
13
14
  overscroll-behavior: auto;
14
15
  background: var(--color-bg);
15
16
  color: var(--color-text);
17
+ scrollbar-width: none;
18
+ }
19
+
20
+ html[data-surface='export-document']::-webkit-scrollbar,
21
+ body[data-surface='export-document']::-webkit-scrollbar {
22
+ display: none;
16
23
  }
17
24
 
18
25
  .export-page-shell {
@@ -78,10 +85,22 @@ body[data-surface='export-document'] {
78
85
  overflow: visible;
79
86
  }
80
87
 
88
+ .export-content .bases-table-shell,
89
+ .export-content pre {
90
+ overflow: visible;
91
+ }
92
+
81
93
  .export-content table {
82
94
  white-space: normal;
83
95
  }
84
96
 
97
+ .export-content pre code {
98
+ width: auto;
99
+ white-space: pre-wrap;
100
+ word-break: break-word;
101
+ overflow-wrap: anywhere;
102
+ }
103
+
85
104
  .export-diagram {
86
105
  display: flex;
87
106
  justify-content: center;
@@ -197,8 +216,18 @@ body[data-surface='export-document'] {
197
216
  }
198
217
 
199
218
  @media print {
219
+ html[data-surface='export-document'],
200
220
  body[data-surface='export-document'] {
221
+ height: auto;
222
+ overflow: hidden !important;
201
223
  background: var(--color-surface);
224
+ scrollbar-width: none;
225
+ }
226
+
227
+ html[data-surface='export-document']::-webkit-scrollbar,
228
+ body[data-surface='export-document']::-webkit-scrollbar,
229
+ .export-content *::-webkit-scrollbar {
230
+ display: none;
202
231
  }
203
232
 
204
233
  .export-page-shell {
@@ -218,6 +247,20 @@ body[data-surface='export-document'] {
218
247
  border: 0;
219
248
  border-radius: 0;
220
249
  box-shadow: none;
250
+ overflow: visible;
251
+ }
252
+
253
+ .export-content pre,
254
+ .export-content .table-wrapper,
255
+ .export-content .bases-table-shell {
256
+ overflow: visible !important;
257
+ }
258
+
259
+ .export-content pre code {
260
+ width: auto;
261
+ white-space: pre-wrap;
262
+ word-break: break-word;
263
+ overflow-wrap: anywhere;
221
264
  }
222
265
 
223
266
  .export-content h1,