@bendyline/docblocks-react 2.2.1 → 2.2.2

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.
package/dist/index.js CHANGED
@@ -13,20 +13,24 @@ import {
13
13
  import {
14
14
  AccentColorSettings,
15
15
  DB_CHROME_COLORS,
16
+ DEFAULT_WRITE_CANVAS_FONT_SCHEME,
16
17
  DEFAULT_WRITE_CANVAS_PREFERENCES,
17
18
  SettingsDialog,
18
19
  ThemeSettings,
20
+ WRITE_CANVAS_FONT_SCHEMES,
19
21
  WriteCanvasSettingsControls,
20
22
  loadAccentColor,
21
23
  loadThemePreference,
22
24
  loadWriteCanvasPreferences,
25
+ resolveWriteCanvasFonts,
23
26
  saveAccentColor,
24
27
  saveThemePreference,
25
28
  saveWriteCanvasPreferences
26
- } from "./chunk-3QSZY74C.js";
29
+ } from "./chunk-VRRL6VTD.js";
27
30
  import {
28
- pickEmptyDocumentPrompt
29
- } from "./chunk-BPGEBGAN.js";
31
+ pickEmptyDocumentPrompt,
32
+ useResponsivePreviewViewportPreset
33
+ } from "./chunk-JDJVRDOP.js";
30
34
  import {
31
35
  ExportDialog
32
36
  } from "./chunk-MHTQORMH.js";
@@ -34,6 +38,9 @@ import {
34
38
  buildExportFilename,
35
39
  runExport
36
40
  } from "./chunk-GHQ4X7KM.js";
41
+ import {
42
+ useMenuKeyboard
43
+ } from "./chunk-MRUK56JS.js";
37
44
  import "./chunk-YBEYTVU2.js";
38
45
  import {
39
46
  Dialog
@@ -44,7 +51,7 @@ import {
44
51
  } from "./chunk-EBWYGTN7.js";
45
52
 
46
53
  // src/FileExplorer/FileExplorer.tsx
47
- import { useCallback as useCallback3, useEffect as useEffect3, useMemo, useRef as useRef3, useState as useState3 } from "react";
54
+ import { useCallback as useCallback4, useEffect as useEffect4, useMemo, useRef as useRef4, useState as useState4 } from "react";
48
55
  import {
49
56
  isFileSystemMoveStateError,
50
57
  parseWorkspacePath as parseWorkspacePath2
@@ -54,10 +61,12 @@ import {
54
61
  import { useState, useCallback, useEffect, useRef } from "react";
55
62
  import {
56
63
  FsError,
64
+ fsErrorFromUnknown,
57
65
  getFileSystemProviderV2,
58
66
  moveFileSystemEntry,
59
67
  parseWorkspacePath
60
68
  } from "@bendyline/docblocks/filesystem";
69
+ var DIRECTORY_NOT_FOUND_RETRY_DELAYS_MS = Object.freeze([40, 120]);
61
70
  function normalisePath(path) {
62
71
  return parseWorkspacePath(path);
63
72
  }
@@ -83,10 +92,44 @@ function hasEquivalentPath(paths, path) {
83
92
  return equivalentPathKeys(path).some((candidate) => paths.has(candidate));
84
93
  }
85
94
  async function readProviderDirectory(provider, path) {
86
- const providerV2 = getFileSystemProviderV2(provider);
87
- if (!providerV2) return provider.readDirectory(path);
88
- const entries = await providerV2.readDirectory(parseWorkspacePath(path));
89
- return entries.map((entry) => ({ kind: entry.kind, name: entry.name, path: entry.path }));
95
+ const canonical = parseWorkspacePath(path);
96
+ const readOnce = async () => {
97
+ const providerV2 = getFileSystemProviderV2(provider);
98
+ if (!providerV2) return provider.readDirectory(path);
99
+ const entries = await providerV2.readDirectory(canonical);
100
+ return entries.map((entry) => ({ kind: entry.kind, name: entry.name, path: entry.path }));
101
+ };
102
+ for (let attempt = 0; ; attempt += 1) {
103
+ try {
104
+ return await readOnce();
105
+ } catch (caught) {
106
+ const error = directoryReadError(caught, canonical);
107
+ const retryDelay = DIRECTORY_NOT_FOUND_RETRY_DELAYS_MS[attempt];
108
+ if (error.code !== "not-found" || retryDelay === void 0) throw error;
109
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
110
+ }
111
+ }
112
+ }
113
+ function directoryReadError(caught, requestedPath) {
114
+ const error = fsErrorFromUnknown(caught, { operation: "list", path: requestedPath });
115
+ if (error.path !== null) return error;
116
+ return new FsError(error.code, error.message, {
117
+ operation: error.operation ?? "list",
118
+ path: requestedPath,
119
+ destinationPath: error.destinationPath ?? void 0,
120
+ retryable: error.retryable
121
+ });
122
+ }
123
+ function readIssue(caught, directoryPath) {
124
+ const canonical = parseWorkspacePath(directoryPath);
125
+ const error = directoryReadError(caught, canonical);
126
+ return Object.freeze({
127
+ directoryPath: canonical,
128
+ path: error.path ?? canonical,
129
+ code: error.code,
130
+ message: error.message,
131
+ retryable: error.retryable || error.code === "not-found"
132
+ });
90
133
  }
91
134
  function useFileTree(provider) {
92
135
  const [entries, setEntries] = useState([]);
@@ -94,12 +137,15 @@ function useFileTree(provider) {
94
137
  const [selectedPath, setSelectedPath] = useState(null);
95
138
  const [selectedKind, setSelectedKind] = useState(null);
96
139
  const [loading, setLoading] = useState(false);
97
- const [error, setError] = useState(null);
140
+ const [rootIssue, setRootIssue] = useState(null);
141
+ const [childIssues, setChildIssues] = useState(/* @__PURE__ */ new Map());
98
142
  const [childEntries, setChildEntries] = useState(/* @__PURE__ */ new Map());
99
143
  const providerRef = useRef(provider);
100
144
  providerRef.current = provider;
101
- const reportError = useCallback((caught) => {
102
- setError(caught instanceof Error ? caught.message : "Unable to read this workspace.");
145
+ const directoryRequestSequenceRef = useRef(0);
146
+ const directoryRequestsRef = useRef(/* @__PURE__ */ new Map());
147
+ const reportRootIssue = useCallback((caught) => {
148
+ setRootIssue(readIssue(caught, ""));
103
149
  }, []);
104
150
  const loadRoot = useCallback(async () => {
105
151
  const sourceProvider = providerRef.current;
@@ -108,43 +154,60 @@ function useFileTree(provider) {
108
154
  setLoading(false);
109
155
  return;
110
156
  }
157
+ const requestId = ++directoryRequestSequenceRef.current;
158
+ directoryRequestsRef.current.set("", requestId);
159
+ const isCurrent = () => providerRef.current === sourceProvider && directoryRequestsRef.current.get("") === requestId;
111
160
  setLoading(true);
112
- setError(null);
113
161
  try {
114
162
  const root = await readProviderDirectory(sourceProvider, "");
115
- if (providerRef.current !== sourceProvider) return;
163
+ if (!isCurrent()) return;
116
164
  setEntries(root);
165
+ setRootIssue(null);
117
166
  } catch (caught) {
118
- if (providerRef.current === sourceProvider) reportError(caught);
167
+ if (isCurrent()) setRootIssue(readIssue(caught, ""));
119
168
  } finally {
120
- if (providerRef.current === sourceProvider) setLoading(false);
169
+ if (isCurrent()) setLoading(false);
121
170
  }
122
- }, [reportError]);
123
- const loadChildren = useCallback(
124
- async (dirPath) => {
125
- const sourceProvider = providerRef.current;
126
- if (!sourceProvider) return;
127
- try {
128
- const children = await readProviderDirectory(sourceProvider, dirPath);
129
- if (providerRef.current !== sourceProvider) return;
130
- setChildEntries((prev) => {
131
- const next = new Map(prev);
132
- next.set(dirPath, children);
133
- return next;
134
- });
135
- } catch (caught) {
136
- if (providerRef.current === sourceProvider) reportError(caught);
137
- }
138
- },
139
- [reportError]
140
- );
171
+ }, []);
172
+ const loadChildren = useCallback(async (dirPath) => {
173
+ const sourceProvider = providerRef.current;
174
+ if (!sourceProvider) return;
175
+ const canonical = normalisePath(dirPath);
176
+ const requestId = ++directoryRequestSequenceRef.current;
177
+ directoryRequestsRef.current.set(canonical, requestId);
178
+ const isCurrent = () => providerRef.current === sourceProvider && directoryRequestsRef.current.get(canonical) === requestId;
179
+ try {
180
+ const children = await readProviderDirectory(sourceProvider, dirPath);
181
+ if (!isCurrent()) return;
182
+ setChildEntries((prev) => {
183
+ const next = new Map(prev);
184
+ next.set(canonical, children);
185
+ return next;
186
+ });
187
+ setChildIssues((prev) => {
188
+ if (!prev.has(canonical)) return prev;
189
+ const next = new Map(prev);
190
+ next.delete(canonical);
191
+ return next;
192
+ });
193
+ } catch (caught) {
194
+ if (!isCurrent()) return;
195
+ setChildIssues((prev) => {
196
+ const next = new Map(prev);
197
+ next.set(canonical, readIssue(caught, dirPath));
198
+ return next;
199
+ });
200
+ }
201
+ }, []);
141
202
  useEffect(() => {
142
203
  setEntries([]);
143
204
  setExpanded(/* @__PURE__ */ new Set());
144
205
  setChildEntries(/* @__PURE__ */ new Map());
206
+ setChildIssues(/* @__PURE__ */ new Map());
145
207
  setSelectedPath(null);
146
208
  setSelectedKind(null);
147
- setError(null);
209
+ setRootIssue(null);
210
+ directoryRequestsRef.current.clear();
148
211
  void loadRoot();
149
212
  }, [provider, loadRoot]);
150
213
  const toggleExpand = useCallback(
@@ -191,6 +254,13 @@ function useFileTree(provider) {
191
254
  await loadChildren(dirPath);
192
255
  }
193
256
  }, [loadRoot, loadChildren, expanded]);
257
+ const retryDirectory = useCallback(
258
+ async (path) => {
259
+ if (normalisePath(path) === "") await loadRoot();
260
+ else await loadChildren(path);
261
+ },
262
+ [loadRoot, loadChildren]
263
+ );
194
264
  const refreshRef = useRef(refresh);
195
265
  refreshRef.current = refresh;
196
266
  useEffect(() => {
@@ -214,7 +284,7 @@ function useFileTree(provider) {
214
284
  await refreshRef.current();
215
285
  } while (refreshAgain && !disposed);
216
286
  } catch (caught) {
217
- reportError(caught);
287
+ reportRootIssue(caught);
218
288
  } finally {
219
289
  refreshing = false;
220
290
  if (refreshAgain && !disposed) requestRefresh();
@@ -229,23 +299,25 @@ function useFileTree(provider) {
229
299
  {
230
300
  onError: (caught) => {
231
301
  if (disposed) return;
232
- reportError(caught);
302
+ reportRootIssue(caught);
233
303
  requestRefresh();
234
304
  }
235
305
  }
236
306
  );
237
307
  void subscription.ready.catch((caught) => {
238
- if (!disposed) reportError(caught);
308
+ if (!disposed) reportRootIssue(caught);
239
309
  });
240
310
  return () => {
241
311
  disposed = true;
242
312
  void subscription.dispose();
243
313
  };
244
- }, [provider, reportError]);
314
+ }, [provider, reportRootIssue]);
245
315
  useEffect(() => {
246
316
  if (!provider) return;
317
+ const providerV2 = getFileSystemProviderV2(provider);
318
+ if (providerV2?.capabilities.watch) return;
247
319
  const refreshOnFocus = () => {
248
- void refreshRef.current().catch(reportError);
320
+ void refreshRef.current().catch(reportRootIssue);
249
321
  };
250
322
  const refreshOnVisibility = () => {
251
323
  if (document.visibilityState === "visible") refreshOnFocus();
@@ -256,7 +328,7 @@ function useFileTree(provider) {
256
328
  window.removeEventListener("focus", refreshOnFocus);
257
329
  document.removeEventListener("visibilitychange", refreshOnVisibility);
258
330
  };
259
- }, [provider, reportError]);
331
+ }, [provider, reportRootIssue]);
260
332
  const createFile = useCallback(
261
333
  async (path, content = "") => {
262
334
  if (!providerRef.current) return;
@@ -331,6 +403,7 @@ function useFileTree(provider) {
331
403
  );
332
404
  setExpanded(nextExpanded);
333
405
  setChildEntries(/* @__PURE__ */ new Map());
406
+ setChildIssues(/* @__PURE__ */ new Map());
334
407
  await loadRoot();
335
408
  for (const dirPath of nextExpanded) {
336
409
  await loadChildren(dirPath);
@@ -344,7 +417,10 @@ function useFileTree(provider) {
344
417
  selectedPath,
345
418
  selectedKind,
346
419
  loading,
347
- error,
420
+ error: rootIssue?.message ?? null,
421
+ rootIssue,
422
+ childIssues,
423
+ childEntries,
348
424
  toggleExpand,
349
425
  select,
350
426
  reveal,
@@ -353,8 +429,7 @@ function useFileTree(provider) {
353
429
  deleteEntry,
354
430
  renameEntry,
355
431
  refresh,
356
- // Expose child entries for rendering
357
- ...{ childEntries }
432
+ retryDirectory
358
433
  };
359
434
  }
360
435
 
@@ -379,6 +454,9 @@ function FolderIcon() {
379
454
  function MoreIcon() {
380
455
  return /* @__PURE__ */ jsx(FontAwesomeIcon, { icon: "fa-solid fa-ellipsis" });
381
456
  }
457
+ function PinIcon() {
458
+ return /* @__PURE__ */ jsx(FontAwesomeIcon, { icon: "fa-solid fa-thumbtack" });
459
+ }
382
460
  function WorkspaceIcon() {
383
461
  return /* @__PURE__ */ jsx(FontAwesomeIcon, { icon: "fa-solid fa-gear" });
384
462
  }
@@ -425,6 +503,8 @@ function FileTreeNode({
425
503
  confirmDelete = defaultConfirmDelete,
426
504
  onDelete,
427
505
  onRename,
506
+ pinned = false,
507
+ onTogglePin,
428
508
  draggable = false,
429
509
  dragging = false,
430
510
  dropTarget = false,
@@ -432,7 +512,8 @@ function FileTreeNode({
432
512
  onDragEnd,
433
513
  onDragOverEntry,
434
514
  onDropEntry,
435
- renderChildren
515
+ renderChildren,
516
+ childError
436
517
  }) {
437
518
  const [renaming, setRenaming] = useState2(false);
438
519
  const [renameValue, setRenameValue] = useState2(entry.name);
@@ -445,8 +526,10 @@ function FileTreeNode({
445
526
  const rowRef = useRef2(null);
446
527
  const keyboardMenuRef = useRef2(false);
447
528
  const renameSubmittedRef = useRef2(false);
529
+ const returnFocusAfterRenameRef = useRef2(false);
448
530
  const isDir = entry.kind === "directory";
449
- const icon = isDir ? expanded ? "\u25BE" : "\u25B8" : "\xA0\xA0";
531
+ const icon = expanded ? "\u25BE" : "\u25B8";
532
+ const nestedFileInset = !isDir && depth > 0 ? 8 : 0;
450
533
  const handleClick = useCallback2(() => {
451
534
  if (isDir) {
452
535
  onToggle(entry.path);
@@ -537,22 +620,27 @@ function FileTreeNode({
537
620
  }, [entry.name, closeMenu]);
538
621
  const handleRenameCancel = useCallback2(() => {
539
622
  renameSubmittedRef.current = true;
623
+ returnFocusAfterRenameRef.current = true;
540
624
  setRenaming(false);
541
625
  }, []);
542
- const handleRenameSubmit = useCallback2(async () => {
543
- if (renameSubmittedRef.current) return;
544
- renameSubmittedRef.current = true;
545
- if (renameValue && renameValue !== entry.name) {
546
- const parentPath2 = entry.path.includes("/") ? entry.path.slice(0, entry.path.lastIndexOf("/")) : "";
547
- const newPath = parentPath2 ? `${parentPath2}/${renameValue}` : renameValue;
548
- try {
549
- await onRename(entry.path, newPath, entry.kind);
550
- } catch (caught) {
551
- setActionError(caught instanceof Error ? caught.message : "Unable to rename this entry.");
626
+ const handleRenameSubmit = useCallback2(
627
+ async (returnFocus = false) => {
628
+ if (renameSubmittedRef.current) return;
629
+ renameSubmittedRef.current = true;
630
+ returnFocusAfterRenameRef.current = returnFocus;
631
+ setRenaming(false);
632
+ if (renameValue && renameValue !== entry.name) {
633
+ const parentPath2 = entry.path.includes("/") ? entry.path.slice(0, entry.path.lastIndexOf("/")) : "";
634
+ const newPath = parentPath2 ? `${parentPath2}/${renameValue}` : renameValue;
635
+ try {
636
+ await onRename(entry.path, newPath, entry.kind);
637
+ } catch (caught) {
638
+ setActionError(caught instanceof Error ? caught.message : "Unable to rename this entry.");
639
+ }
552
640
  }
553
- }
554
- setRenaming(false);
555
- }, [renameValue, entry.name, entry.path, entry.kind, onRename]);
641
+ },
642
+ [renameValue, entry.name, entry.path, entry.kind, onRename]
643
+ );
556
644
  const handleDeleteClick = useCallback2(async () => {
557
645
  closeMenu(false);
558
646
  setActionError(null);
@@ -566,6 +654,17 @@ function FileTreeNode({
566
654
  setActionError(caught instanceof Error ? caught.message : "Unable to delete this entry.");
567
655
  }
568
656
  }, [entry.name, entry.path, entry.kind, confirmDelete, onDelete, closeMenu]);
657
+ const handleTogglePin = useCallback2(async () => {
658
+ closeMenu(false);
659
+ setActionError(null);
660
+ try {
661
+ await onTogglePin?.(entry.path);
662
+ } catch (caught) {
663
+ setActionError(
664
+ caught instanceof Error ? caught.message : `Unable to ${pinned ? "unpin" : "pin"} this file.`
665
+ );
666
+ }
667
+ }, [closeMenu, entry.path, onTogglePin, pinned]);
569
668
  useEffect2(() => {
570
669
  if (!showContext) return;
571
670
  function handleClose(e) {
@@ -591,6 +690,11 @@ function FileTreeNode({
591
690
  inputRef.current.select();
592
691
  }
593
692
  }, [renaming]);
693
+ useEffect2(() => {
694
+ if (renaming || !returnFocusAfterRenameRef.current) return;
695
+ returnFocusAfterRenameRef.current = false;
696
+ rowRef.current?.focus({ preventScroll: true });
697
+ }, [renaming]);
594
698
  useEffect2(() => {
595
699
  if (!showContext || !keyboardMenuRef.current) return;
596
700
  focusMenuItem(0);
@@ -622,7 +726,7 @@ function FileTreeNode({
622
726
  {
623
727
  ref: rowRef,
624
728
  className: `db-tree-row ${selected ? "db-tree-row--selected" : ""} ${dragging ? "db-tree-row--dragging" : ""} ${dropTarget ? "db-tree-row--drop-target" : ""}`,
625
- style: { paddingLeft: depth * 16 + 4 },
729
+ style: { paddingLeft: depth * 12 + 4 + nestedFileInset },
626
730
  onClick: handleClick,
627
731
  onContextMenu: handleContextMenu,
628
732
  draggable: draggable && !renaming,
@@ -665,7 +769,7 @@ function FileTreeNode({
665
769
  }
666
770
  },
667
771
  children: [
668
- /* @__PURE__ */ jsx2("span", { className: "db-tree-icon", children: icon }),
772
+ (isDir || depth === 0) && /* @__PURE__ */ jsx2("span", { className: "db-tree-icon", children: isDir ? icon : null }),
669
773
  renaming ? /* @__PURE__ */ jsx2(
670
774
  "input",
671
775
  {
@@ -678,7 +782,7 @@ function FileTreeNode({
678
782
  if (e.key !== "Enter" && e.key !== "Escape") return;
679
783
  e.preventDefault();
680
784
  e.stopPropagation();
681
- if (e.key === "Enter") void handleRenameSubmit();
785
+ if (e.key === "Enter") void handleRenameSubmit(true);
682
786
  else handleRenameCancel();
683
787
  },
684
788
  onClick: (e) => e.stopPropagation()
@@ -697,6 +801,7 @@ function FileTreeNode({
697
801
  "aria-haspopup": "menu",
698
802
  "aria-expanded": showContext,
699
803
  "aria-keyshortcuts": "Shift+F10",
804
+ title: "More actions (Shift+F10)",
700
805
  tabIndex: -1,
701
806
  children: /* @__PURE__ */ jsx2(MoreIcon, {})
702
807
  }
@@ -728,6 +833,17 @@ function FileTreeNode({
728
833
  children: "Rename"
729
834
  }
730
835
  ),
836
+ !isDir && onTogglePin && /* @__PURE__ */ jsx2(
837
+ "button",
838
+ {
839
+ type: "button",
840
+ role: "menuitem",
841
+ tabIndex: -1,
842
+ className: "db-tree-context-item",
843
+ onClick: () => void handleTogglePin(),
844
+ children: pinned ? "Unpin" : "Pin"
845
+ }
846
+ ),
731
847
  gitActions && (gitActions.viewChanges || gitActions.fileHistory) && /* @__PURE__ */ jsxs2(Fragment, { children: [
732
848
  /* @__PURE__ */ jsx2("div", { className: "db-tree-context-divider", role: "separator" }),
733
849
  gitActions.viewChanges && /* @__PURE__ */ jsx2(
@@ -790,7 +906,8 @@ function FileTreeNode({
790
906
  ),
791
907
  document.body
792
908
  ),
793
- isDir && expanded && renderChildren && /* @__PURE__ */ jsx2("div", { className: "db-tree-children", role: "group", children: renderChildren(entry.path) })
909
+ isDir && expanded && renderChildren && /* @__PURE__ */ jsx2("div", { className: "db-tree-children", role: "group", children: renderChildren(entry.path) }),
910
+ isDir && expanded && childError
794
911
  ] });
795
912
  }
796
913
 
@@ -854,8 +971,409 @@ function treeKeyAction(rows, activePath, key) {
854
971
  }
855
972
  }
856
973
 
857
- // src/FileExplorer/FileExplorer.tsx
974
+ // src/FileExplorer/PinnedDocuments.tsx
975
+ import { useCallback as useCallback3, useEffect as useEffect3, useId, useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState3 } from "react";
976
+ import { createPortal as createPortal2 } from "react-dom";
977
+
978
+ // src/DocBlocksShell/pinned-documents.ts
979
+ import { tryParseWorkspacePath } from "@bendyline/docblocks/filesystem";
980
+ var PINNED_DOCUMENTS_STORAGE_KEY = "docblocks-pinned-documents-v1";
981
+ var MAX_PINNED_DOCUMENTS = 100;
982
+ var MAX_WORKSPACE_ID_LENGTH = 256;
983
+ var MAX_WORKSPACE_NAME_LENGTH = 256;
984
+ var MAX_DOCUMENT_PATH_LENGTH = 4096;
985
+ function resolvePinnedDocumentStorage() {
986
+ try {
987
+ const candidate = Reflect.get(globalThis, "localStorage");
988
+ if (typeof candidate !== "object" || candidate === null || typeof Reflect.get(candidate, "getItem") !== "function" || typeof Reflect.get(candidate, "setItem") !== "function") {
989
+ return null;
990
+ }
991
+ return candidate;
992
+ } catch {
993
+ return null;
994
+ }
995
+ }
996
+ function pinnedDocumentKey(document2) {
997
+ const path = tryParseWorkspacePath(document2.path);
998
+ return `${document2.workspaceId}\0${path ?? document2.path}`;
999
+ }
1000
+ function pinnedDocumentName(document2) {
1001
+ const canonical = tryParseWorkspacePath(document2.path);
1002
+ const path = canonical ?? document2.path;
1003
+ return path.slice(path.lastIndexOf("/") + 1);
1004
+ }
1005
+ function pinnedDocumentDisplayPath(document2) {
1006
+ return `${document2.workspaceName}/${document2.path}`;
1007
+ }
1008
+ function missingPinnedDocumentMessage(document2) {
1009
+ return `${pinnedDocumentName(document2)} is no longer at ${pinnedDocumentDisplayPath(document2)}. Unpin this file?`;
1010
+ }
1011
+ function parsePinnedDocument(value) {
1012
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
1013
+ const record = value;
1014
+ const keys = Object.keys(record);
1015
+ if (keys.some((key) => key !== "workspaceId" && key !== "workspaceName" && key !== "path") || typeof record.workspaceId !== "string" || record.workspaceId.length === 0 || record.workspaceId.length > MAX_WORKSPACE_ID_LENGTH || typeof record.workspaceName !== "string" || record.workspaceName.length === 0 || record.workspaceName.length > MAX_WORKSPACE_NAME_LENGTH || typeof record.path !== "string" || record.path.length === 0 || record.path.length > MAX_DOCUMENT_PATH_LENGTH) {
1016
+ return null;
1017
+ }
1018
+ const path = tryParseWorkspacePath(record.path);
1019
+ if (!path) return null;
1020
+ return {
1021
+ workspaceId: record.workspaceId,
1022
+ workspaceName: record.workspaceName,
1023
+ path
1024
+ };
1025
+ }
1026
+ function parsePinnedDocuments(value) {
1027
+ if (!Array.isArray(value)) return [];
1028
+ const parsed = [];
1029
+ const seen = /* @__PURE__ */ new Set();
1030
+ for (const valueItem of value.slice(0, MAX_PINNED_DOCUMENTS)) {
1031
+ const document2 = parsePinnedDocument(valueItem);
1032
+ if (!document2) continue;
1033
+ const key = pinnedDocumentKey(document2);
1034
+ if (seen.has(key)) continue;
1035
+ seen.add(key);
1036
+ parsed.push(document2);
1037
+ }
1038
+ return parsed;
1039
+ }
1040
+ function loadPinnedDocuments(storage = resolvePinnedDocumentStorage()) {
1041
+ if (!storage) return [];
1042
+ try {
1043
+ const raw = storage.getItem(PINNED_DOCUMENTS_STORAGE_KEY);
1044
+ return raw === null ? [] : parsePinnedDocuments(JSON.parse(raw));
1045
+ } catch {
1046
+ return [];
1047
+ }
1048
+ }
1049
+ function savePinnedDocuments(documents, storage = resolvePinnedDocumentStorage()) {
1050
+ if (!storage) return;
1051
+ try {
1052
+ storage.setItem(PINNED_DOCUMENTS_STORAGE_KEY, JSON.stringify(parsePinnedDocuments(documents)));
1053
+ } catch {
1054
+ }
1055
+ }
1056
+ function togglePinnedDocument(documents, candidate) {
1057
+ const parsed = parsePinnedDocument(candidate);
1058
+ if (!parsed) return [...documents];
1059
+ const key = pinnedDocumentKey(parsed);
1060
+ if (documents.some((document2) => pinnedDocumentKey(document2) === key)) {
1061
+ return documents.filter((document2) => pinnedDocumentKey(document2) !== key);
1062
+ }
1063
+ return [parsed, ...documents].slice(0, MAX_PINNED_DOCUMENTS);
1064
+ }
1065
+ function removePinnedDocument(documents, target) {
1066
+ const key = pinnedDocumentKey(target);
1067
+ return documents.filter((document2) => pinnedDocumentKey(document2) !== key);
1068
+ }
1069
+ function relocatePinnedDocuments(documents, workspaceId, oldPath, newPath) {
1070
+ const oldCanonical = tryParseWorkspacePath(oldPath);
1071
+ const newCanonical = tryParseWorkspacePath(newPath);
1072
+ if (!oldCanonical || !newCanonical) return [...documents];
1073
+ return parsePinnedDocuments(
1074
+ documents.map((document2) => {
1075
+ if (document2.workspaceId !== workspaceId) return document2;
1076
+ if (document2.path === oldCanonical) return { ...document2, path: newCanonical };
1077
+ if (!document2.path.startsWith(`${oldCanonical}/`)) return document2;
1078
+ return {
1079
+ ...document2,
1080
+ path: `${newCanonical}${document2.path.slice(oldCanonical.length)}`
1081
+ };
1082
+ })
1083
+ );
1084
+ }
1085
+ function renamePinnedDocumentWorkspace(documents, workspaceId, workspaceName) {
1086
+ return documents.map(
1087
+ (document2) => document2.workspaceId === workspaceId ? { ...document2, workspaceName } : document2
1088
+ );
1089
+ }
1090
+ function movePinnedDocumentsToWorkspace(documents, sourceWorkspaceId, destination) {
1091
+ return parsePinnedDocuments(
1092
+ documents.map(
1093
+ (document2) => document2.workspaceId === sourceWorkspaceId ? { ...document2, ...destination } : document2
1094
+ )
1095
+ );
1096
+ }
1097
+
1098
+ // src/FileExplorer/PinnedDocuments.tsx
858
1099
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1100
+ function displayName(document2) {
1101
+ const name = pinnedDocumentName(document2);
1102
+ return name.endsWith(".md") ? name.slice(0, -3) : name;
1103
+ }
1104
+ function PinnedDocumentRow({
1105
+ document: pinnedDocument,
1106
+ selected,
1107
+ onSelect,
1108
+ onUnpin,
1109
+ onRename,
1110
+ onDelete
1111
+ }) {
1112
+ const [showContext, setShowContext] = useState3(false);
1113
+ const [contextPos, setContextPos] = useState3({ x: 0, y: 0 });
1114
+ const [actionError, setActionError] = useState3(null);
1115
+ const contextRef = useRef3(null);
1116
+ const selectRef = useRef3(null);
1117
+ const keyboardMenuRef = useRef3(false);
1118
+ const missing = pinnedDocument.availability === "missing";
1119
+ const name = pinnedDocumentName(pinnedDocument);
1120
+ const displayPath = pinnedDocumentDisplayPath(pinnedDocument);
1121
+ const hasActions = Boolean(onUnpin || onRename || onDelete);
1122
+ const closeMenu = useCallback3((returnFocus) => {
1123
+ setShowContext(false);
1124
+ keyboardMenuRef.current = false;
1125
+ if (returnFocus) selectRef.current?.focus({ preventScroll: true });
1126
+ }, []);
1127
+ const openMenuAt = useCallback3((x, y, keyboard) => {
1128
+ keyboardMenuRef.current = keyboard;
1129
+ setContextPos({ x, y });
1130
+ setShowContext(true);
1131
+ }, []);
1132
+ const handleContextMenu = useCallback3(
1133
+ (event) => {
1134
+ if (!hasActions) return;
1135
+ event.preventDefault();
1136
+ event.stopPropagation();
1137
+ openMenuAt(event.clientX, event.clientY, false);
1138
+ },
1139
+ [hasActions, openMenuAt]
1140
+ );
1141
+ const handleMoreClick = useCallback3(
1142
+ (event) => {
1143
+ event.preventDefault();
1144
+ event.stopPropagation();
1145
+ const rect = event.currentTarget.getBoundingClientRect();
1146
+ openMenuAt(rect.right, rect.bottom, false);
1147
+ },
1148
+ [openMenuAt]
1149
+ );
1150
+ const openMenuByKeyboard = useCallback3(() => {
1151
+ if (!hasActions) return;
1152
+ const rect = selectRef.current?.getBoundingClientRect();
1153
+ openMenuAt(rect ? rect.left + 16 : 0, rect ? rect.bottom : 0, true);
1154
+ }, [hasActions, openMenuAt]);
1155
+ const menuItems = useCallback3(
1156
+ () => [...contextRef.current?.querySelectorAll('[role="menuitem"]') ?? []],
1157
+ []
1158
+ );
1159
+ const focusMenuItem = useCallback3(
1160
+ (index) => {
1161
+ const items = menuItems();
1162
+ if (items.length === 0) return;
1163
+ const target = items[(index % items.length + items.length) % items.length];
1164
+ target?.focus({ preventScroll: true });
1165
+ },
1166
+ [menuItems]
1167
+ );
1168
+ const handleMenuKeyDown = useCallback3(
1169
+ (event) => {
1170
+ if (event.key === "Escape" || event.key === "Tab") {
1171
+ event.preventDefault();
1172
+ event.stopPropagation();
1173
+ closeMenu(true);
1174
+ return;
1175
+ }
1176
+ const items = menuItems();
1177
+ const current = items.indexOf(document.activeElement);
1178
+ switch (event.key) {
1179
+ case "ArrowDown":
1180
+ event.preventDefault();
1181
+ focusMenuItem(current + 1);
1182
+ break;
1183
+ case "ArrowUp":
1184
+ event.preventDefault();
1185
+ focusMenuItem(current - 1);
1186
+ break;
1187
+ case "Home":
1188
+ event.preventDefault();
1189
+ focusMenuItem(0);
1190
+ break;
1191
+ case "End":
1192
+ event.preventDefault();
1193
+ focusMenuItem(items.length - 1);
1194
+ break;
1195
+ default:
1196
+ break;
1197
+ }
1198
+ },
1199
+ [closeMenu, focusMenuItem, menuItems]
1200
+ );
1201
+ const runAction = useCallback3(
1202
+ async (action, fallbackMessage) => {
1203
+ closeMenu(false);
1204
+ setActionError(null);
1205
+ if (!action) return;
1206
+ try {
1207
+ await action(pinnedDocument);
1208
+ } catch (caught) {
1209
+ setActionError(caught instanceof Error ? caught.message : fallbackMessage);
1210
+ }
1211
+ },
1212
+ [closeMenu, pinnedDocument]
1213
+ );
1214
+ useEffect3(() => {
1215
+ if (!showContext) return;
1216
+ function handleOutsideAction(event) {
1217
+ if (!contextRef.current?.contains(event.target)) setShowContext(false);
1218
+ }
1219
+ function handleScroll() {
1220
+ setShowContext(false);
1221
+ }
1222
+ document.addEventListener("click", handleOutsideAction, true);
1223
+ document.addEventListener("contextmenu", handleOutsideAction, true);
1224
+ document.addEventListener("scroll", handleScroll, { capture: true, once: true });
1225
+ return () => {
1226
+ document.removeEventListener("click", handleOutsideAction, true);
1227
+ document.removeEventListener("contextmenu", handleOutsideAction, true);
1228
+ document.removeEventListener("scroll", handleScroll, true);
1229
+ };
1230
+ }, [showContext]);
1231
+ useEffect3(() => {
1232
+ if (showContext && keyboardMenuRef.current) focusMenuItem(0);
1233
+ }, [focusMenuItem, showContext]);
1234
+ useLayoutEffect2(() => {
1235
+ if (!showContext || !contextRef.current) return;
1236
+ const rect = contextRef.current.getBoundingClientRect();
1237
+ setContextPos((current) => ({
1238
+ x: Math.max(4, Math.min(current.x, window.innerWidth - rect.width - 4)),
1239
+ y: Math.max(4, Math.min(current.y, window.innerHeight - rect.height - 4))
1240
+ }));
1241
+ }, [showContext]);
1242
+ return /* @__PURE__ */ jsxs3("div", { className: `db-pinned-document-row${selected ? " db-pinned-document-row--selected" : ""}`, children: [
1243
+ /* @__PURE__ */ jsx3(
1244
+ "button",
1245
+ {
1246
+ ref: selectRef,
1247
+ type: "button",
1248
+ className: "db-pinned-document",
1249
+ "aria-current": selected ? "page" : void 0,
1250
+ "aria-label": `${name}, ${displayPath}${missing ? ", missing" : ""}`,
1251
+ "aria-keyshortcuts": hasActions ? "Shift+F10" : void 0,
1252
+ title: displayPath,
1253
+ onClick: () => void onSelect(pinnedDocument),
1254
+ onContextMenu: handleContextMenu,
1255
+ onKeyDown: (event) => {
1256
+ if (event.key === "ContextMenu" || event.key === "F10" && event.shiftKey) {
1257
+ event.preventDefault();
1258
+ openMenuByKeyboard();
1259
+ }
1260
+ },
1261
+ children: /* @__PURE__ */ jsxs3("span", { className: "db-pinned-document-heading", children: [
1262
+ /* @__PURE__ */ jsx3("span", { className: "db-pinned-document-name", children: displayName(pinnedDocument) }),
1263
+ missing && /* @__PURE__ */ jsx3("span", { className: "db-pinned-document-missing", children: "(missing)" })
1264
+ ] })
1265
+ }
1266
+ ),
1267
+ hasActions && /* @__PURE__ */ jsx3(
1268
+ "button",
1269
+ {
1270
+ type: "button",
1271
+ className: `db-pinned-document-more${showContext ? " db-pinned-document-more--active" : ""}`,
1272
+ "aria-label": `More actions for ${name}`,
1273
+ "aria-haspopup": "menu",
1274
+ "aria-expanded": showContext,
1275
+ title: "More actions",
1276
+ onClick: handleMoreClick,
1277
+ onContextMenu: handleMoreClick,
1278
+ children: /* @__PURE__ */ jsx3(MoreIcon, {})
1279
+ }
1280
+ ),
1281
+ actionError && /* @__PURE__ */ jsx3("div", { className: "db-tree-error db-pinned-document-error", role: "alert", children: actionError }),
1282
+ showContext && createPortal2(
1283
+ /* @__PURE__ */ jsxs3(
1284
+ "div",
1285
+ {
1286
+ ref: contextRef,
1287
+ className: "db-tree-context",
1288
+ style: { left: contextPos.x, top: contextPos.y },
1289
+ role: "menu",
1290
+ "aria-label": `Actions for ${name}`,
1291
+ onKeyDown: handleMenuKeyDown,
1292
+ children: [
1293
+ onRename && /* @__PURE__ */ jsx3(
1294
+ "button",
1295
+ {
1296
+ type: "button",
1297
+ role: "menuitem",
1298
+ tabIndex: -1,
1299
+ className: "db-tree-context-item",
1300
+ onClick: () => void runAction(onRename, "Unable to rename this document."),
1301
+ children: "Rename"
1302
+ }
1303
+ ),
1304
+ onUnpin && /* @__PURE__ */ jsx3(
1305
+ "button",
1306
+ {
1307
+ type: "button",
1308
+ role: "menuitem",
1309
+ tabIndex: -1,
1310
+ className: "db-tree-context-item",
1311
+ onClick: () => void runAction(onUnpin, "Unable to unpin this document."),
1312
+ children: "Unpin"
1313
+ }
1314
+ ),
1315
+ onDelete && (onRename || onUnpin) && /* @__PURE__ */ jsx3("div", { className: "db-tree-context-divider", role: "separator" }),
1316
+ onDelete && /* @__PURE__ */ jsx3(
1317
+ "button",
1318
+ {
1319
+ type: "button",
1320
+ role: "menuitem",
1321
+ tabIndex: -1,
1322
+ className: "db-tree-context-item db-tree-context-item--danger",
1323
+ onClick: () => void runAction(onDelete, "Unable to delete this document."),
1324
+ children: "Delete"
1325
+ }
1326
+ )
1327
+ ]
1328
+ }
1329
+ ),
1330
+ document.body
1331
+ )
1332
+ ] });
1333
+ }
1334
+ function PinnedDocuments({
1335
+ documents,
1336
+ activeWorkspaceId,
1337
+ activeFilePath,
1338
+ onSelect,
1339
+ onUnpin,
1340
+ onRename,
1341
+ onDelete
1342
+ }) {
1343
+ const titleId = useId();
1344
+ if (documents.length === 0) return null;
1345
+ return /* @__PURE__ */ jsxs3("section", { className: "db-pinned-documents", "aria-labelledby": titleId, children: [
1346
+ /* @__PURE__ */ jsxs3("h2", { id: titleId, className: "db-explorer-title db-pinned-documents-title", children: [
1347
+ /* @__PURE__ */ jsx3(PinIcon, {}),
1348
+ /* @__PURE__ */ jsx3("span", { children: "Pinned" })
1349
+ ] }),
1350
+ /* @__PURE__ */ jsx3("div", { className: "db-pinned-document-list", children: documents.map((document2) => /* @__PURE__ */ jsx3(
1351
+ PinnedDocumentRow,
1352
+ {
1353
+ document: document2,
1354
+ selected: document2.workspaceId === activeWorkspaceId && document2.path === activeFilePath?.replace(/^\/+|\/+$/g, ""),
1355
+ onSelect,
1356
+ onUnpin,
1357
+ onRename,
1358
+ onDelete
1359
+ },
1360
+ `${document2.workspaceId}\0${document2.path}`
1361
+ )) })
1362
+ ] });
1363
+ }
1364
+
1365
+ // src/FileExplorer/entry-visibility.ts
1366
+ function isHiddenFileEntry(entry) {
1367
+ const name = entry.path.replace(/^\/+/, "").split("/").pop() ?? "";
1368
+ if (name.startsWith(".")) return true;
1369
+ return entry.kind === "directory" && name.endsWith("_files");
1370
+ }
1371
+ function filterVisibleFileEntries(entries) {
1372
+ return entries.filter((entry) => !isHiddenFileEntry(entry));
1373
+ }
1374
+
1375
+ // src/FileExplorer/FileExplorer.tsx
1376
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
859
1377
  var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".txt", ".md", ".docx", ".pdf", ".dbk", ".zip"]);
860
1378
  var INTERNAL_DRAG_TYPE = "application/x-docblocks-entry";
861
1379
  var NEW_ITEM_ERROR_ID = "db-new-item-error";
@@ -892,17 +1410,17 @@ function canMoveTo(entry, directoryPath) {
892
1410
  function isInternalDrag(dataTransfer) {
893
1411
  return Array.from(dataTransfer.types).includes(INTERNAL_DRAG_TYPE);
894
1412
  }
895
- function isHiddenEntry(entry) {
896
- const name = entry.path.replace(/^\/+/, "").split("/").pop() ?? "";
897
- if (name.startsWith(".")) return true;
898
- return entry.kind === "directory" && name.endsWith("_files");
899
- }
900
- function filterVisible(entries) {
901
- return entries.filter((e) => !isHiddenEntry(e));
902
- }
903
1413
  function FileExplorer({
904
1414
  provider,
905
1415
  activeFilePath,
1416
+ activeWorkspaceId,
1417
+ pinnedDocuments = [],
1418
+ pinnedPaths = [],
1419
+ onPinnedDocumentSelect,
1420
+ onPinnedDocumentUnpin,
1421
+ onPinnedDocumentRename,
1422
+ onPinnedDocumentDelete,
1423
+ onTogglePin,
906
1424
  onSelect,
907
1425
  onTreeMutation,
908
1426
  onTreeChange,
@@ -913,33 +1431,37 @@ function FileExplorer({
913
1431
  className
914
1432
  }) {
915
1433
  const tree = useFileTree(provider);
916
- const { childEntries } = tree;
1434
+ const { childEntries, childIssues } = tree;
917
1435
  const { reveal } = tree;
918
1436
  const git = useGitContext();
919
- const [newItemName, setNewItemName] = useState3("");
920
- const [newItemType, setNewItemType] = useState3(null);
921
- const [newItemCreationPending, setNewItemCreationPending] = useState3(false);
922
- const newItemCreationPendingRef = useRef3(false);
923
- const [newItemError, setNewItemError] = useState3(null);
924
- const [dragOver, setDragOver] = useState3(false);
925
- const [draggedEntry, setDraggedEntry] = useState3(null);
926
- const [dropTarget, setDropTarget] = useState3(null);
927
- const [moveError, setMoveError] = useState3(null);
928
- const [movePanelOpen, setMovePanelOpen] = useState3(false);
929
- const [moveDestinationId, setMoveDestinationId] = useState3(moveDestinations[0]?.id ?? "");
930
- const [movingToWorkspace, setMovingToWorkspace] = useState3(false);
931
- const [moveWorkspaceError, setMoveWorkspaceError] = useState3(null);
932
- const dragCounter = useRef3(0);
933
- const [activePath, setActivePath] = useState3(null);
934
- const treeRef = useRef3(null);
935
- useEffect3(() => {
1437
+ const pinnedPathSet = useMemo(
1438
+ () => new Set(pinnedPaths.map((path) => normalisePath2(path))),
1439
+ [pinnedPaths]
1440
+ );
1441
+ const [newItemName, setNewItemName] = useState4("");
1442
+ const [newItemType, setNewItemType] = useState4(null);
1443
+ const [newItemCreationPending, setNewItemCreationPending] = useState4(false);
1444
+ const newItemCreationPendingRef = useRef4(false);
1445
+ const [newItemError, setNewItemError] = useState4(null);
1446
+ const [dragOver, setDragOver] = useState4(false);
1447
+ const [draggedEntry, setDraggedEntry] = useState4(null);
1448
+ const [dropTarget, setDropTarget] = useState4(null);
1449
+ const [moveError, setMoveError] = useState4(null);
1450
+ const [movePanelOpen, setMovePanelOpen] = useState4(false);
1451
+ const [moveDestinationId, setMoveDestinationId] = useState4(moveDestinations[0]?.id ?? "");
1452
+ const [movingToWorkspace, setMovingToWorkspace] = useState4(false);
1453
+ const [moveWorkspaceError, setMoveWorkspaceError] = useState4(null);
1454
+ const dragCounter = useRef4(0);
1455
+ const [activePath, setActivePath] = useState4(null);
1456
+ const treeRef = useRef4(null);
1457
+ useEffect4(() => {
936
1458
  if (moveDestinations.some((destination) => destination.id === moveDestinationId)) return;
937
1459
  setMoveDestinationId(moveDestinations[0]?.id ?? "");
938
1460
  }, [moveDestinationId, moveDestinations]);
939
- useEffect3(() => {
1461
+ useEffect4(() => {
940
1462
  if (activeFilePath) reveal(activeFilePath, "file");
941
1463
  }, [activeFilePath, provider, reveal]);
942
- const hasSupported = useCallback3((dt) => {
1464
+ const hasSupported = useCallback4((dt) => {
943
1465
  for (const item of Array.from(dt.items)) {
944
1466
  if (item.kind !== "file") continue;
945
1467
  const name = item.getAsFile?.()?.name;
@@ -949,7 +1471,7 @@ function FileExplorer({
949
1471
  }
950
1472
  return dt.items.length > 0;
951
1473
  }, []);
952
- const handleDragEnter = useCallback3(
1474
+ const handleDragEnter = useCallback4(
953
1475
  (e) => {
954
1476
  if (isInternalDrag(e.dataTransfer)) return;
955
1477
  e.preventDefault();
@@ -958,12 +1480,12 @@ function FileExplorer({
958
1480
  },
959
1481
  [hasSupported, dragCounter]
960
1482
  );
961
- const handleDragOver = useCallback3((e) => {
1483
+ const handleDragOver = useCallback4((e) => {
962
1484
  if (isInternalDrag(e.dataTransfer)) return;
963
1485
  e.preventDefault();
964
1486
  e.dataTransfer.dropEffect = "copy";
965
1487
  }, []);
966
- const handleDragLeave = useCallback3(
1488
+ const handleDragLeave = useCallback4(
967
1489
  (e) => {
968
1490
  if (isInternalDrag(e.dataTransfer)) return;
969
1491
  e.preventDefault();
@@ -975,7 +1497,7 @@ function FileExplorer({
975
1497
  },
976
1498
  [dragCounter]
977
1499
  );
978
- const handleDrop = useCallback3(
1500
+ const handleDrop = useCallback4(
979
1501
  (e) => {
980
1502
  if (isInternalDrag(e.dataTransfer)) return;
981
1503
  e.preventDefault();
@@ -989,7 +1511,7 @@ function FileExplorer({
989
1511
  },
990
1512
  [onImportFiles]
991
1513
  );
992
- const handleSelect = useCallback3(
1514
+ const handleSelect = useCallback4(
993
1515
  (path) => {
994
1516
  const findKind = (p) => {
995
1517
  for (const e of tree.entries) {
@@ -1008,7 +1530,7 @@ function FileExplorer({
1008
1530
  },
1009
1531
  [tree, onSelect, childEntries]
1010
1532
  );
1011
- const handleNewItemSubmit = useCallback3(async () => {
1533
+ const handleNewItemSubmit = useCallback4(async () => {
1012
1534
  if (newItemCreationPendingRef.current) return;
1013
1535
  if (!newItemName.trim()) {
1014
1536
  setNewItemType(null);
@@ -1041,7 +1563,7 @@ function FileExplorer({
1041
1563
  setNewItemType(null);
1042
1564
  onTreeChange?.({ type: "create", path: createdPath });
1043
1565
  }, [newItemName, newItemType, tree, onTreeChange]);
1044
- const handleMoveToWorkspace = useCallback3(async () => {
1566
+ const handleMoveToWorkspace = useCallback4(async () => {
1045
1567
  if (!onMoveToWorkspace || !moveDestinationId || movingToWorkspace) return;
1046
1568
  setMovingToWorkspace(true);
1047
1569
  setMoveWorkspaceError(null);
@@ -1055,7 +1577,7 @@ function FileExplorer({
1055
1577
  setMovingToWorkspace(false);
1056
1578
  }
1057
1579
  }, [moveDestinationId, movingToWorkspace, onMoveToWorkspace]);
1058
- const runTreeMutation = useCallback3(
1580
+ const runTreeMutation = useCallback4(
1059
1581
  async (change, mutate) => {
1060
1582
  if (onTreeMutation) await onTreeMutation(change, mutate);
1061
1583
  else await mutate();
@@ -1063,14 +1585,14 @@ function FileExplorer({
1063
1585
  },
1064
1586
  [onTreeMutation, onTreeChange]
1065
1587
  );
1066
- const handleDelete = useCallback3(
1588
+ const handleDelete = useCallback4(
1067
1589
  async (path, kind) => {
1068
1590
  const change = { type: "delete", path, kind };
1069
1591
  await runTreeMutation(change, () => tree.deleteEntry(path));
1070
1592
  },
1071
1593
  [tree, runTreeMutation]
1072
1594
  );
1073
- const handleRename = useCallback3(
1595
+ const handleRename = useCallback4(
1074
1596
  async (oldPath, newPath, kind) => {
1075
1597
  setMoveError(null);
1076
1598
  const change = { type: "move", oldPath, newPath, kind };
@@ -1086,7 +1608,7 @@ function FileExplorer({
1086
1608
  },
1087
1609
  [tree, runTreeMutation, onTreeChange]
1088
1610
  );
1089
- const handleMove = useCallback3(
1611
+ const handleMove = useCallback4(
1090
1612
  async (entry, directoryPath) => {
1091
1613
  if (!canMoveTo(entry, directoryPath)) return;
1092
1614
  const newPath = pathInDirectory(entry, directoryPath);
@@ -1097,18 +1619,18 @@ function FileExplorer({
1097
1619
  },
1098
1620
  [handleRename, tree]
1099
1621
  );
1100
- const handleInternalDragStart = useCallback3((e, entry) => {
1622
+ const handleInternalDragStart = useCallback4((e, entry) => {
1101
1623
  e.dataTransfer.effectAllowed = "move";
1102
1624
  e.dataTransfer.setData(INTERNAL_DRAG_TYPE, entry.path);
1103
1625
  e.dataTransfer.setData("text/plain", entry.path);
1104
1626
  setDraggedEntry(entry);
1105
1627
  setMoveError(null);
1106
1628
  }, []);
1107
- const handleInternalDragEnd = useCallback3(() => {
1629
+ const handleInternalDragEnd = useCallback4(() => {
1108
1630
  setDraggedEntry(null);
1109
1631
  setDropTarget(null);
1110
1632
  }, []);
1111
- const handleEntryDragOver = useCallback3(
1633
+ const handleEntryDragOver = useCallback4(
1112
1634
  (e, entry) => {
1113
1635
  if (!isInternalDrag(e.dataTransfer)) return;
1114
1636
  e.stopPropagation();
@@ -1123,7 +1645,7 @@ function FileExplorer({
1123
1645
  },
1124
1646
  [draggedEntry]
1125
1647
  );
1126
- const handleEntryDrop = useCallback3(
1648
+ const handleEntryDrop = useCallback4(
1127
1649
  (e, entry) => {
1128
1650
  if (!isInternalDrag(e.dataTransfer)) return;
1129
1651
  e.preventDefault();
@@ -1135,7 +1657,7 @@ function FileExplorer({
1135
1657
  },
1136
1658
  [draggedEntry, handleMove]
1137
1659
  );
1138
- const handleRootDragOver = useCallback3(
1660
+ const handleRootDragOver = useCallback4(
1139
1661
  (e) => {
1140
1662
  if (!isInternalDrag(e.dataTransfer) || !draggedEntry) return;
1141
1663
  e.preventDefault();
@@ -1146,7 +1668,7 @@ function FileExplorer({
1146
1668
  },
1147
1669
  [draggedEntry]
1148
1670
  );
1149
- const handleRootDrop = useCallback3(
1671
+ const handleRootDrop = useCallback4(
1150
1672
  (e) => {
1151
1673
  if (!isInternalDrag(e.dataTransfer)) return;
1152
1674
  e.preventDefault();
@@ -1156,7 +1678,7 @@ function FileExplorer({
1156
1678
  },
1157
1679
  [draggedEntry, handleMove]
1158
1680
  );
1159
- const badgeFor = useCallback3(
1681
+ const badgeFor = useCallback4(
1160
1682
  (entry) => {
1161
1683
  if (!git?.repo) return void 0;
1162
1684
  const key = entry.path.startsWith("/") ? entry.path : `/${entry.path}`;
@@ -1166,7 +1688,7 @@ function FileExplorer({
1166
1688
  },
1167
1689
  [git]
1168
1690
  );
1169
- const gitActionsFor = useCallback3(
1691
+ const gitActionsFor = useCallback4(
1170
1692
  (entry) => {
1171
1693
  if (!git?.repo || entry.kind !== "file") return void 0;
1172
1694
  const path = entry.path.startsWith("/") ? entry.path : `/${entry.path}`;
@@ -1183,7 +1705,7 @@ function FileExplorer({
1183
1705
  roots: tree.entries,
1184
1706
  childrenOf: (dirPath) => getEquivalentPathValue(childEntries, dirPath) ?? [],
1185
1707
  isExpanded: (dirPath) => hasEquivalentPath2(tree.expanded, dirPath),
1186
- isVisible: (entry) => !isHiddenEntry(entry)
1708
+ isVisible: (entry) => !isHiddenFileEntry(entry)
1187
1709
  }),
1188
1710
  [tree.entries, tree.expanded, childEntries]
1189
1711
  );
@@ -1193,7 +1715,7 @@ function FileExplorer({
1193
1715
  return visibleRows.find((row) => normalisePath2(row.path) === target)?.path ?? null;
1194
1716
  }, [visibleRows, tree.selectedPath]);
1195
1717
  const activeRowPath = resolveActiveRow(visibleRows, activePath, selectedRowPath);
1196
- const focusRow = useCallback3((path) => {
1718
+ const focusRow = useCallback4((path) => {
1197
1719
  const rows = treeRef.current?.querySelectorAll('[role="treeitem"]');
1198
1720
  for (const row of rows ?? []) {
1199
1721
  if (row.dataset.path === path) {
@@ -1202,7 +1724,7 @@ function FileExplorer({
1202
1724
  }
1203
1725
  }
1204
1726
  }, []);
1205
- const handleTreeKeyDown = useCallback3(
1727
+ const handleTreeKeyDown = useCallback4(
1206
1728
  (e) => {
1207
1729
  const target = e.target;
1208
1730
  if (!target.classList?.contains("db-tree-row")) return;
@@ -1227,41 +1749,67 @@ function FileExplorer({
1227
1749
  },
1228
1750
  [visibleRows, activeRowPath, focusRow, tree]
1229
1751
  );
1230
- const renderEntries = useCallback3(
1752
+ const renderEntries = useCallback4(
1231
1753
  (entries, depth) => {
1232
- const visible = filterVisible(entries);
1233
- return visible.map((entry, index) => /* @__PURE__ */ jsx3(
1234
- FileTreeNode,
1235
- {
1236
- entry,
1237
- depth,
1238
- focusable: entry.path === activeRowPath,
1239
- posInSet: index + 1,
1240
- setSize: visible.length,
1241
- onRowFocus: setActivePath,
1242
- expanded: hasEquivalentPath2(tree.expanded, entry.path),
1243
- selected: tree.selectedPath !== null && normalisePath2(tree.selectedPath) === normalisePath2(entry.path),
1244
- badge: badgeFor(entry),
1245
- gitActions: gitActionsFor(entry),
1246
- onToggle: tree.toggleExpand,
1247
- onSelect: handleSelect,
1248
- confirmDelete,
1249
- onDelete: handleDelete,
1250
- onRename: handleRename,
1251
- draggable: true,
1252
- dragging: draggedEntry?.path === entry.path,
1253
- dropTarget: dropTarget === entry.path,
1254
- onDragStart: handleInternalDragStart,
1255
- onDragEnd: handleInternalDragEnd,
1256
- onDragOverEntry: handleEntryDragOver,
1257
- onDropEntry: handleEntryDrop,
1258
- renderChildren: (dirPath) => {
1259
- const children = getEquivalentPathValue(childEntries, dirPath) ?? [];
1260
- return renderEntries(children, depth + 1);
1261
- }
1262
- },
1263
- entry.path
1264
- ));
1754
+ const visible = filterVisibleFileEntries(entries);
1755
+ return visible.map((entry, index) => {
1756
+ const childIssue = entry.kind === "directory" ? getEquivalentPathValue(childIssues, entry.path) : void 0;
1757
+ return /* @__PURE__ */ jsx4(
1758
+ FileTreeNode,
1759
+ {
1760
+ entry,
1761
+ depth,
1762
+ focusable: entry.path === activeRowPath,
1763
+ posInSet: index + 1,
1764
+ setSize: visible.length,
1765
+ onRowFocus: setActivePath,
1766
+ expanded: hasEquivalentPath2(tree.expanded, entry.path),
1767
+ selected: tree.selectedPath !== null && normalisePath2(tree.selectedPath) === normalisePath2(entry.path),
1768
+ badge: badgeFor(entry),
1769
+ gitActions: gitActionsFor(entry),
1770
+ onToggle: tree.toggleExpand,
1771
+ onSelect: handleSelect,
1772
+ confirmDelete,
1773
+ onDelete: handleDelete,
1774
+ onRename: handleRename,
1775
+ pinned: entry.kind === "file" && pinnedPathSet.has(normalisePath2(entry.path)),
1776
+ onTogglePin: entry.kind === "file" ? onTogglePin : void 0,
1777
+ draggable: true,
1778
+ dragging: draggedEntry?.path === entry.path,
1779
+ dropTarget: dropTarget === entry.path,
1780
+ onDragStart: handleInternalDragStart,
1781
+ onDragEnd: handleInternalDragEnd,
1782
+ onDragOverEntry: handleEntryDragOver,
1783
+ onDropEntry: handleEntryDrop,
1784
+ renderChildren: (dirPath) => {
1785
+ const children = getEquivalentPathValue(childEntries, dirPath) ?? [];
1786
+ return renderEntries(children, depth + 1);
1787
+ },
1788
+ childError: childIssue ? /* @__PURE__ */ jsxs4(
1789
+ "div",
1790
+ {
1791
+ className: "db-tree-error db-tree-error--child",
1792
+ role: "alert",
1793
+ "data-directory-path": childIssue.directoryPath,
1794
+ children: [
1795
+ /* @__PURE__ */ jsx4("span", { children: childIssue.message }),
1796
+ " ",
1797
+ /* @__PURE__ */ jsx4(
1798
+ "button",
1799
+ {
1800
+ type: "button",
1801
+ className: "db-tree-error-retry",
1802
+ onClick: () => void tree.retryDirectory(childIssue.directoryPath),
1803
+ children: "Retry folder"
1804
+ }
1805
+ )
1806
+ ]
1807
+ }
1808
+ ) : void 0
1809
+ },
1810
+ entry.path
1811
+ );
1812
+ });
1265
1813
  },
1266
1814
  [
1267
1815
  tree,
@@ -1270,6 +1818,7 @@ function FileExplorer({
1270
1818
  handleDelete,
1271
1819
  handleRename,
1272
1820
  childEntries,
1821
+ childIssues,
1273
1822
  badgeFor,
1274
1823
  gitActionsFor,
1275
1824
  draggedEntry,
@@ -1278,10 +1827,12 @@ function FileExplorer({
1278
1827
  handleInternalDragEnd,
1279
1828
  handleEntryDragOver,
1280
1829
  handleEntryDrop,
1281
- activeRowPath
1830
+ activeRowPath,
1831
+ pinnedPathSet,
1832
+ onTogglePin
1282
1833
  ]
1283
1834
  );
1284
- return /* @__PURE__ */ jsxs3(
1835
+ return /* @__PURE__ */ jsxs4(
1285
1836
  "div",
1286
1837
  {
1287
1838
  className: `db-file-explorer ${dragOver ? "db-file-explorer--drop-active" : ""} ${className ?? ""}`,
@@ -1290,10 +1841,22 @@ function FileExplorer({
1290
1841
  onDragLeave: handleDragLeave,
1291
1842
  onDrop: handleDrop,
1292
1843
  children: [
1293
- /* @__PURE__ */ jsxs3("div", { className: "db-explorer-toolbar", children: [
1294
- /* @__PURE__ */ jsx3("span", { className: "db-explorer-title", children: "Files" }),
1295
- /* @__PURE__ */ jsxs3("div", { className: "db-explorer-actions", children: [
1296
- /* @__PURE__ */ jsx3(
1844
+ onPinnedDocumentSelect && /* @__PURE__ */ jsx4(
1845
+ PinnedDocuments,
1846
+ {
1847
+ documents: pinnedDocuments,
1848
+ activeWorkspaceId,
1849
+ activeFilePath,
1850
+ onSelect: onPinnedDocumentSelect,
1851
+ onUnpin: onPinnedDocumentUnpin,
1852
+ onRename: onPinnedDocumentRename,
1853
+ onDelete: onPinnedDocumentDelete
1854
+ }
1855
+ ),
1856
+ /* @__PURE__ */ jsxs4("div", { className: "db-explorer-toolbar", children: [
1857
+ /* @__PURE__ */ jsx4("span", { className: "db-explorer-title", children: "Files" }),
1858
+ /* @__PURE__ */ jsxs4("div", { className: "db-explorer-actions", children: [
1859
+ /* @__PURE__ */ jsx4(
1297
1860
  "button",
1298
1861
  {
1299
1862
  className: "db-explorer-btn",
@@ -1304,10 +1867,10 @@ function FileExplorer({
1304
1867
  },
1305
1868
  title: "New Folder",
1306
1869
  "aria-label": "New Folder",
1307
- children: /* @__PURE__ */ jsx3(NewFolderIcon, {})
1870
+ children: /* @__PURE__ */ jsx4(NewFolderIcon, {})
1308
1871
  }
1309
1872
  ),
1310
- /* @__PURE__ */ jsx3(
1873
+ /* @__PURE__ */ jsx4(
1311
1874
  "button",
1312
1875
  {
1313
1876
  className: "db-explorer-btn",
@@ -1318,12 +1881,12 @@ function FileExplorer({
1318
1881
  },
1319
1882
  title: "New File",
1320
1883
  "aria-label": "New File",
1321
- children: /* @__PURE__ */ jsx3(NewFileIcon, {})
1884
+ children: /* @__PURE__ */ jsx4(NewFileIcon, {})
1322
1885
  }
1323
1886
  )
1324
1887
  ] })
1325
1888
  ] }),
1326
- onMoveToWorkspace && /* @__PURE__ */ jsx3("div", { className: "db-transient-move", children: !movePanelOpen ? /* @__PURE__ */ jsx3(
1889
+ onMoveToWorkspace && /* @__PURE__ */ jsx4("div", { className: "db-transient-move", children: !movePanelOpen ? /* @__PURE__ */ jsx4(
1327
1890
  "button",
1328
1891
  {
1329
1892
  type: "button",
@@ -1334,7 +1897,7 @@ function FileExplorer({
1334
1897
  },
1335
1898
  children: "Move this into a workspace"
1336
1899
  }
1337
- ) : /* @__PURE__ */ jsxs3(
1900
+ ) : /* @__PURE__ */ jsxs4(
1338
1901
  "form",
1339
1902
  {
1340
1903
  className: "db-transient-move-form",
@@ -1344,8 +1907,8 @@ function FileExplorer({
1344
1907
  void handleMoveToWorkspace();
1345
1908
  },
1346
1909
  children: [
1347
- /* @__PURE__ */ jsx3("label", { className: "db-transient-move-label", htmlFor: "db-transient-move-destination", children: "Move this document into" }),
1348
- /* @__PURE__ */ jsx3(
1910
+ /* @__PURE__ */ jsx4("label", { className: "db-transient-move-label", htmlFor: "db-transient-move-destination", children: "Move this document into" }),
1911
+ /* @__PURE__ */ jsx4(
1349
1912
  "select",
1350
1913
  {
1351
1914
  id: "db-transient-move-destination",
@@ -1356,13 +1919,13 @@ function FileExplorer({
1356
1919
  setMoveDestinationId(event.currentTarget.value);
1357
1920
  setMoveWorkspaceError(null);
1358
1921
  },
1359
- children: moveDestinations.map((destination) => /* @__PURE__ */ jsx3("option", { value: destination.id, children: destination.name }, destination.id))
1922
+ children: moveDestinations.map((destination) => /* @__PURE__ */ jsx4("option", { value: destination.id, children: destination.name }, destination.id))
1360
1923
  }
1361
1924
  ),
1362
- moveDestinations.length === 0 && /* @__PURE__ */ jsx3("p", { className: "db-transient-move-hint", children: "Open or create another workspace first." }),
1363
- moveWorkspaceError && /* @__PURE__ */ jsx3("div", { className: "db-transient-move-error", role: "alert", children: moveWorkspaceError }),
1364
- /* @__PURE__ */ jsxs3("div", { className: "db-transient-move-actions", children: [
1365
- /* @__PURE__ */ jsx3(
1925
+ moveDestinations.length === 0 && /* @__PURE__ */ jsx4("p", { className: "db-transient-move-hint", children: "Open or create another workspace first." }),
1926
+ moveWorkspaceError && /* @__PURE__ */ jsx4("div", { className: "db-transient-move-error", role: "alert", children: moveWorkspaceError }),
1927
+ /* @__PURE__ */ jsxs4("div", { className: "db-transient-move-actions", children: [
1928
+ /* @__PURE__ */ jsx4(
1366
1929
  "button",
1367
1930
  {
1368
1931
  type: "button",
@@ -1375,7 +1938,7 @@ function FileExplorer({
1375
1938
  children: "Cancel"
1376
1939
  }
1377
1940
  ),
1378
- /* @__PURE__ */ jsx3(
1941
+ /* @__PURE__ */ jsx4(
1379
1942
  "button",
1380
1943
  {
1381
1944
  type: "submit",
@@ -1388,8 +1951,8 @@ function FileExplorer({
1388
1951
  ]
1389
1952
  }
1390
1953
  ) }),
1391
- newItemType && /* @__PURE__ */ jsxs3("div", { className: "db-new-item", children: [
1392
- /* @__PURE__ */ jsxs3(
1954
+ newItemType && /* @__PURE__ */ jsxs4("div", { className: "db-new-item", children: [
1955
+ /* @__PURE__ */ jsxs4(
1393
1956
  "form",
1394
1957
  {
1395
1958
  className: "db-new-item-row",
@@ -1399,7 +1962,7 @@ function FileExplorer({
1399
1962
  void handleNewItemSubmit();
1400
1963
  },
1401
1964
  children: [
1402
- /* @__PURE__ */ jsx3(
1965
+ /* @__PURE__ */ jsx4(
1403
1966
  "input",
1404
1967
  {
1405
1968
  className: "db-new-item-input",
@@ -1423,20 +1986,36 @@ function FileExplorer({
1423
1986
  autoFocus: true
1424
1987
  }
1425
1988
  ),
1426
- newItemType === "file" && /* @__PURE__ */ jsx3("span", { className: "db-new-item-suffix", children: ".md" }),
1427
- /* @__PURE__ */ jsx3("button", { type: "submit", className: "db-new-item-add", disabled: newItemCreationPending, children: newItemCreationPending ? "Adding\u2026" : "Add" })
1989
+ newItemType === "file" && /* @__PURE__ */ jsx4("span", { className: "db-new-item-suffix", children: ".md" }),
1990
+ /* @__PURE__ */ jsx4("button", { type: "submit", className: "db-new-item-add", disabled: newItemCreationPending, children: newItemCreationPending ? "Adding\u2026" : "Add" })
1428
1991
  ]
1429
1992
  }
1430
1993
  ),
1431
- newItemError && /* @__PURE__ */ jsx3("div", { id: NEW_ITEM_ERROR_ID, className: "db-tree-error", role: "alert", children: newItemError })
1994
+ newItemError && /* @__PURE__ */ jsx4("div", { id: NEW_ITEM_ERROR_ID, className: "db-tree-error", role: "alert", children: newItemError })
1432
1995
  ] }),
1433
- moveError && /* @__PURE__ */ jsx3("div", { className: "db-tree-error", role: "alert", children: moveError }),
1434
- tree.error && /* @__PURE__ */ jsxs3("div", { className: "db-tree-error", role: "alert", children: [
1435
- /* @__PURE__ */ jsx3("span", { children: tree.error }),
1436
- " ",
1437
- /* @__PURE__ */ jsx3("button", { type: "button", className: "db-tree-error-retry", onClick: () => void tree.refresh(), children: "Retry" })
1438
- ] }),
1439
- tree.loading ? /* @__PURE__ */ jsx3("div", { className: "db-tree", role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsx3("div", { className: "db-tree-loading", children: "Loading..." }) }) : filterVisible(tree.entries).length === 0 ? /* @__PURE__ */ jsx3("div", { className: "db-tree", children: /* @__PURE__ */ jsx3("div", { className: "db-tree-empty", children: "No files yet" }) }) : /* @__PURE__ */ jsx3(
1996
+ moveError && /* @__PURE__ */ jsx4("div", { className: "db-tree-error", role: "alert", children: moveError }),
1997
+ tree.rootIssue && /* @__PURE__ */ jsxs4(
1998
+ "div",
1999
+ {
2000
+ className: "db-tree-error",
2001
+ role: "alert",
2002
+ "data-directory-path": tree.rootIssue.directoryPath,
2003
+ children: [
2004
+ /* @__PURE__ */ jsx4("span", { children: tree.rootIssue.message }),
2005
+ " ",
2006
+ /* @__PURE__ */ jsx4(
2007
+ "button",
2008
+ {
2009
+ type: "button",
2010
+ className: "db-tree-error-retry",
2011
+ onClick: () => void tree.retryDirectory(""),
2012
+ children: "Retry"
2013
+ }
2014
+ )
2015
+ ]
2016
+ }
2017
+ ),
2018
+ tree.loading ? /* @__PURE__ */ jsx4("div", { className: "db-tree", role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsx4("div", { className: "db-tree-loading", children: "Loading..." }) }) : filterVisibleFileEntries(tree.entries).length === 0 ? /* @__PURE__ */ jsx4("div", { className: "db-tree", children: /* @__PURE__ */ jsx4("div", { className: "db-tree-empty", children: "No files yet" }) }) : /* @__PURE__ */ jsx4(
1440
2019
  "div",
1441
2020
  {
1442
2021
  ref: treeRef,
@@ -1455,17 +2034,17 @@ function FileExplorer({
1455
2034
  }
1456
2035
 
1457
2036
  // src/WorkspacePicker/WorkspacePicker.tsx
1458
- import { Fragment as Fragment2, useState as useState4, useEffect as useEffect4, useCallback as useCallback4, useRef as useRef4 } from "react";
2037
+ import { Fragment as Fragment2, useState as useState5, useEffect as useEffect5, useCallback as useCallback5, useRef as useRef5 } from "react";
1459
2038
  import { listWorkspaces, saveWorkspace, touchWorkspace } from "@bendyline/docblocks/workspace";
1460
2039
  import { isElectronHost } from "@bendyline/docblocks/host";
1461
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2040
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1462
2041
  function isNativeFileSystemSupported() {
1463
2042
  return typeof globalThis !== "undefined" && typeof globalThis.showDirectoryPicker === "function";
1464
2043
  }
1465
2044
  function WorkspacePath({ path }) {
1466
- return path.split(/([\\/])/).map((segment, index) => /* @__PURE__ */ jsxs4(Fragment2, { children: [
2045
+ return path.split(/([\\/])/).map((segment, index) => /* @__PURE__ */ jsxs5(Fragment2, { children: [
1467
2046
  segment,
1468
- (segment === "\\" || segment === "/") && /* @__PURE__ */ jsx4("wbr", {})
2047
+ (segment === "\\" || segment === "/") && /* @__PURE__ */ jsx5("wbr", {})
1469
2048
  ] }, index));
1470
2049
  }
1471
2050
  function WorkspacePicker({
@@ -1476,35 +2055,50 @@ function WorkspacePicker({
1476
2055
  refreshKey,
1477
2056
  className
1478
2057
  }) {
1479
- const [workspaces, setWorkspaces] = useState4([]);
1480
- const [isOpen, setIsOpen] = useState4(false);
1481
- const [creatingNew, setCreatingNew] = useState4(false);
1482
- const [newWorkspaceName, setNewWorkspaceName] = useState4("");
1483
- const [newWorkspaceError, setNewWorkspaceError] = useState4(null);
1484
- const [newWorkspacePending, setNewWorkspacePending] = useState4(false);
1485
- const pickerRef = useRef4(null);
1486
- useEffect4(() => {
2058
+ const [workspaces, setWorkspaces] = useState5([]);
2059
+ const [isOpen, setIsOpen] = useState5(false);
2060
+ const [creatingNew, setCreatingNew] = useState5(false);
2061
+ const [newWorkspaceName, setNewWorkspaceName] = useState5("");
2062
+ const [newWorkspaceError, setNewWorkspaceError] = useState5(null);
2063
+ const [newWorkspacePending, setNewWorkspacePending] = useState5(false);
2064
+ const pickerRef = useRef5(null);
2065
+ const triggerRef = useRef5(null);
2066
+ const closeDropdown = useCallback5((returnFocus) => {
2067
+ setIsOpen(false);
2068
+ setCreatingNew(false);
2069
+ setNewWorkspaceError(null);
2070
+ if (returnFocus) triggerRef.current?.focus({ preventScroll: true });
2071
+ }, []);
2072
+ useEffect5(() => {
1487
2073
  if (!isOpen) return;
1488
2074
  function handleOutsideClick(e) {
1489
2075
  if (pickerRef.current && !pickerRef.current.contains(e.target)) {
1490
- setIsOpen(false);
1491
- setCreatingNew(false);
1492
- setNewWorkspaceError(null);
2076
+ closeDropdown(false);
1493
2077
  }
1494
2078
  }
2079
+ function handleKeyDown(e) {
2080
+ if (e.key !== "Escape" || e.defaultPrevented) return;
2081
+ e.preventDefault();
2082
+ e.stopPropagation();
2083
+ closeDropdown(true);
2084
+ }
1495
2085
  document.addEventListener("mousedown", handleOutsideClick);
1496
- return () => document.removeEventListener("mousedown", handleOutsideClick);
1497
- }, [isOpen]);
2086
+ document.addEventListener("keydown", handleKeyDown);
2087
+ return () => {
2088
+ document.removeEventListener("mousedown", handleOutsideClick);
2089
+ document.removeEventListener("keydown", handleKeyDown);
2090
+ };
2091
+ }, [closeDropdown, isOpen]);
1498
2092
  const electron = isElectronHost();
1499
- const refresh = useCallback4(async () => {
2093
+ const refresh = useCallback5(async () => {
1500
2094
  const list = await listWorkspaces();
1501
2095
  const filtered = electron ? list.filter((w) => w.type === "electron-native" || w.type === "transient") : list.filter((w) => w.type !== "electron-native");
1502
2096
  setWorkspaces(filtered);
1503
2097
  }, [electron]);
1504
- useEffect4(() => {
2098
+ useEffect5(() => {
1505
2099
  refresh();
1506
2100
  }, [refresh, activeWorkspaceId, refreshKey]);
1507
- const handleSelect = useCallback4(
2101
+ const handleSelect = useCallback5(
1508
2102
  async (ws) => {
1509
2103
  await touchWorkspace(ws.id);
1510
2104
  onSelect(ws);
@@ -1512,7 +2106,7 @@ function WorkspacePicker({
1512
2106
  },
1513
2107
  [onSelect]
1514
2108
  );
1515
- const handleStartCreateNew = useCallback4(() => {
2109
+ const handleStartCreateNew = useCallback5(() => {
1516
2110
  const existingNames = new Set(workspaces.map((workspace) => workspace.name.toLowerCase()));
1517
2111
  let suffix = workspaces.length + 1;
1518
2112
  while (existingNames.has(`workspace ${suffix}`.toLowerCase())) suffix += 1;
@@ -1520,12 +2114,12 @@ function WorkspacePicker({
1520
2114
  setNewWorkspaceError(null);
1521
2115
  setCreatingNew(true);
1522
2116
  }, [workspaces]);
1523
- const handleCancelCreateNew = useCallback4(() => {
2117
+ const handleCancelCreateNew = useCallback5(() => {
1524
2118
  setCreatingNew(false);
1525
2119
  setNewWorkspaceName("");
1526
2120
  setNewWorkspaceError(null);
1527
2121
  }, []);
1528
- const handleCreateNew = useCallback4(async () => {
2122
+ const handleCreateNew = useCallback5(async () => {
1529
2123
  if (newWorkspacePending) return;
1530
2124
  const name = newWorkspaceName.trim();
1531
2125
  if (!name) {
@@ -1564,10 +2158,11 @@ function WorkspacePicker({
1564
2158
  }, [newWorkspaceName, newWorkspacePending, onSelect, refresh, workspaces]);
1565
2159
  const activeWs = workspaces.find((w) => w.id === activeWorkspaceId);
1566
2160
  const activeWorkspaceName = activeWs?.name ?? "No workspace";
1567
- return /* @__PURE__ */ jsxs4("div", { ref: pickerRef, className: `db-workspace-picker ${className ?? ""}`, children: [
1568
- /* @__PURE__ */ jsxs4(
2161
+ return /* @__PURE__ */ jsxs5("div", { ref: pickerRef, className: `db-workspace-picker ${className ?? ""}`, children: [
2162
+ /* @__PURE__ */ jsxs5(
1569
2163
  "button",
1570
2164
  {
2165
+ ref: triggerRef,
1571
2166
  className: "db-workspace-picker-btn",
1572
2167
  onClick: () => {
1573
2168
  const nextOpen = !isOpen;
@@ -1576,10 +2171,11 @@ function WorkspacePicker({
1576
2171
  },
1577
2172
  title: "Switch workspace",
1578
2173
  "aria-label": `Switch workspace, current: ${activeWorkspaceName}`,
2174
+ "aria-expanded": isOpen,
1579
2175
  children: [
1580
- /* @__PURE__ */ jsx4("span", { className: "db-workspace-picker-label", children: activeWorkspaceName }),
1581
- /* @__PURE__ */ jsx4("span", { className: "db-workspace-picker-compact-icon", children: /* @__PURE__ */ jsx4(FolderIcon, {}) }),
1582
- /* @__PURE__ */ jsx4(
2176
+ /* @__PURE__ */ jsx5("span", { className: "db-workspace-picker-label", children: activeWorkspaceName }),
2177
+ /* @__PURE__ */ jsx5("span", { className: "db-workspace-picker-compact-icon", children: /* @__PURE__ */ jsx5(FolderIcon, {}) }),
2178
+ /* @__PURE__ */ jsx5(
1583
2179
  "span",
1584
2180
  {
1585
2181
  className: `db-workspace-picker-caret${isOpen ? " db-workspace-picker-caret--open" : ""}`,
@@ -1589,24 +2185,24 @@ function WorkspacePicker({
1589
2185
  ]
1590
2186
  }
1591
2187
  ),
1592
- isOpen && /* @__PURE__ */ jsxs4("div", { className: "db-workspace-dropdown", children: [
1593
- workspaces.map((ws) => /* @__PURE__ */ jsx4(
2188
+ isOpen && /* @__PURE__ */ jsxs5("div", { className: "db-workspace-dropdown", children: [
2189
+ workspaces.map((ws) => /* @__PURE__ */ jsx5(
1594
2190
  "button",
1595
2191
  {
1596
2192
  className: `db-workspace-dropdown-item ${ws.id === activeWorkspaceId ? "db-workspace-dropdown-item--active" : ""}`,
1597
2193
  onClick: () => handleSelect(ws),
1598
- children: /* @__PURE__ */ jsxs4("span", { className: "db-workspace-details", children: [
1599
- /* @__PURE__ */ jsxs4("span", { className: "db-workspace-heading", children: [
1600
- /* @__PURE__ */ jsx4("span", { children: ws.name }),
1601
- (ws.type === "native" || ws.type === "electron-native") && /* @__PURE__ */ jsx4("span", { className: "db-workspace-type", children: "(folder)" })
2194
+ children: /* @__PURE__ */ jsxs5("span", { className: "db-workspace-details", children: [
2195
+ /* @__PURE__ */ jsxs5("span", { className: "db-workspace-heading", children: [
2196
+ /* @__PURE__ */ jsx5("span", { children: ws.name }),
2197
+ (ws.type === "native" || ws.type === "electron-native") && /* @__PURE__ */ jsx5("span", { className: "db-workspace-type", children: "(folder)" })
1602
2198
  ] }),
1603
- ws.rootPath && /* @__PURE__ */ jsx4("span", { className: "db-workspace-path", title: ws.rootPath, children: /* @__PURE__ */ jsx4(WorkspacePath, { path: ws.rootPath }) })
2199
+ ws.rootPath && /* @__PURE__ */ jsx5("span", { className: "db-workspace-path", title: ws.rootPath, children: /* @__PURE__ */ jsx5(WorkspacePath, { path: ws.rootPath }) })
1604
2200
  ] })
1605
2201
  },
1606
2202
  ws.id
1607
2203
  )),
1608
- /* @__PURE__ */ jsx4("div", { className: "db-workspace-dropdown-divider" }),
1609
- !electron && (creatingNew ? /* @__PURE__ */ jsxs4(
2204
+ /* @__PURE__ */ jsx5("div", { className: "db-workspace-dropdown-divider" }),
2205
+ !electron && (creatingNew ? /* @__PURE__ */ jsxs5(
1610
2206
  "form",
1611
2207
  {
1612
2208
  className: "db-workspace-create",
@@ -1616,8 +2212,8 @@ function WorkspacePicker({
1616
2212
  void handleCreateNew();
1617
2213
  },
1618
2214
  children: [
1619
- /* @__PURE__ */ jsx4("label", { className: "db-workspace-create-label", htmlFor: "db-new-workspace-name", children: "Workspace name" }),
1620
- /* @__PURE__ */ jsx4(
2215
+ /* @__PURE__ */ jsx5("label", { className: "db-workspace-create-label", htmlFor: "db-new-workspace-name", children: "Workspace name" }),
2216
+ /* @__PURE__ */ jsx5(
1621
2217
  "input",
1622
2218
  {
1623
2219
  id: "db-new-workspace-name",
@@ -1637,9 +2233,9 @@ function WorkspacePicker({
1637
2233
  }
1638
2234
  }
1639
2235
  ),
1640
- newWorkspaceError && /* @__PURE__ */ jsx4("p", { id: "db-new-workspace-error", className: "db-workspace-create-error", role: "alert", children: newWorkspaceError }),
1641
- /* @__PURE__ */ jsxs4("div", { className: "db-workspace-create-actions", children: [
1642
- /* @__PURE__ */ jsx4(
2236
+ newWorkspaceError && /* @__PURE__ */ jsx5("p", { id: "db-new-workspace-error", className: "db-workspace-create-error", role: "alert", children: newWorkspaceError }),
2237
+ /* @__PURE__ */ jsxs5("div", { className: "db-workspace-create-actions", children: [
2238
+ /* @__PURE__ */ jsx5(
1643
2239
  "button",
1644
2240
  {
1645
2241
  type: "button",
@@ -1649,15 +2245,15 @@ function WorkspacePicker({
1649
2245
  children: "Cancel"
1650
2246
  }
1651
2247
  ),
1652
- /* @__PURE__ */ jsx4("button", { type: "submit", disabled: newWorkspacePending, children: newWorkspacePending ? "Creating\xE2\u20AC\xA6" : "Create" })
2248
+ /* @__PURE__ */ jsx5("button", { type: "submit", disabled: newWorkspacePending, children: newWorkspacePending ? "Creating\xE2\u20AC\xA6" : "Create" })
1653
2249
  ] })
1654
2250
  ]
1655
2251
  }
1656
- ) : /* @__PURE__ */ jsx4("button", { className: "db-workspace-dropdown-item", onClick: handleStartCreateNew, children: /* @__PURE__ */ jsxs4("span", { className: "db-workspace-dropdown-action-label", children: [
1657
- /* @__PURE__ */ jsx4(NewFolderIcon, {}),
1658
- /* @__PURE__ */ jsx4("span", { children: "New Workspace" })
2252
+ ) : /* @__PURE__ */ jsx5("button", { className: "db-workspace-dropdown-item", onClick: handleStartCreateNew, children: /* @__PURE__ */ jsxs5("span", { className: "db-workspace-dropdown-action-label", children: [
2253
+ /* @__PURE__ */ jsx5(NewFolderIcon, {}),
2254
+ /* @__PURE__ */ jsx5("span", { children: "New Workspace" })
1659
2255
  ] }) })),
1660
- (electron || isNativeFileSystemSupported()) && /* @__PURE__ */ jsx4(
2256
+ (electron || isNativeFileSystemSupported()) && /* @__PURE__ */ jsx5(
1661
2257
  "button",
1662
2258
  {
1663
2259
  className: "db-workspace-dropdown-item",
@@ -1668,7 +2264,7 @@ function WorkspacePicker({
1668
2264
  children: "Open Folder..."
1669
2265
  }
1670
2266
  ),
1671
- onCloneRepository && /* @__PURE__ */ jsx4(
2267
+ onCloneRepository && /* @__PURE__ */ jsx5(
1672
2268
  "button",
1673
2269
  {
1674
2270
  className: "db-workspace-dropdown-item",
@@ -1684,8 +2280,7 @@ function WorkspacePicker({
1684
2280
  }
1685
2281
 
1686
2282
  // src/DocBlocksShell/DocBlocksShell.tsx
1687
- import { useState as useState14, useCallback as useCallback14, useEffect as useEffect14, useRef as useRef14, useMemo as useMemo3, lazy as lazy2, Suspense as Suspense2 } from "react";
1688
- import { MediaContext } from "@bendyline/squisq-react";
2283
+ import { useState as useState15, useCallback as useCallback15, useEffect as useEffect15, useRef as useRef15, useMemo as useMemo3, lazy as lazy2, Suspense as Suspense2 } from "react";
1689
2284
  import {
1690
2285
  DocumentVersionManager
1691
2286
  } from "@bendyline/squisq/versions";
@@ -1696,6 +2291,7 @@ import {
1696
2291
  FsError as FsError5,
1697
2292
  getFileSystemProviderV2 as getFileSystemProviderV25,
1698
2293
  isQuotaExceededError,
2294
+ moveFileSystemEntry as moveFileSystemEntry2,
1699
2295
  parseWorkspacePath as parseWorkspacePath5,
1700
2296
  workspacePathContains
1701
2297
  } from "@bendyline/docblocks/filesystem";
@@ -1722,9 +2318,9 @@ import {
1722
2318
  } from "@bendyline/docblocks/workspace";
1723
2319
 
1724
2320
  // src/AppMenu/AppMenu.tsx
1725
- import { useState as useState5, useCallback as useCallback5, useRef as useRef5, useEffect as useEffect5 } from "react";
2321
+ import { useState as useState6, useCallback as useCallback6, useRef as useRef6, useEffect as useEffect6 } from "react";
1726
2322
  import { isElectronHost as isElectronHost2 } from "@bendyline/docblocks/host";
1727
- import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2323
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1728
2324
  function formatBytes(bytes) {
1729
2325
  if (!Number.isFinite(bytes) || bytes < 0) return "\u2014";
1730
2326
  let value = bytes;
@@ -1754,14 +2350,15 @@ function AppMenu({
1754
2350
  appVersion,
1755
2351
  appBuildDate
1756
2352
  }) {
1757
- const [isOpen, setIsOpen] = useState5(false);
1758
- const [showAbout, setShowAbout] = useState5(false);
1759
- const [showSettings, setShowSettings] = useState5(false);
1760
- const [requestingPersistentStorage, setRequestingPersistentStorage] = useState5(false);
2353
+ const [isOpen, setIsOpen] = useState6(false);
2354
+ const [showAbout, setShowAbout] = useState6(false);
2355
+ const [showSettings, setShowSettings] = useState6(false);
2356
+ const [requestingPersistentStorage, setRequestingPersistentStorage] = useState6(false);
1761
2357
  const moreInformationUrl = isElectronHost2() ? "https://docblocks.com/desktop/" : "https://docblocks.com/web/";
1762
- const menuRef = useRef5(null);
1763
- const [storageEstimate, setStorageEstimate] = useState5(null);
1764
- useEffect5(() => {
2358
+ const containerRef = useRef6(null);
2359
+ const { menuRef, triggerRef, handleMenuKeyDown, handleTriggerKeyDown, closeMenu } = useMenuKeyboard(isOpen, setIsOpen);
2360
+ const [storageEstimate, setStorageEstimate] = useState6(null);
2361
+ useEffect6(() => {
1765
2362
  if (!showSettings || !getStorageEstimate) return;
1766
2363
  let cancelled = false;
1767
2364
  getStorageEstimate().then((estimate) => {
@@ -1773,22 +2370,26 @@ function AppMenu({
1773
2370
  cancelled = true;
1774
2371
  };
1775
2372
  }, [showSettings, getStorageEstimate]);
1776
- useEffect5(() => {
2373
+ useEffect6(() => {
1777
2374
  if (!isOpen) return;
1778
2375
  function handleOutsideClick(e) {
1779
- if (menuRef.current && !menuRef.current.contains(e.target)) {
1780
- setIsOpen(false);
2376
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
2377
+ closeMenu(false);
1781
2378
  }
1782
2379
  }
1783
2380
  document.addEventListener("mousedown", handleOutsideClick);
1784
2381
  return () => document.removeEventListener("mousedown", handleOutsideClick);
1785
- }, [isOpen]);
1786
- const handleAction = useCallback5((action) => {
1787
- setIsOpen(false);
1788
- action();
1789
- }, []);
1790
- const handleKeepBrowserDataFromSettings = useCallback5(async () => {
2382
+ }, [closeMenu, isOpen]);
2383
+ const handleAction = useCallback6(
2384
+ (action) => {
2385
+ closeMenu(false);
2386
+ action();
2387
+ },
2388
+ [closeMenu]
2389
+ );
2390
+ const handleKeepBrowserDataFromSettings = useCallback6(async () => {
1791
2391
  if (!onKeepBrowserData || requestingPersistentStorage) return;
2392
+ setShowSettings(false);
1792
2393
  setRequestingPersistentStorage(true);
1793
2394
  try {
1794
2395
  await onKeepBrowserData();
@@ -1796,17 +2397,19 @@ function AppMenu({
1796
2397
  setRequestingPersistentStorage(false);
1797
2398
  }
1798
2399
  }, [onKeepBrowserData, requestingPersistentStorage]);
1799
- return /* @__PURE__ */ jsxs5(Fragment3, { children: [
1800
- /* @__PURE__ */ jsxs5("div", { ref: menuRef, className: "db-app-menu", children: [
1801
- /* @__PURE__ */ jsxs5(
2400
+ return /* @__PURE__ */ jsxs6(Fragment3, { children: [
2401
+ /* @__PURE__ */ jsxs6("div", { ref: containerRef, className: "db-app-menu", children: [
2402
+ /* @__PURE__ */ jsxs6(
1802
2403
  "button",
1803
2404
  {
2405
+ ref: triggerRef,
1804
2406
  className: "db-app-menu-btn",
1805
2407
  onClick: () => setIsOpen(!isOpen),
2408
+ onKeyDown: handleTriggerKeyDown,
1806
2409
  "aria-expanded": isOpen,
1807
- "aria-haspopup": "true",
2410
+ "aria-haspopup": "menu",
1808
2411
  children: [
1809
- logoUrl ? /* @__PURE__ */ jsx5(
2412
+ logoUrl ? /* @__PURE__ */ jsx6(
1810
2413
  "img",
1811
2414
  {
1812
2415
  src: logoUrl,
@@ -1815,8 +2418,8 @@ function AppMenu({
1815
2418
  width: 1266,
1816
2419
  height: 544
1817
2420
  }
1818
- ) : /* @__PURE__ */ jsx5("span", { className: "db-app-menu-label", children: "docblocks" }),
1819
- /* @__PURE__ */ jsx5(
2421
+ ) : /* @__PURE__ */ jsx6("span", { className: "db-app-menu-label", children: "docblocks" }),
2422
+ /* @__PURE__ */ jsx6(
1820
2423
  "span",
1821
2424
  {
1822
2425
  className: `db-app-menu-caret${isOpen ? " db-app-menu-caret--open" : ""}`,
@@ -1826,86 +2429,100 @@ function AppMenu({
1826
2429
  ]
1827
2430
  }
1828
2431
  ),
1829
- isOpen && /* @__PURE__ */ jsxs5("div", { className: "db-app-menu-dropdown", role: "menu", children: [
1830
- /* @__PURE__ */ jsx5(
1831
- "button",
1832
- {
1833
- className: "db-app-menu-item",
1834
- role: "menuitem",
1835
- onClick: () => handleAction(() => setShowSettings(true)),
1836
- children: "Settings"
1837
- }
1838
- ),
1839
- onInstallApp && /* @__PURE__ */ jsx5(
1840
- "button",
1841
- {
1842
- className: "db-app-menu-item",
1843
- role: "menuitem",
1844
- onClick: () => handleAction(() => void onInstallApp()),
1845
- children: "Install DocBlocks\u2026"
1846
- }
1847
- ),
1848
- onKeepBrowserData && /* @__PURE__ */ jsx5(
1849
- "button",
1850
- {
1851
- className: "db-app-menu-item",
1852
- role: "menuitem",
1853
- onClick: () => handleAction(() => setShowSettings(true)),
1854
- children: "Storage protection\u2026"
1855
- }
1856
- ),
1857
- onDownloadAllWorkspaces && /* @__PURE__ */ jsx5(
1858
- "button",
1859
- {
1860
- className: "db-app-menu-item",
1861
- role: "menuitem",
1862
- onClick: () => handleAction(() => void onDownloadAllWorkspaces()),
1863
- children: "Download all workspaces"
1864
- }
1865
- ),
1866
- /* @__PURE__ */ jsx5("div", { className: "db-app-menu-divider" }),
1867
- /* @__PURE__ */ jsx5(
1868
- "button",
1869
- {
1870
- className: "db-app-menu-item",
1871
- role: "menuitem",
1872
- onClick: () => handleAction(() => setShowAbout(true)),
1873
- children: "About"
1874
- }
1875
- )
1876
- ] })
2432
+ isOpen && /* @__PURE__ */ jsxs6(
2433
+ "div",
2434
+ {
2435
+ ref: menuRef,
2436
+ className: "db-app-menu-dropdown",
2437
+ role: "menu",
2438
+ onKeyDown: handleMenuKeyDown,
2439
+ children: [
2440
+ /* @__PURE__ */ jsx6(
2441
+ "button",
2442
+ {
2443
+ className: "db-app-menu-item",
2444
+ role: "menuitem",
2445
+ tabIndex: -1,
2446
+ onClick: () => handleAction(() => setShowSettings(true)),
2447
+ children: "Settings"
2448
+ }
2449
+ ),
2450
+ onInstallApp && /* @__PURE__ */ jsx6(
2451
+ "button",
2452
+ {
2453
+ className: "db-app-menu-item",
2454
+ role: "menuitem",
2455
+ tabIndex: -1,
2456
+ onClick: () => handleAction(() => void onInstallApp()),
2457
+ children: "Install DocBlocks\u2026"
2458
+ }
2459
+ ),
2460
+ onKeepBrowserData && /* @__PURE__ */ jsx6(
2461
+ "button",
2462
+ {
2463
+ className: "db-app-menu-item",
2464
+ role: "menuitem",
2465
+ tabIndex: -1,
2466
+ onClick: () => handleAction(() => void handleKeepBrowserDataFromSettings()),
2467
+ children: "Protect data from browser cleanup"
2468
+ }
2469
+ ),
2470
+ onDownloadAllWorkspaces && /* @__PURE__ */ jsx6(
2471
+ "button",
2472
+ {
2473
+ className: "db-app-menu-item",
2474
+ role: "menuitem",
2475
+ tabIndex: -1,
2476
+ onClick: () => handleAction(() => void onDownloadAllWorkspaces()),
2477
+ children: "Download all workspaces"
2478
+ }
2479
+ ),
2480
+ /* @__PURE__ */ jsx6("div", { className: "db-app-menu-divider" }),
2481
+ /* @__PURE__ */ jsx6(
2482
+ "button",
2483
+ {
2484
+ className: "db-app-menu-item",
2485
+ role: "menuitem",
2486
+ tabIndex: -1,
2487
+ onClick: () => handleAction(() => setShowAbout(true)),
2488
+ children: "About"
2489
+ }
2490
+ )
2491
+ ]
2492
+ }
2493
+ )
1877
2494
  ] }),
1878
- showSettings && /* @__PURE__ */ jsxs5(SettingsDialog, { onClose: () => setShowSettings(false), children: [
1879
- /* @__PURE__ */ jsx5(
2495
+ showSettings && /* @__PURE__ */ jsxs6(SettingsDialog, { onClose: () => setShowSettings(false), children: [
2496
+ /* @__PURE__ */ jsx6(
1880
2497
  ThemeSettings,
1881
2498
  {
1882
2499
  value: themePreference,
1883
2500
  onChange: (preference) => onThemeChange?.(preference)
1884
2501
  }
1885
2502
  ),
1886
- /* @__PURE__ */ jsx5(
2503
+ /* @__PURE__ */ jsx6(
1887
2504
  AccentColorSettings,
1888
2505
  {
1889
2506
  value: accentColor,
1890
2507
  onChange: (color) => onAccentColorChange?.(color)
1891
2508
  }
1892
2509
  ),
1893
- /* @__PURE__ */ jsx5(
2510
+ /* @__PURE__ */ jsx6(
1894
2511
  WriteCanvasSettingsControls,
1895
2512
  {
1896
2513
  value: writeCanvasSettings,
1897
2514
  onChange: (settings) => onWriteCanvasSettingsChange?.(settings)
1898
2515
  }
1899
2516
  ),
1900
- getStorageEstimate && /* @__PURE__ */ jsxs5("fieldset", { className: "db-settings-fieldset", children: [
1901
- /* @__PURE__ */ jsx5("legend", { className: "db-settings-legend", children: "Storage" }),
1902
- /* @__PURE__ */ jsx5("p", { className: "db-settings-hint", children: storageEstimate ? `DocBlocks documents and app data are using ${formatBytes(
2517
+ getStorageEstimate && /* @__PURE__ */ jsxs6("fieldset", { className: "db-settings-fieldset", children: [
2518
+ /* @__PURE__ */ jsx6("legend", { className: "db-settings-legend", children: "Storage" }),
2519
+ /* @__PURE__ */ jsx6("p", { className: "db-settings-hint", children: storageEstimate ? `DocBlocks documents and app data are using ${formatBytes(
1903
2520
  storageEstimate.usage
1904
2521
  )} of the ${formatBytes(
1905
2522
  storageEstimate.quota
1906
2523
  )} this browser allows for the site.` : "Storage usage is not available in this browser." }),
1907
- storagePersistent !== void 0 && /* @__PURE__ */ jsx5("p", { className: "db-settings-hint", children: storagePersistent ? "Protected from routine browser cleanup on this device." : "Browsers may clear site data under storage pressure unless protection is granted." }),
1908
- onKeepBrowserData && /* @__PURE__ */ jsx5(
2524
+ storagePersistent !== void 0 && /* @__PURE__ */ jsx6("p", { className: "db-settings-hint", children: storagePersistent ? "Protected from routine browser cleanup on this device." : "Browsers may clear site data under storage pressure unless protection is granted." }),
2525
+ onKeepBrowserData && /* @__PURE__ */ jsx6(
1909
2526
  "button",
1910
2527
  {
1911
2528
  type: "button",
@@ -1916,16 +2533,16 @@ function AppMenu({
1916
2533
  }
1917
2534
  )
1918
2535
  ] }),
1919
- onVersioningPreferenceChange && /* @__PURE__ */ jsxs5("fieldset", { className: "db-settings-fieldset", children: [
1920
- /* @__PURE__ */ jsx5("legend", { className: "db-settings-legend", children: "Version history" }),
1921
- /* @__PURE__ */ jsxs5("p", { className: "db-settings-hint", children: [
2536
+ onVersioningPreferenceChange && /* @__PURE__ */ jsxs6("fieldset", { className: "db-settings-fieldset", children: [
2537
+ /* @__PURE__ */ jsx6("legend", { className: "db-settings-legend", children: "Version history" }),
2538
+ /* @__PURE__ */ jsxs6("p", { className: "db-settings-hint", children: [
1922
2539
  "When on, DocBlocks keeps prior revisions of each document inside a sibling",
1923
2540
  " ",
1924
- /* @__PURE__ */ jsx5("code", { children: "<name>_files/.versions/" }),
2541
+ /* @__PURE__ */ jsx6("code", { children: "<name>_files/.versions/" }),
1925
2542
  " folder. Individual workspaces can override this default in their own settings."
1926
2543
  ] }),
1927
- /* @__PURE__ */ jsxs5("label", { className: "db-settings-radio", children: [
1928
- /* @__PURE__ */ jsx5(
2544
+ /* @__PURE__ */ jsxs6("label", { className: "db-settings-radio", children: [
2545
+ /* @__PURE__ */ jsx6(
1929
2546
  "input",
1930
2547
  {
1931
2548
  type: "radio",
@@ -1937,8 +2554,8 @@ function AppMenu({
1937
2554
  ),
1938
2555
  "On for all workspaces"
1939
2556
  ] }),
1940
- /* @__PURE__ */ jsxs5("label", { className: "db-settings-radio", children: [
1941
- /* @__PURE__ */ jsx5(
2557
+ /* @__PURE__ */ jsxs6("label", { className: "db-settings-radio", children: [
2558
+ /* @__PURE__ */ jsx6(
1942
2559
  "input",
1943
2560
  {
1944
2561
  type: "radio",
@@ -1950,8 +2567,8 @@ function AppMenu({
1950
2567
  ),
1951
2568
  "On in browser workspaces, off for local folders"
1952
2569
  ] }),
1953
- /* @__PURE__ */ jsxs5("label", { className: "db-settings-radio", children: [
1954
- /* @__PURE__ */ jsx5(
2570
+ /* @__PURE__ */ jsxs6("label", { className: "db-settings-radio", children: [
2571
+ /* @__PURE__ */ jsx6(
1955
2572
  "input",
1956
2573
  {
1957
2574
  type: "radio",
@@ -1965,16 +2582,16 @@ function AppMenu({
1965
2582
  ] })
1966
2583
  ] })
1967
2584
  ] }),
1968
- showAbout && /* @__PURE__ */ jsxs5(Dialog, { title: "About DocBlocks", onClose: () => setShowAbout(false), children: [
1969
- /* @__PURE__ */ jsxs5("p", { children: [
1970
- /* @__PURE__ */ jsx5("strong", { children: "DocBlocks" }),
2585
+ showAbout && /* @__PURE__ */ jsxs6(Dialog, { title: "About DocBlocks", onClose: () => setShowAbout(false), children: [
2586
+ /* @__PURE__ */ jsxs6("p", { children: [
2587
+ /* @__PURE__ */ jsx6("strong", { children: "DocBlocks" }),
1971
2588
  " is a local-first Markdown document editor. Your files stay under your control."
1972
2589
  ] }),
1973
- /* @__PURE__ */ jsxs5("aside", { className: "db-about-beta", children: [
1974
- /* @__PURE__ */ jsx5("strong", { children: "Beta Software." }),
2590
+ /* @__PURE__ */ jsxs6("aside", { className: "db-about-beta", children: [
2591
+ /* @__PURE__ */ jsx6("strong", { children: "Beta Software." }),
1975
2592
  " We're still working through initial hiccups and issues. Please bear with us, make sure you keep backups, and",
1976
2593
  " ",
1977
- /* @__PURE__ */ jsx5(
2594
+ /* @__PURE__ */ jsx6(
1978
2595
  "a",
1979
2596
  {
1980
2597
  href: "https://github.com/bendyline/docblocks/issues/new",
@@ -1986,28 +2603,28 @@ function AppMenu({
1986
2603
  " ",
1987
2604
  "where you find them. Thanks!"
1988
2605
  ] }),
1989
- appVersion && /* @__PURE__ */ jsxs5("p", { className: "db-about-version", children: [
1990
- /* @__PURE__ */ jsx5("span", { children: "Version" }),
1991
- /* @__PURE__ */ jsx5("code", { "aria-label": `DocBlocks version ${appVersion}`, children: appVersion })
2606
+ appVersion && /* @__PURE__ */ jsxs6("p", { className: "db-about-version", children: [
2607
+ /* @__PURE__ */ jsx6("span", { children: "Version" }),
2608
+ /* @__PURE__ */ jsx6("code", { "aria-label": `DocBlocks version ${appVersion}`, children: appVersion })
1992
2609
  ] }),
1993
- appBuildDate && /* @__PURE__ */ jsxs5("p", { className: "db-about-version", children: [
1994
- /* @__PURE__ */ jsx5("span", { children: "Build" }),
1995
- /* @__PURE__ */ jsx5("time", { dateTime: appBuildDate, children: appBuildDate })
2610
+ appBuildDate && /* @__PURE__ */ jsxs6("p", { className: "db-about-version", children: [
2611
+ /* @__PURE__ */ jsx6("span", { children: "Build" }),
2612
+ /* @__PURE__ */ jsx6("time", { dateTime: appBuildDate, children: appBuildDate })
1996
2613
  ] }),
1997
- /* @__PURE__ */ jsxs5("p", { children: [
2614
+ /* @__PURE__ */ jsxs6("p", { children: [
1998
2615
  "Built with",
1999
2616
  " ",
2000
- /* @__PURE__ */ jsx5("a", { href: "https://github.com/bendyline/squisq", target: "_blank", rel: "noopener noreferrer", children: "squisq" }),
2617
+ /* @__PURE__ */ jsx6("a", { href: "https://github.com/bendyline/squisq", target: "_blank", rel: "noopener noreferrer", children: "squisq" }),
2001
2618
  " ",
2002
2619
  "by",
2003
2620
  " ",
2004
- /* @__PURE__ */ jsx5("a", { href: "https://bendyline.com", target: "_blank", rel: "noopener noreferrer", children: "Bendyline" }),
2621
+ /* @__PURE__ */ jsx6("a", { href: "https://bendyline.com", target: "_blank", rel: "noopener noreferrer", children: "Bendyline" }),
2005
2622
  "."
2006
2623
  ] }),
2007
- /* @__PURE__ */ jsxs5("p", { className: "db-dialog-links", children: [
2008
- /* @__PURE__ */ jsx5("a", { href: moreInformationUrl, target: "_blank", rel: "noopener noreferrer", children: "More information..." }),
2009
- /* @__PURE__ */ jsx5("span", { className: "db-dialog-sep", children: "\xB7" }),
2010
- /* @__PURE__ */ jsx5(
2624
+ /* @__PURE__ */ jsxs6("p", { className: "db-dialog-links", children: [
2625
+ /* @__PURE__ */ jsx6("a", { href: moreInformationUrl, target: "_blank", rel: "noopener noreferrer", children: "More information..." }),
2626
+ /* @__PURE__ */ jsx6("span", { className: "db-dialog-sep", children: "\xB7" }),
2627
+ /* @__PURE__ */ jsx6(
2011
2628
  "a",
2012
2629
  {
2013
2630
  href: "https://github.com/bendyline/docblocks",
@@ -2016,8 +2633,8 @@ function AppMenu({
2016
2633
  children: "GitHub"
2017
2634
  }
2018
2635
  ),
2019
- /* @__PURE__ */ jsx5("span", { className: "db-dialog-sep", children: "\xB7" }),
2020
- /* @__PURE__ */ jsx5(
2636
+ /* @__PURE__ */ jsx6("span", { className: "db-dialog-sep", children: "\xB7" }),
2637
+ /* @__PURE__ */ jsx6(
2021
2638
  "a",
2022
2639
  {
2023
2640
  href: "https://github.com/bendyline/docblocks/releases",
@@ -2026,8 +2643,8 @@ function AppMenu({
2026
2643
  children: "Release notes"
2027
2644
  }
2028
2645
  ),
2029
- /* @__PURE__ */ jsx5("span", { className: "db-dialog-sep", children: "\xB7" }),
2030
- /* @__PURE__ */ jsx5(
2646
+ /* @__PURE__ */ jsx6("span", { className: "db-dialog-sep", children: "\xB7" }),
2647
+ /* @__PURE__ */ jsx6(
2031
2648
  "a",
2032
2649
  {
2033
2650
  href: "https://github.com/bendyline/docblocks/issues",
@@ -2036,8 +2653,8 @@ function AppMenu({
2036
2653
  children: "Support"
2037
2654
  }
2038
2655
  ),
2039
- /* @__PURE__ */ jsx5("span", { className: "db-dialog-sep", children: "\xB7" }),
2040
- /* @__PURE__ */ jsx5(
2656
+ /* @__PURE__ */ jsx6("span", { className: "db-dialog-sep", children: "\xB7" }),
2657
+ /* @__PURE__ */ jsx6(
2041
2658
  "a",
2042
2659
  {
2043
2660
  href: "https://github.com/bendyline/docblocks/blob/main/LICENSE",
@@ -2046,8 +2663,8 @@ function AppMenu({
2046
2663
  children: "License (MIT)"
2047
2664
  }
2048
2665
  ),
2049
- /* @__PURE__ */ jsx5("span", { className: "db-dialog-sep", children: "\xB7" }),
2050
- /* @__PURE__ */ jsx5(
2666
+ /* @__PURE__ */ jsx6("span", { className: "db-dialog-sep", children: "\xB7" }),
2667
+ /* @__PURE__ */ jsx6(
2051
2668
  "a",
2052
2669
  {
2053
2670
  href: "https://github.com/bendyline/docblocks/blob/main/NOTICE.md",
@@ -2062,82 +2679,101 @@ function AppMenu({
2062
2679
  }
2063
2680
 
2064
2681
  // src/WorkspacePicker/WorkspaceSettingsButton.tsx
2065
- import { useState as useState6, useCallback as useCallback6, useRef as useRef6, useEffect as useEffect6 } from "react";
2066
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2682
+ import { useState as useState7, useCallback as useCallback7, useRef as useRef7, useEffect as useEffect7 } from "react";
2683
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2067
2684
  function WorkspaceSettingsButton({
2068
2685
  onSettings,
2069
2686
  onRename,
2070
2687
  onDownload,
2071
2688
  onRemove
2072
2689
  }) {
2073
- const [isOpen, setIsOpen] = useState6(false);
2074
- const ref = useRef6(null);
2075
- useEffect6(() => {
2690
+ const [isOpen, setIsOpen] = useState7(false);
2691
+ const containerRef = useRef7(null);
2692
+ const { menuRef, triggerRef, handleMenuKeyDown, handleTriggerKeyDown, closeMenu } = useMenuKeyboard(isOpen, setIsOpen);
2693
+ useEffect7(() => {
2076
2694
  if (!isOpen) return;
2077
2695
  function handleOutsideClick(e) {
2078
- if (ref.current && !ref.current.contains(e.target)) {
2079
- setIsOpen(false);
2696
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
2697
+ closeMenu(false);
2080
2698
  }
2081
2699
  }
2082
2700
  document.addEventListener("mousedown", handleOutsideClick);
2083
2701
  return () => document.removeEventListener("mousedown", handleOutsideClick);
2084
- }, [isOpen]);
2085
- const handleAction = useCallback6((action) => {
2086
- setIsOpen(false);
2087
- action();
2088
- }, []);
2089
- return /* @__PURE__ */ jsxs6("div", { ref, className: "db-ws-settings", children: [
2090
- /* @__PURE__ */ jsx6(
2702
+ }, [closeMenu, isOpen]);
2703
+ const handleAction = useCallback7(
2704
+ (action) => {
2705
+ closeMenu(false);
2706
+ action();
2707
+ },
2708
+ [closeMenu]
2709
+ );
2710
+ return /* @__PURE__ */ jsxs7("div", { ref: containerRef, className: "db-ws-settings", children: [
2711
+ /* @__PURE__ */ jsx7(
2091
2712
  "button",
2092
2713
  {
2714
+ ref: triggerRef,
2093
2715
  className: "db-ws-settings-btn",
2094
2716
  onClick: () => setIsOpen(!isOpen),
2717
+ onKeyDown: handleTriggerKeyDown,
2095
2718
  "aria-expanded": isOpen,
2096
- "aria-haspopup": "true",
2719
+ "aria-haspopup": "menu",
2097
2720
  "aria-label": "Workspace settings",
2098
2721
  title: "Workspace settings",
2099
- children: /* @__PURE__ */ jsx6(WorkspaceIcon, {})
2722
+ children: /* @__PURE__ */ jsx7(WorkspaceIcon, {})
2100
2723
  }
2101
2724
  ),
2102
- isOpen && /* @__PURE__ */ jsxs6("div", { className: "db-ws-settings-dropdown", role: "menu", children: [
2103
- /* @__PURE__ */ jsx6(
2104
- "button",
2105
- {
2106
- className: "db-ws-settings-item",
2107
- role: "menuitem",
2108
- onClick: () => handleAction(onSettings),
2109
- children: "Workspace settings\u2026"
2110
- }
2111
- ),
2112
- /* @__PURE__ */ jsx6(
2113
- "button",
2114
- {
2115
- className: "db-ws-settings-item",
2116
- role: "menuitem",
2117
- onClick: () => handleAction(onRename),
2118
- children: "Rename workspace"
2119
- }
2120
- ),
2121
- /* @__PURE__ */ jsx6(
2122
- "button",
2123
- {
2124
- className: "db-ws-settings-item",
2125
- role: "menuitem",
2126
- onClick: () => handleAction(onDownload),
2127
- children: "Download workspace"
2128
- }
2129
- ),
2130
- /* @__PURE__ */ jsx6("div", { className: "db-ws-settings-divider" }),
2131
- /* @__PURE__ */ jsx6(
2132
- "button",
2133
- {
2134
- className: "db-ws-settings-item db-ws-settings-item--danger",
2135
- role: "menuitem",
2136
- onClick: () => handleAction(onRemove),
2137
- children: "Remove workspace"
2138
- }
2139
- )
2140
- ] })
2725
+ isOpen && /* @__PURE__ */ jsxs7(
2726
+ "div",
2727
+ {
2728
+ ref: menuRef,
2729
+ className: "db-ws-settings-dropdown",
2730
+ role: "menu",
2731
+ onKeyDown: handleMenuKeyDown,
2732
+ children: [
2733
+ /* @__PURE__ */ jsx7(
2734
+ "button",
2735
+ {
2736
+ className: "db-ws-settings-item",
2737
+ role: "menuitem",
2738
+ tabIndex: -1,
2739
+ onClick: () => handleAction(onSettings),
2740
+ children: "Workspace settings\u2026"
2741
+ }
2742
+ ),
2743
+ /* @__PURE__ */ jsx7(
2744
+ "button",
2745
+ {
2746
+ className: "db-ws-settings-item",
2747
+ role: "menuitem",
2748
+ tabIndex: -1,
2749
+ onClick: () => handleAction(onRename),
2750
+ children: "Rename workspace"
2751
+ }
2752
+ ),
2753
+ /* @__PURE__ */ jsx7(
2754
+ "button",
2755
+ {
2756
+ className: "db-ws-settings-item",
2757
+ role: "menuitem",
2758
+ tabIndex: -1,
2759
+ onClick: () => handleAction(onDownload),
2760
+ children: "Download workspace"
2761
+ }
2762
+ ),
2763
+ /* @__PURE__ */ jsx7("div", { className: "db-ws-settings-divider" }),
2764
+ /* @__PURE__ */ jsx7(
2765
+ "button",
2766
+ {
2767
+ className: "db-ws-settings-item db-ws-settings-item--danger",
2768
+ role: "menuitem",
2769
+ tabIndex: -1,
2770
+ onClick: () => handleAction(onRemove),
2771
+ children: "Remove workspace"
2772
+ }
2773
+ )
2774
+ ]
2775
+ }
2776
+ )
2141
2777
  ] });
2142
2778
  }
2143
2779
 
@@ -2177,7 +2813,7 @@ function resolveVersioningEnabled(workspace, globalPref) {
2177
2813
  }
2178
2814
 
2179
2815
  // src/WorkspacePicker/WorkspaceSettingsDialog.tsx
2180
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2816
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2181
2817
  var VERSIONING_LABELS = {
2182
2818
  on: "on",
2183
2819
  "browser-only": "on for browser workspaces, off for local folders",
@@ -2196,23 +2832,23 @@ function WorkspaceSettingsDialog({
2196
2832
  globalVersioningPreference
2197
2833
  );
2198
2834
  const inheritLabel = `Inherit default \u2014 currently ${VERSIONING_LABELS[globalVersioningPreference]} (${inheritedEnabled ? "on" : "off"} for this workspace)`;
2199
- return /* @__PURE__ */ jsxs7(Dialog, { title: "Workspace settings", onClose, children: [
2200
- /* @__PURE__ */ jsxs7("p", { className: "db-settings-hint", children: [
2201
- /* @__PURE__ */ jsx7("strong", { children: workspace.name }),
2835
+ return /* @__PURE__ */ jsxs8(Dialog, { title: "Workspace settings", onClose, children: [
2836
+ /* @__PURE__ */ jsxs8("p", { className: "db-settings-hint", children: [
2837
+ /* @__PURE__ */ jsx8("strong", { children: workspace.name }),
2202
2838
  " \xB7",
2203
2839
  " ",
2204
2840
  isLocal ? "Local folder workspace" : "Browser workspace"
2205
2841
  ] }),
2206
- /* @__PURE__ */ jsxs7("fieldset", { className: "db-settings-fieldset", children: [
2207
- /* @__PURE__ */ jsx7("legend", { className: "db-settings-legend", children: "Version history" }),
2208
- /* @__PURE__ */ jsxs7("p", { className: "db-settings-hint", children: [
2842
+ /* @__PURE__ */ jsxs8("fieldset", { className: "db-settings-fieldset", children: [
2843
+ /* @__PURE__ */ jsx8("legend", { className: "db-settings-legend", children: "Version history" }),
2844
+ /* @__PURE__ */ jsxs8("p", { className: "db-settings-hint", children: [
2209
2845
  "Controls whether DocBlocks keeps prior revisions in",
2210
2846
  " ",
2211
- /* @__PURE__ */ jsx7("code", { children: "<name>_files/.versions/" }),
2847
+ /* @__PURE__ */ jsx8("code", { children: "<name>_files/.versions/" }),
2212
2848
  " for documents in this workspace."
2213
2849
  ] }),
2214
- /* @__PURE__ */ jsxs7("label", { className: "db-settings-radio", children: [
2215
- /* @__PURE__ */ jsx7(
2850
+ /* @__PURE__ */ jsxs8("label", { className: "db-settings-radio", children: [
2851
+ /* @__PURE__ */ jsx8(
2216
2852
  "input",
2217
2853
  {
2218
2854
  type: "radio",
@@ -2224,8 +2860,8 @@ function WorkspaceSettingsDialog({
2224
2860
  ),
2225
2861
  inheritLabel
2226
2862
  ] }),
2227
- /* @__PURE__ */ jsxs7("label", { className: "db-settings-radio", children: [
2228
- /* @__PURE__ */ jsx7(
2863
+ /* @__PURE__ */ jsxs8("label", { className: "db-settings-radio", children: [
2864
+ /* @__PURE__ */ jsx8(
2229
2865
  "input",
2230
2866
  {
2231
2867
  type: "radio",
@@ -2237,8 +2873,8 @@ function WorkspaceSettingsDialog({
2237
2873
  ),
2238
2874
  "On for this workspace"
2239
2875
  ] }),
2240
- /* @__PURE__ */ jsxs7("label", { className: "db-settings-radio", children: [
2241
- /* @__PURE__ */ jsx7(
2876
+ /* @__PURE__ */ jsxs8("label", { className: "db-settings-radio", children: [
2877
+ /* @__PURE__ */ jsx8(
2242
2878
  "input",
2243
2879
  {
2244
2880
  type: "radio",
@@ -2255,14 +2891,14 @@ function WorkspaceSettingsDialog({
2255
2891
  }
2256
2892
 
2257
2893
  // src/hooks/useDocumentSession.ts
2258
- import { useState as useState7, useSyncExternalStore } from "react";
2894
+ import { useState as useState8, useSyncExternalStore } from "react";
2259
2895
  import {
2260
2896
  DocumentRecoveryJournal,
2261
2897
  DocumentSession,
2262
2898
  getDefaultDocumentRecoveryStorage
2263
2899
  } from "@bendyline/docblocks/document";
2264
2900
  function useDocumentSession(autoSaveDelayMs = 500) {
2265
- const [session] = useState7(
2901
+ const [session] = useState8(
2266
2902
  () => new DocumentSession({
2267
2903
  autoSaveDelayMs,
2268
2904
  recoveryJournal: new DocumentRecoveryJournal(getDefaultDocumentRecoveryStorage())
@@ -2278,38 +2914,38 @@ function useDocumentSession(autoSaveDelayMs = 500) {
2278
2914
 
2279
2915
  // src/Export/DeferredExportToolbarControls.tsx
2280
2916
  import { lazy, Suspense } from "react";
2281
- import { jsx as jsx8 } from "react/jsx-runtime";
2917
+ import { jsx as jsx9 } from "react/jsx-runtime";
2282
2918
  var ExportToolbarControlsImplementation = lazy(
2283
- () => import("./ExportToolbarControls-2VB43ITO.js").then((module) => ({
2919
+ () => import("./ExportToolbarControls-773QRE4R.js").then((module) => ({
2284
2920
  default: module.ExportToolbarControls
2285
2921
  }))
2286
2922
  );
2287
2923
  function ExportToolbarControls(props) {
2288
- return /* @__PURE__ */ jsx8(Suspense, { fallback: null, children: /* @__PURE__ */ jsx8(ExportToolbarControlsImplementation, { ...props }) });
2924
+ return /* @__PURE__ */ jsx9(Suspense, { fallback: null, children: /* @__PURE__ */ jsx9(ExportToolbarControlsImplementation, { ...props }) });
2289
2925
  }
2290
2926
 
2291
2927
  // src/Git/useGit.ts
2292
- import { useCallback as useCallback8, useEffect as useEffect8, useMemo as useMemo2, useRef as useRef8, useState as useState9 } from "react";
2928
+ import { useCallback as useCallback9, useEffect as useEffect9, useMemo as useMemo2, useRef as useRef9, useState as useState10 } from "react";
2293
2929
  import { maybeGetDocBlocksHost } from "@bendyline/docblocks/host";
2294
2930
 
2295
2931
  // src/Git/useGitStatus.ts
2296
- import { useCallback as useCallback7, useEffect as useEffect7, useRef as useRef7, useState as useState8 } from "react";
2932
+ import { useCallback as useCallback8, useEffect as useEffect8, useRef as useRef8, useState as useState9 } from "react";
2297
2933
  var SCHEDULE_REFRESH_MS = 1500;
2298
2934
  function statusKey(status) {
2299
2935
  return JSON.stringify(status);
2300
2936
  }
2301
2937
  function useGitStatus(gitApi, repositoryId, enabled) {
2302
- const [status, setStatus] = useState8(null);
2303
- const lastKeyRef = useRef7(null);
2304
- const timerRef = useRef7(null);
2305
- const generationRef = useRef7(0);
2306
- const apply = useCallback7((next) => {
2938
+ const [status, setStatus] = useState9(null);
2939
+ const lastKeyRef = useRef8(null);
2940
+ const timerRef = useRef8(null);
2941
+ const generationRef = useRef8(0);
2942
+ const apply = useCallback8((next) => {
2307
2943
  const key = statusKey(next);
2308
2944
  if (key === lastKeyRef.current) return;
2309
2945
  lastKeyRef.current = key;
2310
2946
  setStatus(next);
2311
2947
  }, []);
2312
- useEffect7(() => {
2948
+ useEffect8(() => {
2313
2949
  generationRef.current += 1;
2314
2950
  if (timerRef.current) {
2315
2951
  clearTimeout(timerRef.current);
@@ -2327,7 +2963,7 @@ function useGitStatus(gitApi, repositoryId, enabled) {
2327
2963
  unsubscribe();
2328
2964
  };
2329
2965
  }, [gitApi, repositoryId, enabled, apply]);
2330
- const refresh = useCallback7(() => {
2966
+ const refresh = useCallback8(() => {
2331
2967
  if (!gitApi || !repositoryId || !enabled) return;
2332
2968
  const generation = generationRef.current;
2333
2969
  void gitApi.status(repositoryId).then(
@@ -2341,14 +2977,14 @@ function useGitStatus(gitApi, repositoryId, enabled) {
2341
2977
  () => void 0
2342
2978
  );
2343
2979
  }, [gitApi, repositoryId, enabled, apply]);
2344
- const scheduleRefresh = useCallback7(() => {
2980
+ const scheduleRefresh = useCallback8(() => {
2345
2981
  if (timerRef.current) clearTimeout(timerRef.current);
2346
2982
  timerRef.current = setTimeout(() => {
2347
2983
  timerRef.current = null;
2348
2984
  refresh();
2349
2985
  }, SCHEDULE_REFRESH_MS);
2350
2986
  }, [refresh]);
2351
- useEffect7(() => {
2987
+ useEffect8(() => {
2352
2988
  return () => {
2353
2989
  if (timerRef.current) clearTimeout(timerRef.current);
2354
2990
  };
@@ -2374,8 +3010,8 @@ function useGit(provider, requestedWorkspaceId, theme) {
2374
3010
  const host = maybeGetDocBlocksHost();
2375
3011
  const gitApi = host?.git ?? null;
2376
3012
  const workspaceId = gitApi ? requestedWorkspaceId : null;
2377
- const [capabilities, setCapabilities] = useState9(null);
2378
- useEffect8(() => {
3013
+ const [capabilities, setCapabilities] = useState10(null);
3014
+ useEffect9(() => {
2379
3015
  if (!gitApi) return;
2380
3016
  let cancelled = false;
2381
3017
  let timer = null;
@@ -2401,9 +3037,9 @@ function useGit(provider, requestedWorkspaceId, theme) {
2401
3037
  };
2402
3038
  }, [gitApi]);
2403
3039
  const available = gitApi !== null && capabilities?.gitAvailable === true;
2404
- const [repo, setRepo] = useState9(null);
2405
- const [remoteWeb, setRemoteWeb] = useState9(null);
2406
- useEffect8(() => {
3040
+ const [repo, setRepo] = useState10(null);
3041
+ const [remoteWeb, setRemoteWeb] = useState10(null);
3042
+ useEffect9(() => {
2407
3043
  setRepo(null);
2408
3044
  setRemoteWeb(null);
2409
3045
  if (!gitApi || !workspaceId || !available) return;
@@ -2436,15 +3072,15 @@ function useGit(provider, requestedWorkspaceId, theme) {
2436
3072
  const isRepo = repositoryId !== null;
2437
3073
  const { status, refresh, scheduleRefresh } = useGitStatus(gitApi, repositoryId, isRepo);
2438
3074
  const badges = useMemo2(() => buildBadgeMap(status?.changes ?? []), [status]);
2439
- const [busy, setBusy] = useState9(null);
2440
- const [lastResult, setLastResult] = useState9(null);
2441
- const [dialog, setDialog] = useState9({ kind: "none" });
2442
- const openDialog = useCallback8((next) => {
3075
+ const [busy, setBusy] = useState10(null);
3076
+ const [lastResult, setLastResult] = useState10(null);
3077
+ const [dialog, setDialog] = useState10({ kind: "none" });
3078
+ const openDialog = useCallback9((next) => {
2443
3079
  setLastResult(null);
2444
3080
  setDialog(next);
2445
3081
  }, []);
2446
- const closeDialog = useCallback8(() => setDialog({ kind: "none" }), []);
2447
- const routeError = useCallback8(
3082
+ const closeDialog = useCallback9(() => setDialog({ kind: "none" }), []);
3083
+ const routeError = useCallback9(
2448
3084
  (operation, error) => {
2449
3085
  if (error.code === "auth-failed") {
2450
3086
  setDialog({
@@ -2458,11 +3094,11 @@ function useGit(provider, requestedWorkspaceId, theme) {
2458
3094
  },
2459
3095
  []
2460
3096
  );
2461
- const statusRef = useRef8(status);
2462
- useEffect8(() => {
3097
+ const statusRef = useRef9(status);
3098
+ useEffect9(() => {
2463
3099
  statusRef.current = status;
2464
3100
  }, [status]);
2465
- const runAction = useCallback8(
3101
+ const runAction = useCallback9(
2466
3102
  async (kind, operation, action, successMessage) => {
2467
3103
  if (!gitApi || !repositoryId) return false;
2468
3104
  setBusy(kind);
@@ -2491,7 +3127,7 @@ function useGit(provider, requestedWorkspaceId, theme) {
2491
3127
  },
2492
3128
  [gitApi, repositoryId, refresh, routeError]
2493
3129
  );
2494
- const commit = useCallback8(
3130
+ const commit = useCallback9(
2495
3131
  (message, paths) => runAction(
2496
3132
  "commit",
2497
3133
  "commit",
@@ -2500,7 +3136,7 @@ function useGit(provider, requestedWorkspaceId, theme) {
2500
3136
  ),
2501
3137
  [runAction]
2502
3138
  );
2503
- const push = useCallback8(async () => {
3139
+ const push = useCallback9(async () => {
2504
3140
  const setUpstream = statusRef.current?.upstream === null;
2505
3141
  await runAction(
2506
3142
  "push",
@@ -2509,13 +3145,13 @@ function useGit(provider, requestedWorkspaceId, theme) {
2509
3145
  setUpstream ? "Branch published" : "Pushed"
2510
3146
  );
2511
3147
  }, [runAction]);
2512
- const pull = useCallback8(async () => {
3148
+ const pull = useCallback9(async () => {
2513
3149
  await runAction("pull", "pull", (api, repository) => api.pull(repository), "Pulled");
2514
3150
  }, [runAction]);
2515
- const fetchRemote = useCallback8(async () => {
3151
+ const fetchRemote = useCallback9(async () => {
2516
3152
  await runAction("fetch", "fetch", (api, repository) => api.fetch(repository), "Fetched");
2517
3153
  }, [runAction]);
2518
- const createBranch = useCallback8(
3154
+ const createBranch = useCallback9(
2519
3155
  (name) => runAction(
2520
3156
  "branch",
2521
3157
  "branch",
@@ -2524,7 +3160,7 @@ function useGit(provider, requestedWorkspaceId, theme) {
2524
3160
  ),
2525
3161
  [runAction]
2526
3162
  );
2527
- const switchBranch = useCallback8(
3163
+ const switchBranch = useCallback9(
2528
3164
  (name) => runAction(
2529
3165
  "branch",
2530
3166
  "branch",
@@ -2533,7 +3169,7 @@ function useGit(provider, requestedWorkspaceId, theme) {
2533
3169
  ),
2534
3170
  [runAction]
2535
3171
  );
2536
- const openOnRemote = useCallback8(
3172
+ const openOnRemote = useCallback9(
2537
3173
  (filePath) => {
2538
3174
  if (!remoteWeb || !host) return;
2539
3175
  let url = remoteWeb.webUrl;
@@ -2545,7 +3181,7 @@ function useGit(provider, requestedWorkspaceId, theme) {
2545
3181
  },
2546
3182
  [remoteWeb, host]
2547
3183
  );
2548
- const createPullRequest = useCallback8(async () => {
3184
+ const createPullRequest = useCallback9(async () => {
2549
3185
  if (!gitApi || !repositoryId) return;
2550
3186
  setBusy("pr");
2551
3187
  setLastResult(null);
@@ -2663,7 +3299,7 @@ function retainFileSystemProvider(provider) {
2663
3299
  }
2664
3300
 
2665
3301
  // src/DocBlocksShell/document-title.ts
2666
- import { useEffect as useEffect9 } from "react";
3302
+ import { useEffect as useEffect10 } from "react";
2667
3303
  var APP_TITLE = "DocBlocks";
2668
3304
  var INSTALLED_DISPLAY_QUERIES = [
2669
3305
  "(display-mode: window-controls-overlay)",
@@ -2687,7 +3323,7 @@ function titleForSelectedFile(selectedFile, homeDocumentTitle = APP_TITLE, homeD
2687
3323
  return includeAppName ? `${name} - ${APP_TITLE}` : name;
2688
3324
  }
2689
3325
  function useDocumentTitle(selectedFile, homeDocumentTitle, homeDocumentPath) {
2690
- useEffect9(() => {
3326
+ useEffect10(() => {
2691
3327
  if (typeof document === "undefined") return;
2692
3328
  const displayQueries = typeof globalThis.matchMedia === "function" ? INSTALLED_DISPLAY_QUERIES.map((query) => globalThis.matchMedia(query)) : [];
2693
3329
  const updateTitle = () => {
@@ -2946,33 +3582,33 @@ function summariseImport(result) {
2946
3582
  }
2947
3583
 
2948
3584
  // src/components/usePromptDialog.ts
2949
- import React2, { useCallback as useCallback10, useEffect as useEffect11, useRef as useRef10, useState as useState11 } from "react";
3585
+ import React2, { useCallback as useCallback11, useEffect as useEffect12, useRef as useRef11, useState as useState12 } from "react";
2950
3586
 
2951
3587
  // src/components/PromptDialog.tsx
2952
- import { useCallback as useCallback9, useEffect as useEffect10, useId, useRef as useRef9, useState as useState10 } from "react";
2953
- import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
3588
+ import { useCallback as useCallback10, useEffect as useEffect11, useId as useId2, useRef as useRef10, useState as useState11 } from "react";
3589
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2954
3590
  function PromptDialog({ request, onSettle }) {
2955
- const [value, setValue] = useState10(request.initialValue ?? "");
2956
- const inputRef = useRef9(null);
2957
- const inputId = useId();
2958
- const cancel = useCallback9(() => onSettle(null), [onSettle]);
2959
- const submit = useCallback9(() => {
3591
+ const [value, setValue] = useState11(request.initialValue ?? "");
3592
+ const inputRef = useRef10(null);
3593
+ const inputId = useId2();
3594
+ const cancel = useCallback10(() => onSettle(null), [onSettle]);
3595
+ const submit = useCallback10(() => {
2960
3596
  if (value.trim() === "") return;
2961
3597
  onSettle(value);
2962
3598
  }, [onSettle, value]);
2963
- useEffect10(() => {
3599
+ useEffect11(() => {
2964
3600
  inputRef.current?.select();
2965
3601
  }, []);
2966
- return /* @__PURE__ */ jsx9(
3602
+ return /* @__PURE__ */ jsx10(
2967
3603
  Dialog,
2968
3604
  {
2969
3605
  title: request.title,
2970
3606
  onClose: cancel,
2971
3607
  initialFocusRef: inputRef,
2972
3608
  closeOnBackdrop: false,
2973
- footer: /* @__PURE__ */ jsxs8(Fragment4, { children: [
2974
- /* @__PURE__ */ jsx9("button", { type: "button", className: "db-git-secondary-btn", onClick: cancel, children: "Cancel" }),
2975
- /* @__PURE__ */ jsx9(
3609
+ footer: /* @__PURE__ */ jsxs9(Fragment4, { children: [
3610
+ /* @__PURE__ */ jsx10("button", { type: "button", className: "db-git-secondary-btn", onClick: cancel, children: "Cancel" }),
3611
+ /* @__PURE__ */ jsx10(
2976
3612
  "button",
2977
3613
  {
2978
3614
  type: "button",
@@ -2983,9 +3619,9 @@ function PromptDialog({ request, onSettle }) {
2983
3619
  }
2984
3620
  )
2985
3621
  ] }),
2986
- children: /* @__PURE__ */ jsxs8("div", { className: "db-git-form-row", children: [
2987
- /* @__PURE__ */ jsx9("label", { className: "db-git-form-label", htmlFor: inputId, children: request.label }),
2988
- /* @__PURE__ */ jsx9(
3622
+ children: /* @__PURE__ */ jsxs9("div", { className: "db-git-form-row", children: [
3623
+ /* @__PURE__ */ jsx10("label", { className: "db-git-form-label", htmlFor: inputId, children: request.label }),
3624
+ /* @__PURE__ */ jsx10(
2989
3625
  "input",
2990
3626
  {
2991
3627
  id: inputId,
@@ -3008,17 +3644,17 @@ function PromptDialog({ request, onSettle }) {
3008
3644
 
3009
3645
  // src/components/usePromptDialog.ts
3010
3646
  function usePromptDialog() {
3011
- const [pending, setPending] = useState11(null);
3012
- const pendingRef = useRef10(null);
3013
- const nextIdRef = useRef10(0);
3014
- const settlePending = useCallback10((value) => {
3647
+ const [pending, setPending] = useState12(null);
3648
+ const pendingRef = useRef11(null);
3649
+ const nextIdRef = useRef11(0);
3650
+ const settlePending = useCallback11((value) => {
3015
3651
  const current = pendingRef.current;
3016
3652
  if (!current) return;
3017
3653
  pendingRef.current = null;
3018
3654
  setPending((open) => open?.id === current.id ? null : open);
3019
3655
  current.settle(value);
3020
3656
  }, []);
3021
- const prompt = useCallback10(
3657
+ const prompt = useCallback11(
3022
3658
  (request) => new Promise((resolve) => {
3023
3659
  settlePending(null);
3024
3660
  const next = { id: ++nextIdRef.current, request, settle: resolve };
@@ -3027,7 +3663,7 @@ function usePromptDialog() {
3027
3663
  }),
3028
3664
  [settlePending]
3029
3665
  );
3030
- useEffect11(() => {
3666
+ useEffect12(() => {
3031
3667
  return () => settlePending(null);
3032
3668
  }, [settlePending]);
3033
3669
  const promptDialog = pending ? React2.createElement(PromptDialog, {
@@ -3040,28 +3676,28 @@ function usePromptDialog() {
3040
3676
  }
3041
3677
 
3042
3678
  // src/components/useConfirmDialog.ts
3043
- import React4, { useCallback as useCallback12, useEffect as useEffect12, useRef as useRef12, useState as useState12 } from "react";
3679
+ import React4, { useCallback as useCallback13, useEffect as useEffect13, useRef as useRef13, useState as useState13 } from "react";
3044
3680
 
3045
3681
  // src/components/ConfirmDialog.tsx
3046
- import { useCallback as useCallback11, useRef as useRef11 } from "react";
3047
- import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
3682
+ import { useCallback as useCallback12, useRef as useRef12 } from "react";
3683
+ import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3048
3684
  function ConfirmDialog({ request, onSettle }) {
3049
3685
  const acknowledge = request.kind === "acknowledge";
3050
3686
  const destructive = request.kind === "confirm" && request.destructive === true;
3051
- const cancelRef = useRef11(null);
3052
- const confirmRef = useRef11(null);
3053
- const accept = useCallback11(() => onSettle(true), [onSettle]);
3054
- const reject = useCallback11(() => onSettle(false), [onSettle]);
3687
+ const cancelRef = useRef12(null);
3688
+ const confirmRef = useRef12(null);
3689
+ const accept = useCallback12(() => onSettle(true), [onSettle]);
3690
+ const reject = useCallback12(() => onSettle(false), [onSettle]);
3055
3691
  const close = acknowledge ? accept : reject;
3056
- return /* @__PURE__ */ jsx10(
3692
+ return /* @__PURE__ */ jsx11(
3057
3693
  Dialog,
3058
3694
  {
3059
3695
  title: request.title,
3060
3696
  onClose: close,
3061
3697
  initialFocusRef: destructive ? cancelRef : confirmRef,
3062
- footer: /* @__PURE__ */ jsxs9(Fragment5, { children: [
3063
- !acknowledge && /* @__PURE__ */ jsx10("button", { ref: cancelRef, type: "button", className: "db-git-secondary-btn", onClick: reject, children: request.cancelLabel ?? "Cancel" }),
3064
- /* @__PURE__ */ jsx10(
3698
+ footer: /* @__PURE__ */ jsxs10(Fragment5, { children: [
3699
+ !acknowledge && /* @__PURE__ */ jsx11("button", { ref: cancelRef, type: "button", className: "db-git-secondary-btn", onClick: reject, children: request.cancelLabel ?? "Cancel" }),
3700
+ /* @__PURE__ */ jsx11(
3065
3701
  "button",
3066
3702
  {
3067
3703
  ref: confirmRef,
@@ -3072,24 +3708,24 @@ function ConfirmDialog({ request, onSettle }) {
3072
3708
  }
3073
3709
  )
3074
3710
  ] }),
3075
- children: /* @__PURE__ */ jsx10("p", { className: "db-dialog-message", children: request.message })
3711
+ children: /* @__PURE__ */ jsx11("p", { className: "db-dialog-message", children: request.message })
3076
3712
  }
3077
3713
  );
3078
3714
  }
3079
3715
 
3080
3716
  // src/components/useConfirmDialog.ts
3081
3717
  function useConfirmDialog() {
3082
- const [pending, setPending] = useState12(null);
3083
- const pendingRef = useRef12(null);
3084
- const nextIdRef = useRef12(0);
3085
- const settlePending = useCallback12((value) => {
3718
+ const [pending, setPending] = useState13(null);
3719
+ const pendingRef = useRef13(null);
3720
+ const nextIdRef = useRef13(0);
3721
+ const settlePending = useCallback13((value) => {
3086
3722
  const current = pendingRef.current;
3087
3723
  if (!current) return;
3088
3724
  pendingRef.current = null;
3089
3725
  setPending((open2) => open2?.id === current.id ? null : open2);
3090
3726
  current.settle(value);
3091
3727
  }, []);
3092
- const open = useCallback12(
3728
+ const open = useCallback13(
3093
3729
  (request) => new Promise((resolve) => {
3094
3730
  settlePending(false);
3095
3731
  const next = { id: ++nextIdRef.current, request, settle: resolve };
@@ -3098,17 +3734,17 @@ function useConfirmDialog() {
3098
3734
  }),
3099
3735
  [settlePending]
3100
3736
  );
3101
- const confirm = useCallback12(
3737
+ const confirm = useCallback13(
3102
3738
  (request) => open({ kind: "confirm", ...request }),
3103
3739
  [open]
3104
3740
  );
3105
- const acknowledge = useCallback12(
3741
+ const acknowledge = useCallback13(
3106
3742
  async (request) => {
3107
3743
  await open({ kind: "acknowledge", ...request });
3108
3744
  },
3109
3745
  [open]
3110
3746
  );
3111
- useEffect12(() => {
3747
+ useEffect13(() => {
3112
3748
  return () => settlePending(false);
3113
3749
  }, [settlePending]);
3114
3750
  const confirmDialog = pending ? React4.createElement(ConfirmDialog, {
@@ -3161,7 +3797,7 @@ function buildIssueReportUrl(environment) {
3161
3797
  }
3162
3798
 
3163
3799
  // src/DocBlocksShell/last-state.ts
3164
- import { tryParseWorkspacePath } from "@bendyline/docblocks/filesystem";
3800
+ import { tryParseWorkspacePath as tryParseWorkspacePath2 } from "@bendyline/docblocks/filesystem";
3165
3801
  var LAST_STATE_KEY = "docblocks:lastState";
3166
3802
  var MAX_WORKSPACE_ID_CHARACTERS = 256;
3167
3803
  var MAX_WORKSPACE_PATH_CHARACTERS = 4096;
@@ -3182,7 +3818,7 @@ function parseLastState(value) {
3182
3818
  if (!hasExactKeys(record, ["workspaceId", "filePath", "view"])) return null;
3183
3819
  if (!isBoundedText(record.workspaceId, MAX_WORKSPACE_ID_CHARACTERS)) return null;
3184
3820
  if (!isBoundedText(record.filePath, MAX_WORKSPACE_PATH_CHARACTERS)) return null;
3185
- const parsedPath = tryParseWorkspacePath(record.filePath);
3821
+ const parsedPath = tryParseWorkspacePath2(record.filePath);
3186
3822
  if (!parsedPath) return null;
3187
3823
  if (typeof record.view !== "string" || !EDITOR_VIEWS.has(record.view)) return null;
3188
3824
  return {
@@ -3282,8 +3918,8 @@ function saveViewPreferences(preferences) {
3282
3918
  }
3283
3919
 
3284
3920
  // src/DocBlocksShell/UpdateAvailableNotice.tsx
3285
- import { useState as useState13 } from "react";
3286
- import { Fragment as Fragment6, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3921
+ import { useState as useState14 } from "react";
3922
+ import { Fragment as Fragment6, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
3287
3923
  var UPDATE_PROMPT_ID = "db-update-available-prompt";
3288
3924
  function UpdateAvailableNotice({
3289
3925
  available,
@@ -3291,11 +3927,11 @@ function UpdateAvailableNotice({
3291
3927
  blocked = false,
3292
3928
  statusBarVisible
3293
3929
  }) {
3294
- const [promptOpen, setPromptOpen] = useState13(false);
3930
+ const [promptOpen, setPromptOpen] = useState14(false);
3295
3931
  if (!available || !onApplyUpdate) return null;
3296
3932
  const promptVisible = promptOpen && !blocked;
3297
- return /* @__PURE__ */ jsxs10(Fragment6, { children: [
3298
- /* @__PURE__ */ jsx11(
3933
+ return /* @__PURE__ */ jsxs11(Fragment6, { children: [
3934
+ /* @__PURE__ */ jsx12(
3299
3935
  "button",
3300
3936
  {
3301
3937
  type: "button",
@@ -3309,17 +3945,17 @@ function UpdateAvailableNotice({
3309
3945
  children: "Update available"
3310
3946
  }
3311
3947
  ),
3312
- promptVisible && /* @__PURE__ */ jsxs10(
3948
+ promptVisible && /* @__PURE__ */ jsxs11(
3313
3949
  "div",
3314
3950
  {
3315
3951
  id: UPDATE_PROMPT_ID,
3316
3952
  className: `db-update-banner${statusBarVisible ? "" : " db-update-banner--floating"}`,
3317
3953
  role: "alert",
3318
3954
  children: [
3319
- /* @__PURE__ */ jsx11("span", { children: "A new version of DocBlocks is available. Reload to update the editor and site pages." }),
3320
- /* @__PURE__ */ jsxs10("div", { className: "db-update-banner-actions", children: [
3321
- /* @__PURE__ */ jsx11("button", { type: "button", onClick: onApplyUpdate, disabled: blocked, children: "Reload" }),
3322
- /* @__PURE__ */ jsx11("button", { type: "button", onClick: () => setPromptOpen(false), children: "Later" })
3955
+ /* @__PURE__ */ jsx12("span", { children: "A new version of DocBlocks is available. Reload to update the editor and site pages." }),
3956
+ /* @__PURE__ */ jsxs11("div", { className: "db-update-banner-actions", children: [
3957
+ /* @__PURE__ */ jsx12("button", { type: "button", onClick: onApplyUpdate, disabled: blocked, children: "Reload" }),
3958
+ /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => setPromptOpen(false), children: "Later" })
3323
3959
  ] })
3324
3960
  ]
3325
3961
  }
@@ -3328,7 +3964,7 @@ function UpdateAvailableNotice({
3328
3964
  }
3329
3965
 
3330
3966
  // src/DocBlocksShell/useDocumentLinkProvider.ts
3331
- import { useCallback as useCallback13, useEffect as useEffect13, useRef as useRef13 } from "react";
3967
+ import { useCallback as useCallback14, useEffect as useEffect14, useRef as useRef14 } from "react";
3332
3968
 
3333
3969
  // src/DocBlocksShell/document-links.ts
3334
3970
  import {
@@ -3510,8 +4146,8 @@ function createDocumentLinkCandidates(entries, selectedFile, query) {
3510
4146
 
3511
4147
  // src/DocBlocksShell/useDocumentLinkProvider.ts
3512
4148
  function useDocumentLinkProvider(provider, selectedFile, epoch = 0) {
3513
- const cacheRef = useRef13(null);
3514
- useEffect13(
4149
+ const cacheRef = useRef14(null);
4150
+ useEffect14(
3515
4151
  () => () => {
3516
4152
  if (cacheRef.current?.provider !== provider || cacheRef.current.epoch !== epoch) return;
3517
4153
  cacheRef.current.controller.abort();
@@ -3519,7 +4155,7 @@ function useDocumentLinkProvider(provider, selectedFile, epoch = 0) {
3519
4155
  },
3520
4156
  [provider, epoch]
3521
4157
  );
3522
- return useCallback13(
4158
+ return useCallback14(
3523
4159
  async (query) => {
3524
4160
  if (!provider || !selectedFile) return [];
3525
4161
  let cache = cacheRef.current;
@@ -3720,7 +4356,7 @@ var WELCOME_DOCUMENT_CONTENT = [
3720
4356
  ].join("\n");
3721
4357
 
3722
4358
  // src/DocBlocksShell/DocBlocksShell.tsx
3723
- import { Fragment as Fragment7, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
4359
+ import { Fragment as Fragment7, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
3724
4360
  var editorShellModulePromise = null;
3725
4361
  function loadEditorShell() {
3726
4362
  editorShellModulePromise ?? (editorShellModulePromise = (async () => {
@@ -3732,9 +4368,9 @@ function loadEditorShell() {
3732
4368
  return editorShellModulePromise;
3733
4369
  }
3734
4370
  var EditorShell = lazy2(loadEditorShell);
3735
- var GitUI = lazy2(() => import("./GitUI-GMH3PAYL.js").then((m) => ({ default: m.GitUI })));
4371
+ var GitUI = lazy2(() => import("./GitUI-CAKFVKWY.js").then((m) => ({ default: m.GitUI })));
3736
4372
  var GitToolbarControl = lazy2(
3737
- () => import("./GitToolbarControl-FW3BZFXV.js").then((m) => ({ default: m.GitToolbarControl }))
4373
+ () => import("./GitToolbarControl-VJVK2B4X.js").then((m) => ({ default: m.GitToolbarControl }))
3738
4374
  );
3739
4375
  var DOCBLOCKS_VIDEO_EXPORT_PALETTE = Object.freeze({
3740
4376
  overlay: "rgba(0, 0, 0, 0.72)",
@@ -3809,10 +4445,10 @@ function isMemoryWorkspaceProvider(provider) {
3809
4445
  return typeof candidate.captureContents === "function" && typeof candidate.replaceContents === "function" && typeof candidate.treeVersion === "number";
3810
4446
  }
3811
4447
  function useOsTheme() {
3812
- const [dark, setDark] = useState14(
4448
+ const [dark, setDark] = useState15(
3813
4449
  () => typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches
3814
4450
  );
3815
- useEffect14(() => {
4451
+ useEffect15(() => {
3816
4452
  const mq = window.matchMedia("(prefers-color-scheme: dark)");
3817
4453
  const handler = (e) => setDark(e.matches);
3818
4454
  mq.addEventListener("change", handler);
@@ -3883,6 +4519,32 @@ async function readProviderText(provider, path) {
3883
4519
  const file = await providerV2.readFile(parseWorkspacePath5(path));
3884
4520
  return file ? decodeUtf8Text(file.data, { label: "The document", path: parseWorkspacePath5(path) }) : null;
3885
4521
  }
4522
+ async function pinnedProviderFileExists(provider, path) {
4523
+ const providerV2 = getFileSystemProviderV25(provider);
4524
+ if (!providerV2) return provider.exists(path);
4525
+ const entry = await providerV2.stat(parseWorkspacePath5(path));
4526
+ if (!entry) return false;
4527
+ if (entry.kind !== "file") throw new Error("The pinned path is no longer a document.");
4528
+ return true;
4529
+ }
4530
+ async function removePinnedProviderFile(provider, path) {
4531
+ const providerV2 = getFileSystemProviderV25(provider);
4532
+ if (providerV2) {
4533
+ const canonical = parseWorkspacePath5(path);
4534
+ const entry = await providerV2.stat(canonical);
4535
+ if (!entry) return false;
4536
+ if (entry.kind !== "file") throw new Error("The pinned path is no longer a document.");
4537
+ await providerV2.remove(canonical, {
4538
+ recursive: false,
4539
+ missing: "error",
4540
+ expectedVersion: entry.version
4541
+ });
4542
+ return true;
4543
+ }
4544
+ if (!await provider.exists(path)) return false;
4545
+ await provider.delete(path);
4546
+ return true;
4547
+ }
3886
4548
  async function readStableFileSnapshot(provider, path) {
3887
4549
  const providerV2 = getFileSystemProviderV25(provider);
3888
4550
  if (providerV2) {
@@ -3968,10 +4630,10 @@ async function createElectronProviderFromWorkspace(ws) {
3968
4630
  return createElectronFileSystemProvider(ws.id, ws.name, ws.rootPath);
3969
4631
  }
3970
4632
  function useIsMobile(breakpoint = 768) {
3971
- const [isMobile, setIsMobile] = useState14(
4633
+ const [isMobile, setIsMobile] = useState15(
3972
4634
  () => typeof window !== "undefined" && window.matchMedia(`(max-width: ${breakpoint}px)`).matches
3973
4635
  );
3974
- useEffect14(() => {
4636
+ useEffect15(() => {
3975
4637
  const mq = window.matchMedia(`(max-width: ${breakpoint}px)`);
3976
4638
  const handler = (e) => setIsMobile(e.matches);
3977
4639
  mq.addEventListener("change", handler);
@@ -3980,7 +4642,7 @@ function useIsMobile(breakpoint = 768) {
3980
4642
  return isMobile;
3981
4643
  }
3982
4644
  function FolderGlyph() {
3983
- return /* @__PURE__ */ jsx12(
4645
+ return /* @__PURE__ */ jsx13(
3984
4646
  "svg",
3985
4647
  {
3986
4648
  viewBox: "0 0 24 24",
@@ -3989,12 +4651,12 @@ function FolderGlyph() {
3989
4651
  strokeWidth: "1.5",
3990
4652
  strokeLinejoin: "round",
3991
4653
  "aria-hidden": "true",
3992
- children: /* @__PURE__ */ jsx12("path", { d: "M3 7a1 1 0 0 1 1-1h5l2 2h9a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7z" })
4654
+ children: /* @__PURE__ */ jsx13("path", { d: "M3 7a1 1 0 0 1 1-1h5l2 2h9a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7z" })
3993
4655
  }
3994
4656
  );
3995
4657
  }
3996
4658
  function FileGlyph() {
3997
- return /* @__PURE__ */ jsxs11(
4659
+ return /* @__PURE__ */ jsxs12(
3998
4660
  "svg",
3999
4661
  {
4000
4662
  viewBox: "0 0 24 24",
@@ -4004,8 +4666,8 @@ function FileGlyph() {
4004
4666
  strokeLinejoin: "round",
4005
4667
  "aria-hidden": "true",
4006
4668
  children: [
4007
- /* @__PURE__ */ jsx12("path", { d: "M6 3h8l5 5v12a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z" }),
4008
- /* @__PURE__ */ jsx12("path", { d: "M14 3v5h5" })
4669
+ /* @__PURE__ */ jsx13("path", { d: "M6 3h8l5 5v12a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z" }),
4670
+ /* @__PURE__ */ jsx13("path", { d: "M14 3v5h5" })
4009
4671
  ]
4010
4672
  }
4011
4673
  );
@@ -4026,48 +4688,58 @@ function DocBlocksShell({
4026
4688
  versioningRef,
4027
4689
  updateAvailable = false,
4028
4690
  onApplyUpdate,
4691
+ statusBarSlotRight,
4029
4692
  offlineReady = false
4030
4693
  }) {
4031
4694
  const osTheme = useOsTheme();
4032
- const [themePreference, setThemePreference] = useState14(loadThemePreference);
4033
- const [accentColor, setAccentColor] = useState14(loadAccentColor);
4034
- const [writeCanvasSettings, setWriteCanvasSettings] = useState14(
4695
+ const [themePreference, setThemePreference] = useState15(loadThemePreference);
4696
+ const [accentColor, setAccentColor] = useState15(loadAccentColor);
4697
+ const [writeCanvasSettings, setWriteCanvasSettings] = useState15(
4035
4698
  loadWriteCanvasPreferences
4036
4699
  );
4700
+ const editorWriteCanvasSettings = useMemo3(
4701
+ () => ({
4702
+ textSize: writeCanvasSettings.textSize,
4703
+ lineSpacing: writeCanvasSettings.lineSpacing,
4704
+ ...resolveWriteCanvasFonts(writeCanvasSettings.fontScheme)
4705
+ }),
4706
+ [writeCanvasSettings]
4707
+ );
4037
4708
  const resolvedTheme = resolveShellTheme({ preference: themePreference, hostTheme, osTheme });
4038
- const handleThemeChange = useCallback14((pref) => {
4709
+ const handleThemeChange = useCallback15((pref) => {
4039
4710
  setThemePreference(pref);
4040
4711
  saveThemePreference(pref);
4041
4712
  }, []);
4042
- useEffect14(() => {
4713
+ useEffect15(() => {
4043
4714
  if (isElectronHost3() || typeof document === "undefined") return;
4044
4715
  const metas = document.querySelectorAll('meta[name="theme-color"]');
4045
4716
  metas.forEach((meta) => {
4046
4717
  meta.content = DB_CHROME_COLORS[resolvedTheme];
4047
4718
  });
4048
4719
  }, [resolvedTheme]);
4049
- const handleAccentColorChange = useCallback14((color) => {
4720
+ const handleAccentColorChange = useCallback15((color) => {
4050
4721
  setAccentColor(color);
4051
4722
  saveAccentColor(color);
4052
4723
  }, []);
4053
- const handleWriteCanvasSettingsChange = useCallback14((settings) => {
4724
+ const handleWriteCanvasSettingsChange = useCallback15((settings) => {
4054
4725
  setWriteCanvasSettings(settings);
4055
4726
  saveWriteCanvasPreferences(settings);
4056
4727
  }, []);
4057
- const [viewPreferences, setViewPreferences] = useState14(loadViewPreferences);
4058
- const handleViewPreferencesChange = useCallback14((prefs) => {
4728
+ const [viewPreferences, setViewPreferences] = useState15(loadViewPreferences);
4729
+ const handleViewPreferencesChange = useCallback15((prefs) => {
4059
4730
  setViewPreferences(prefs);
4060
4731
  saveViewPreferences(prefs);
4061
4732
  }, []);
4062
4733
  const isMobile = useIsMobile();
4063
- const [mobileShowEditor, setMobileShowEditor] = useState14(
4734
+ const defaultPreviewViewportPreset = useResponsivePreviewViewportPreset();
4735
+ const [mobileShowEditor, setMobileShowEditor] = useState15(
4064
4736
  () => isMobile && isWelcomeGatewayDismissed()
4065
4737
  );
4066
- useEffect14(() => {
4738
+ useEffect15(() => {
4067
4739
  if (isMobile) void loadEditorShell();
4068
4740
  }, [isMobile]);
4069
- const [sidebarWidth, setSidebarWidth] = useState14(loadSidebarWidth);
4070
- const [compactLayout, setCompactLayout] = useState14(false);
4741
+ const [sidebarWidth, setSidebarWidth] = useState15(loadSidebarWidth);
4742
+ const [compactLayout, setCompactLayout] = useState15(false);
4071
4743
  const effectiveCompact = isMobile || compactLayout;
4072
4744
  const showBrowserStorageWarning = !isElectronHost3();
4073
4745
  const appVersion = isElectronHost3() ? `${getDocBlocksHost().env.appVersion} desktop` : issueReportVersion ?? "web";
@@ -4076,17 +4748,17 @@ function DocBlocksShell({
4076
4748
  version: appVersion,
4077
4749
  userAgent: typeof navigator === "undefined" ? "" : navigator.userAgent
4078
4750
  });
4079
- const [browserStoragePersistent, setBrowserStoragePersistent] = useState14(false);
4080
- const [saveToast, setSaveToast] = useState14(null);
4081
- const saveToastTimerRef = useRef14(null);
4082
- const showToast = useCallback14((kind, message) => {
4751
+ const [browserStoragePersistent, setBrowserStoragePersistent] = useState15(false);
4752
+ const [saveToast, setSaveToast] = useState15(null);
4753
+ const saveToastTimerRef = useRef15(null);
4754
+ const showToast = useCallback15((kind, message) => {
4083
4755
  setSaveToast({ kind, message });
4084
4756
  if (saveToastTimerRef.current) clearTimeout(saveToastTimerRef.current);
4085
4757
  saveToastTimerRef.current = setTimeout(() => setSaveToast(null), 3e3);
4086
4758
  }, []);
4087
4759
  const { prompt: promptForText, promptDialog } = usePromptDialog();
4088
4760
  const { confirm: confirmAction, acknowledge, confirmDialog } = useConfirmDialog();
4089
- const confirmDeleteEntry = useCallback14(
4761
+ const confirmDeleteEntry = useCallback15(
4090
4762
  (message) => confirmAction({
4091
4763
  title: "Delete",
4092
4764
  message,
@@ -4095,12 +4767,12 @@ function DocBlocksShell({
4095
4767
  }),
4096
4768
  [confirmAction]
4097
4769
  );
4098
- const [installPromptEvent, setInstallPromptEvent] = useState14(
4770
+ const [installPromptEvent, setInstallPromptEvent] = useState15(
4099
4771
  null
4100
4772
  );
4101
- const sidebarRef = useRef14(null);
4102
- const dragStateRef = useRef14(null);
4103
- const handleResizerPointerDown = useCallback14(
4773
+ const sidebarRef = useRef15(null);
4774
+ const dragStateRef = useRef15(null);
4775
+ const handleResizerPointerDown = useCallback15(
4104
4776
  (e) => {
4105
4777
  if (e.button !== 0) return;
4106
4778
  dragStateRef.current = { startX: e.clientX, startWidth: sidebarWidth };
@@ -4152,17 +4824,17 @@ function DocBlocksShell({
4152
4824
  },
4153
4825
  [sidebarWidth]
4154
4826
  );
4155
- const [provider, setProvider] = useState14(null);
4156
- const [workspaceStartupError, setWorkspaceStartupError] = useState14(null);
4157
- const [activeWorkspaceId, setActiveWorkspaceId] = useState14(null);
4158
- const [activeWorkspaceDescriptor, setActiveWorkspaceDescriptor] = useState14(null);
4159
- useEffect14(() => {
4827
+ const [provider, setProvider] = useState15(null);
4828
+ const [workspaceStartupError, setWorkspaceStartupError] = useState15(null);
4829
+ const [activeWorkspaceId, setActiveWorkspaceId] = useState15(null);
4830
+ const [activeWorkspaceDescriptor, setActiveWorkspaceDescriptor] = useState15(null);
4831
+ useEffect15(() => {
4160
4832
  if (!provider || getTransientWorkspace(provider.id)) return;
4161
4833
  const providerV2 = getFileSystemProviderV25(provider);
4162
4834
  return providerV2 ? retainFileSystemProvider(providerV2) : void 0;
4163
4835
  }, [provider]);
4164
- const [descriptorRefreshKey, setDescriptorRefreshKey] = useState14(0);
4165
- useEffect14(() => {
4836
+ const [descriptorRefreshKey, setDescriptorRefreshKey] = useState15(0);
4837
+ useEffect15(() => {
4166
4838
  let cancelled = false;
4167
4839
  if (!activeWorkspaceId) {
4168
4840
  setActiveWorkspaceDescriptor(null);
@@ -4175,10 +4847,94 @@ function DocBlocksShell({
4175
4847
  cancelled = true;
4176
4848
  };
4177
4849
  }, [activeWorkspaceId, descriptorRefreshKey]);
4178
- const [transientMoveDestinations, setTransientMoveDestinations] = useState14(
4850
+ const [pinnedDocuments, setPinnedDocuments] = useState15(loadPinnedDocuments);
4851
+ const [pinnedDocumentAvailability, setPinnedDocumentAvailability] = useState15(() => /* @__PURE__ */ new Map());
4852
+ useEffect15(() => {
4853
+ savePinnedDocuments(pinnedDocuments);
4854
+ }, [pinnedDocuments]);
4855
+ useEffect15(() => {
4856
+ if (!isElectronHost3()) return;
4857
+ getDocBlocksHost().menu.setPinnedDocuments(
4858
+ pinnedDocuments.map((document2) => ({
4859
+ workspaceId: document2.workspaceId,
4860
+ workspaceName: document2.workspaceName,
4861
+ path: document2.path
4862
+ }))
4863
+ );
4864
+ }, [pinnedDocuments]);
4865
+ const setPinnedAvailability = useCallback15(
4866
+ (document2, availability) => {
4867
+ const key = pinnedDocumentKey(document2);
4868
+ setPinnedDocumentAvailability((current) => {
4869
+ if (current.get(key) === availability) return current;
4870
+ const next = new Map(current);
4871
+ next.set(key, availability);
4872
+ return next;
4873
+ });
4874
+ },
4875
+ []
4876
+ );
4877
+ const pinnedDocumentItems = useMemo3(
4878
+ () => pinnedDocuments.map((document2) => ({
4879
+ ...document2,
4880
+ availability: pinnedDocumentAvailability.get(pinnedDocumentKey(document2)) ?? "unknown"
4881
+ })),
4882
+ [pinnedDocumentAvailability, pinnedDocuments]
4883
+ );
4884
+ useEffect15(() => {
4885
+ if (!provider || !activeWorkspaceId) return;
4886
+ const activePins = pinnedDocuments.filter(
4887
+ (document2) => document2.workspaceId === activeWorkspaceId
4888
+ );
4889
+ if (activePins.length === 0) return;
4890
+ let cancelled = false;
4891
+ let scanning = false;
4892
+ let scanAgain = false;
4893
+ const scan = async () => {
4894
+ if (scanning) {
4895
+ scanAgain = true;
4896
+ return;
4897
+ }
4898
+ scanning = true;
4899
+ try {
4900
+ do {
4901
+ scanAgain = false;
4902
+ await Promise.all(
4903
+ activePins.map(async (document2) => {
4904
+ try {
4905
+ const exists = await providerEntryExists(provider, document2.path);
4906
+ if (!cancelled) setPinnedAvailability(document2, exists ? "available" : "missing");
4907
+ } catch {
4908
+ if (!cancelled) setPinnedAvailability(document2, "unknown");
4909
+ }
4910
+ })
4911
+ );
4912
+ } while (scanAgain && !cancelled);
4913
+ } finally {
4914
+ scanning = false;
4915
+ }
4916
+ };
4917
+ void scan();
4918
+ const scanOnFocus = () => void scan();
4919
+ window.addEventListener("focus", scanOnFocus);
4920
+ const providerV2 = getFileSystemProviderV25(provider);
4921
+ const subscription = providerV2?.capabilities.watch ? providerV2.watch(
4922
+ (event) => {
4923
+ if (event.type !== "modified") void scan();
4924
+ },
4925
+ { onError: () => void 0 }
4926
+ ) : null;
4927
+ void subscription?.ready.catch(() => void 0);
4928
+ return () => {
4929
+ cancelled = true;
4930
+ window.removeEventListener("focus", scanOnFocus);
4931
+ void subscription?.dispose();
4932
+ };
4933
+ }, [activeWorkspaceId, pinnedDocuments, provider, setPinnedAvailability]);
4934
+ const [transientMoveDestinations, setTransientMoveDestinations] = useState15(
4179
4935
  []
4180
4936
  );
4181
- useEffect14(() => {
4937
+ useEffect15(() => {
4182
4938
  if (activeWorkspaceDescriptor?.type !== "transient") {
4183
4939
  setTransientMoveDestinations([]);
4184
4940
  return;
@@ -4201,21 +4957,21 @@ function DocBlocksShell({
4201
4957
  }, [activeWorkspaceDescriptor]);
4202
4958
  const gitWorkspaceId = provider && activeWorkspaceDescriptor?.id === activeWorkspaceId && provider.id === activeWorkspaceId && activeWorkspaceDescriptor.type === "electron-native" ? activeWorkspaceDescriptor.id : null;
4203
4959
  const git = useGit(provider, gitWorkspaceId, resolvedTheme);
4204
- const gitRef = useRef14(git);
4960
+ const gitRef = useRef15(git);
4205
4961
  gitRef.current = git;
4206
4962
  const { scheduleRefresh: gitScheduleRefresh } = git;
4207
- const [workspaceSettingsOpen, setWorkspaceSettingsOpen] = useState14(false);
4208
- const [versioningPreference, setVersioningPreference] = useState14(loadVersioningPreference);
4209
- const handleVersioningPreferenceChange = useCallback14((pref) => {
4963
+ const [workspaceSettingsOpen, setWorkspaceSettingsOpen] = useState15(false);
4964
+ const [versioningPreference, setVersioningPreference] = useState15(loadVersioningPreference);
4965
+ const handleVersioningPreferenceChange = useCallback15((pref) => {
4210
4966
  setVersioningPreference(pref);
4211
4967
  saveVersioningPreference(pref);
4212
4968
  }, []);
4213
4969
  const effectiveVersioning = allowVersioning && resolveVersioningEnabled(activeWorkspaceDescriptor, versioningPreference);
4214
- const handleOpenWorkspaceSettings = useCallback14(() => {
4970
+ const handleOpenWorkspaceSettings = useCallback15(() => {
4215
4971
  if (!activeWorkspaceDescriptor) return;
4216
4972
  setWorkspaceSettingsOpen(true);
4217
4973
  }, [activeWorkspaceDescriptor]);
4218
- const handleWorkspaceVersioningOverrideChange = useCallback14(
4974
+ const handleWorkspaceVersioningOverrideChange = useCallback15(
4219
4975
  async (override) => {
4220
4976
  if (!activeWorkspaceDescriptor) return;
4221
4977
  await saveWorkspace2({ ...activeWorkspaceDescriptor, versioningOverride: override });
@@ -4224,7 +4980,7 @@ function DocBlocksShell({
4224
4980
  },
4225
4981
  [activeWorkspaceDescriptor]
4226
4982
  );
4227
- const [selectedFile, setSelectedFile] = useState14(null);
4983
+ const [selectedFile, setSelectedFile] = useState15(null);
4228
4984
  useDocumentTitle(selectedFile, homeDocumentTitle, homeDocumentPath);
4229
4985
  const exportDestinationAdapter = useMemo3(() => {
4230
4986
  if (!isElectronHost3() || !activeWorkspaceId || !selectedFile) return void 0;
@@ -4236,8 +4992,12 @@ function DocBlocksShell({
4236
4992
  saveBlob: async (blob, filename, target) => host.save(documentId, filename, target?.grantId ?? null, await blob.arrayBuffer())
4237
4993
  };
4238
4994
  }, [activeWorkspaceId, selectedFile]);
4239
- const [selectedFolder, setSelectedFolder] = useState14(null);
4240
- const [folderEntries, setFolderEntries] = useState14([]);
4995
+ const [selectedFolder, setSelectedFolder] = useState15(null);
4996
+ const [folderEntries, setFolderEntries] = useState15([]);
4997
+ const visibleFolderEntries = useMemo3(
4998
+ () => filterVisibleFileEntries(folderEntries),
4999
+ [folderEntries]
5000
+ );
4241
5001
  const { session: documentSession, snapshot: documentSnapshot } = useDocumentSession(500);
4242
5002
  const editorContent = documentSnapshot.content;
4243
5003
  const editorSessionScope = useMemo3(
@@ -4247,22 +5007,22 @@ function DocBlocksShell({
4247
5007
  } : null,
4248
5008
  [documentSnapshot.frozen, documentSnapshot.generation, documentSnapshot.targetKey]
4249
5009
  );
4250
- const [editorPresentationEpoch, setEditorPresentationEpoch] = useState14(0);
5010
+ const [editorPresentationEpoch, setEditorPresentationEpoch] = useState15(0);
4251
5011
  const editorKey = `${documentSnapshot.generation}:${editorPresentationEpoch}`;
4252
5012
  const editorPlaceholder = useMemo3(() => {
4253
5013
  void editorKey;
4254
5014
  return pickEmptyDocumentPrompt();
4255
5015
  }, [editorKey]);
4256
- const [explorerKey, setExplorerKey] = useState14(0);
4257
- const [documentLinkEpoch, setDocumentLinkEpoch] = useState14(0);
4258
- const [initialView, setInitialView] = useState14("wysiwyg");
4259
- const [initialSharedMode, setInitialSharedMode] = useState14(null);
4260
- const [showWelcomeGateway, setShowWelcomeGateway] = useState14(false);
4261
- const navigationRequestRef = useRef14(0);
5016
+ const [explorerKey, setExplorerKey] = useState15(0);
5017
+ const [documentLinkEpoch, setDocumentLinkEpoch] = useState15(0);
5018
+ const [initialView, setInitialView] = useState15("wysiwyg");
5019
+ const [initialSharedMode, setInitialSharedMode] = useState15(null);
5020
+ const [showWelcomeGateway, setShowWelcomeGateway] = useState15(false);
5021
+ const navigationRequestRef = useRef15(0);
4262
5022
  const workspaceAuthorityBarrier = useMemo3(() => new WorkspaceAuthorityBarrier(), []);
4263
- const preparedCloseRequestRef = useRef14(null);
4264
- const pendingDbkConflictsRef = useRef14(/* @__PURE__ */ new Map());
4265
- const createDocumentTarget = useCallback14(
5023
+ const preparedCloseRequestRef = useRef15(null);
5024
+ const pendingDbkConflictsRef = useRef15(/* @__PURE__ */ new Map());
5025
+ const createDocumentTarget = useCallback15(
4266
5026
  (fsProvider, workspaceId, filePath) => {
4267
5027
  const transient = getTransientWorkspace(workspaceId);
4268
5028
  const baseTarget = createFileSystemDocumentTarget(fsProvider, filePath);
@@ -4428,18 +5188,18 @@ function DocBlocksShell({
4428
5188
  },
4429
5189
  [gitScheduleRefresh]
4430
5190
  );
4431
- const mediaContainerRef = useRef14(null);
4432
- const [mediaProvider, setMediaProvider] = useState14(null);
4433
- const [mediaEpoch, setMediaEpoch] = useState14(0);
4434
- const versionsContainerRef = useRef14(null);
4435
- const [versionsContainer, setVersionsContainer] = useState14(null);
4436
- const pushHash = useCallback14((wsId, filePath) => {
5191
+ const mediaContainerRef = useRef15(null);
5192
+ const [mediaProvider, setMediaProvider] = useState15(null);
5193
+ const [mediaEpoch, setMediaEpoch] = useState15(0);
5194
+ const versionsContainerRef = useRef15(null);
5195
+ const [versionsContainer, setVersionsContainer] = useState15(null);
5196
+ const pushHash = useCallback15((wsId, filePath) => {
4437
5197
  const hash = buildHash(wsId, filePath);
4438
5198
  if (window.location.hash !== hash) {
4439
5199
  window.history.pushState(null, "", hash);
4440
5200
  }
4441
5201
  }, []);
4442
- const openFromIds = useCallback14(
5202
+ const openFromIds = useCallback15(
4443
5203
  async (wsId, filePath, push, view, navigationRequestId, sharedMode = null) => {
4444
5204
  const requestId = navigationRequestId ?? ++navigationRequestRef.current;
4445
5205
  if (requestId !== navigationRequestRef.current) return null;
@@ -4516,7 +5276,7 @@ function DocBlocksShell({
4516
5276
  },
4517
5277
  [createDocumentTarget, documentSession, pushHash]
4518
5278
  );
4519
- const adoptTransientWorkspace = useCallback14(
5279
+ const adoptTransientWorkspace = useCallback15(
4520
5280
  async (options) => {
4521
5281
  const descriptor = {
4522
5282
  id: options.id,
@@ -4545,7 +5305,7 @@ function DocBlocksShell({
4545
5305
  },
4546
5306
  [openFromIds]
4547
5307
  );
4548
- const openSharedDocument = useCallback14(
5308
+ const openSharedDocument = useCallback15(
4549
5309
  async (payload, navigationRequestId) => {
4550
5310
  const id = `transient-shared-${navigationRequestId}`;
4551
5311
  const mem = await createMemoryFileSystemProvider(id, "Shared document");
@@ -4573,7 +5333,7 @@ function DocBlocksShell({
4573
5333
  },
4574
5334
  [adoptTransientWorkspace]
4575
5335
  );
4576
- const seedWelcomeFile = useCallback14(
5336
+ const seedWelcomeFile = useCallback15(
4577
5337
  async (fs, navigationRequestId) => {
4578
5338
  const isCurrent = () => navigationRequestId === void 0 || navigationRequestId === navigationRequestRef.current;
4579
5339
  if (!isCurrent()) return;
@@ -4623,19 +5383,19 @@ function DocBlocksShell({
4623
5383
  },
4624
5384
  [createDocumentTarget, documentSession, pushHash]
4625
5385
  );
4626
- const startupOpenFromIdsRef = useRef14(openFromIds);
5386
+ const startupOpenFromIdsRef = useRef15(openFromIds);
4627
5387
  startupOpenFromIdsRef.current = openFromIds;
4628
- const startupOpenSharedDocumentRef = useRef14(openSharedDocument);
5388
+ const startupOpenSharedDocumentRef = useRef15(openSharedDocument);
4629
5389
  startupOpenSharedDocumentRef.current = openSharedDocument;
4630
- const startupSeedWelcomeFileRef = useRef14(seedWelcomeFile);
5390
+ const startupSeedWelcomeFileRef = useRef15(seedWelcomeFile);
4631
5391
  startupSeedWelcomeFileRef.current = seedWelcomeFile;
4632
- const closeWelcomeGateway = useCallback14(() => {
5392
+ const closeWelcomeGateway = useCallback15(() => {
4633
5393
  setShowWelcomeGateway((showing) => {
4634
5394
  if (showing) markWelcomeGatewayDismissed();
4635
5395
  return false;
4636
5396
  });
4637
5397
  }, []);
4638
- const handleStartWriting = useCallback14(() => {
5398
+ const handleStartWriting = useCallback15(() => {
4639
5399
  closeWelcomeGateway();
4640
5400
  setInitialView("wysiwyg");
4641
5401
  setInitialSharedMode(null);
@@ -4644,7 +5404,7 @@ function DocBlocksShell({
4644
5404
  saveLastState({ workspaceId: activeWorkspaceId, filePath: selectedFile, view: "wysiwyg" });
4645
5405
  }
4646
5406
  }, [closeWelcomeGateway, activeWorkspaceId, selectedFile]);
4647
- useEffect14(() => {
5407
+ useEffect15(() => {
4648
5408
  const requestId = ++navigationRequestRef.current;
4649
5409
  let cancelled = false;
4650
5410
  const isCurrent = () => !cancelled && requestId === navigationRequestRef.current;
@@ -4841,7 +5601,7 @@ function DocBlocksShell({
4841
5601
  if (navigationRequestRef.current === requestId) navigationRequestRef.current += 1;
4842
5602
  };
4843
5603
  }, [pushHash, workspaceAuthorityBarrier]);
4844
- useEffect14(() => {
5604
+ useEffect15(() => {
4845
5605
  const onPopState = () => {
4846
5606
  const requestedHash = window.location.hash;
4847
5607
  const restoreCurrentHash = () => {
@@ -4883,7 +5643,7 @@ function DocBlocksShell({
4883
5643
  window.addEventListener("popstate", onPopState);
4884
5644
  return () => window.removeEventListener("popstate", onPopState);
4885
5645
  }, [activeWorkspaceId, openFromIds, openSharedDocument, selectedFile, showToast]);
4886
- useEffect14(() => {
5646
+ useEffect15(() => {
4887
5647
  if (!showBrowserStorageWarning || typeof navigator === "undefined") return;
4888
5648
  const storage = navigator.storage;
4889
5649
  if (!storage || typeof storage.persisted !== "function") return;
@@ -4896,7 +5656,7 @@ function DocBlocksShell({
4896
5656
  cancelled = true;
4897
5657
  };
4898
5658
  }, [showBrowserStorageWarning]);
4899
- useEffect14(() => {
5659
+ useEffect15(() => {
4900
5660
  if (isElectronHost3() || typeof window === "undefined") return;
4901
5661
  const onBeforeInstallPrompt = (e) => {
4902
5662
  e.preventDefault();
@@ -4919,7 +5679,7 @@ function DocBlocksShell({
4919
5679
  window.removeEventListener("appinstalled", onAppInstalled);
4920
5680
  };
4921
5681
  }, []);
4922
- const handleInstallApp = useCallback14(async () => {
5682
+ const handleInstallApp = useCallback15(async () => {
4923
5683
  const promptEvent = installPromptEvent;
4924
5684
  setInstallPromptEvent(null);
4925
5685
  if (!promptEvent) return;
@@ -4929,7 +5689,7 @@ function DocBlocksShell({
4929
5689
  } catch {
4930
5690
  }
4931
5691
  }, [installPromptEvent]);
4932
- useEffect14(() => {
5692
+ useEffect15(() => {
4933
5693
  const handler = (e) => {
4934
5694
  const target = e.target.closest?.("[data-view]");
4935
5695
  if (target) {
@@ -4945,7 +5705,7 @@ function DocBlocksShell({
4945
5705
  window.addEventListener("click", handler, true);
4946
5706
  return () => window.removeEventListener("click", handler, true);
4947
5707
  }, [activeWorkspaceId, selectedFile, closeWelcomeGateway]);
4948
- useEffect14(() => {
5708
+ useEffect15(() => {
4949
5709
  const onKey = (e) => {
4950
5710
  const sKey = e.key === "s" || e.key === "S";
4951
5711
  const accel = e.ctrlKey || e.metaKey;
@@ -4967,30 +5727,30 @@ function DocBlocksShell({
4967
5727
  window.addEventListener("keydown", onKey, true);
4968
5728
  return () => window.removeEventListener("keydown", onKey, true);
4969
5729
  }, [documentSession, showToast]);
4970
- useEffect14(() => {
5730
+ useEffect15(() => {
4971
5731
  return () => {
4972
5732
  if (saveToastTimerRef.current) clearTimeout(saveToastTimerRef.current);
4973
5733
  };
4974
5734
  }, []);
4975
- const [offlineReadyToast, setOfflineReadyToast] = useState14(false);
4976
- const offlineReadyAnnouncedRef = useRef14(false);
4977
- useEffect14(() => {
5735
+ const [offlineReadyToast, setOfflineReadyToast] = useState15(false);
5736
+ const offlineReadyAnnouncedRef = useRef15(false);
5737
+ useEffect15(() => {
4978
5738
  if (!offlineReady || offlineReadyAnnouncedRef.current) return;
4979
5739
  offlineReadyAnnouncedRef.current = true;
4980
5740
  setOfflineReadyToast(true);
4981
5741
  const timer = setTimeout(() => setOfflineReadyToast(false), 3e3);
4982
5742
  return () => clearTimeout(timer);
4983
5743
  }, [offlineReady]);
4984
- const [storageFull, setStorageFull] = useState14(false);
4985
- useEffect14(() => {
5744
+ const [storageFull, setStorageFull] = useState15(false);
5745
+ useEffect15(() => {
4986
5746
  if (documentSnapshot.error && isQuotaExceededError(documentSnapshot.error)) {
4987
5747
  setStorageFull(true);
4988
5748
  } else if (documentSnapshot.status === "saved") {
4989
5749
  setStorageFull(false);
4990
5750
  }
4991
5751
  }, [documentSnapshot.error, documentSnapshot.status]);
4992
- const autoPersistRequestedRef = useRef14(false);
4993
- useEffect14(() => {
5752
+ const autoPersistRequestedRef = useRef15(false);
5753
+ useEffect15(() => {
4994
5754
  if (!showBrowserStorageWarning || browserStoragePersistent) return;
4995
5755
  if (autoPersistRequestedRef.current) return;
4996
5756
  if (documentSnapshot.status !== "saved") return;
@@ -5002,7 +5762,7 @@ function DocBlocksShell({
5002
5762
  }).catch(() => {
5003
5763
  });
5004
5764
  }, [showBrowserStorageWarning, browserStoragePersistent, documentSnapshot.status]);
5005
- useEffect14(() => {
5765
+ useEffect15(() => {
5006
5766
  if (!provider || !selectedFile) {
5007
5767
  mediaContainerRef.current = null;
5008
5768
  versionsContainerRef.current = null;
@@ -5026,7 +5786,7 @@ function DocBlocksShell({
5026
5786
  };
5027
5787
  }, [provider, selectedFile, mediaEpoch]);
5028
5788
  const documentLinkProvider = useDocumentLinkProvider(provider, selectedFile, documentLinkEpoch);
5029
- useEffect14(() => {
5789
+ useEffect15(() => {
5030
5790
  const ref = versioningRef;
5031
5791
  if (!ref) return;
5032
5792
  const assign = (mgr2) => {
@@ -5044,7 +5804,7 @@ function DocBlocksShell({
5044
5804
  assign(mgr);
5045
5805
  return () => assign(null);
5046
5806
  }, [versioningRef, effectiveVersioning, versionBasename, selectedFile, versionsContainer]);
5047
- useEffect14(() => {
5807
+ useEffect15(() => {
5048
5808
  if (!provider) return;
5049
5809
  const providerV2 = getFileSystemProviderV25(provider);
5050
5810
  if (!providerV2?.capabilities.watch) return;
@@ -5104,7 +5864,7 @@ function DocBlocksShell({
5104
5864
  void subscription.dispose();
5105
5865
  };
5106
5866
  }, [provider, selectedFile, documentSession, documentSnapshot.targetKey]);
5107
- const transitionAwayFromDocument = useCallback14(
5867
+ const transitionAwayFromDocument = useCallback15(
5108
5868
  async (requestId) => {
5109
5869
  if (requestId !== navigationRequestRef.current) return false;
5110
5870
  try {
@@ -5123,7 +5883,7 @@ function DocBlocksShell({
5123
5883
  },
5124
5884
  [documentSession, showToast]
5125
5885
  );
5126
- const handleUseExternalDocument = useCallback14(async () => {
5886
+ const handleUseExternalDocument = useCallback15(async () => {
5127
5887
  const conflictKey = documentSession.getSnapshot().conflict?.targetKey;
5128
5888
  const pendingDbk = conflictKey ? pendingDbkConflictsRef.current.get(conflictKey) : void 0;
5129
5889
  try {
@@ -5146,7 +5906,7 @@ function DocBlocksShell({
5146
5906
  });
5147
5907
  }
5148
5908
  }, [activeWorkspaceId, documentSession, pushHash]);
5149
- const handleKeepLocalDocument = useCallback14(async () => {
5909
+ const handleKeepLocalDocument = useCallback15(async () => {
5150
5910
  const conflictKey = documentSession.getSnapshot().conflict?.targetKey;
5151
5911
  try {
5152
5912
  await documentSession.resolveConflict("use-local");
@@ -5159,7 +5919,7 @@ function DocBlocksShell({
5159
5919
  });
5160
5920
  }
5161
5921
  }, [documentSession]);
5162
- useEffect14(() => {
5922
+ useEffect15(() => {
5163
5923
  const needsWarning = ["dirty", "saving", "error", "conflict"].includes(documentSnapshot.status);
5164
5924
  if (!documentSnapshot.targetKey) return;
5165
5925
  const flushBestEffort = () => {
@@ -5185,7 +5945,7 @@ function DocBlocksShell({
5185
5945
  document.removeEventListener("visibilitychange", onVisibilityChange);
5186
5946
  };
5187
5947
  }, [documentSession, documentSnapshot.status, documentSnapshot.targetKey]);
5188
- useEffect14(() => {
5948
+ useEffect15(() => {
5189
5949
  if (!isElectronHost3()) return;
5190
5950
  const lifecycle = getDocBlocksHost().lifecycle;
5191
5951
  const stopPrepare = lifecycle.onPrepareClose(async (request) => {
@@ -5212,7 +5972,7 @@ function DocBlocksShell({
5212
5972
  stopCancel();
5213
5973
  };
5214
5974
  }, [documentSession]);
5215
- const handleWorkspaceSelect = useCallback14(
5975
+ const handleWorkspaceSelect = useCallback15(
5216
5976
  async (ws) => {
5217
5977
  const requestId = ++navigationRequestRef.current;
5218
5978
  await touchWorkspace2(ws.id);
@@ -5244,7 +6004,184 @@ function DocBlocksShell({
5244
6004
  },
5245
6005
  [pushHash, transitionAwayFromDocument]
5246
6006
  );
5247
- const handleMoveTransientWorkspace = useCallback14(
6007
+ const unpinDocument = useCallback15((document2) => {
6008
+ const key = pinnedDocumentKey(document2);
6009
+ setPinnedDocuments((current) => removePinnedDocument(current, document2));
6010
+ setPinnedDocumentAvailability((current) => {
6011
+ if (!current.has(key)) return current;
6012
+ const next = new Map(current);
6013
+ next.delete(key);
6014
+ return next;
6015
+ });
6016
+ }, []);
6017
+ const confirmMissingPinnedDocument = useCallback15(
6018
+ async (document2) => {
6019
+ setPinnedAvailability(document2, "missing");
6020
+ const shouldUnpin = await confirmAction({
6021
+ title: "File not found",
6022
+ message: missingPinnedDocumentMessage(document2),
6023
+ confirmLabel: "OK",
6024
+ cancelLabel: "Cancel"
6025
+ });
6026
+ if (shouldUnpin) unpinDocument(document2);
6027
+ },
6028
+ [confirmAction, setPinnedAvailability, unpinDocument]
6029
+ );
6030
+ const handleTogglePin = useCallback15(
6031
+ (path) => {
6032
+ if (!activeWorkspaceId || !provider) return;
6033
+ const canonicalPath = parseWorkspacePath5(path);
6034
+ if (!canonicalPath) return;
6035
+ const document2 = {
6036
+ workspaceId: activeWorkspaceId,
6037
+ workspaceName: activeWorkspaceDescriptor?.name ?? provider.label,
6038
+ path: canonicalPath
6039
+ };
6040
+ const alreadyPinned = pinnedDocuments.some(
6041
+ (candidate) => pinnedDocumentKey(candidate) === pinnedDocumentKey(document2)
6042
+ );
6043
+ setPinnedDocuments((current) => togglePinnedDocument(current, document2));
6044
+ if (alreadyPinned) {
6045
+ setPinnedDocumentAvailability((current) => {
6046
+ const key = pinnedDocumentKey(document2);
6047
+ if (!current.has(key)) return current;
6048
+ const next = new Map(current);
6049
+ next.delete(key);
6050
+ return next;
6051
+ });
6052
+ } else {
6053
+ setPinnedAvailability(document2, "available");
6054
+ }
6055
+ },
6056
+ [
6057
+ activeWorkspaceDescriptor?.name,
6058
+ activeWorkspaceId,
6059
+ pinnedDocuments,
6060
+ provider,
6061
+ setPinnedAvailability
6062
+ ]
6063
+ );
6064
+ const activeWorkspacePinnedPaths = useMemo3(
6065
+ () => pinnedDocuments.filter((document2) => document2.workspaceId === activeWorkspaceId).map((document2) => document2.path),
6066
+ [activeWorkspaceId, pinnedDocuments]
6067
+ );
6068
+ const handlePinnedDocumentSelect = useCallback15(
6069
+ async (document2) => {
6070
+ const requestId = ++navigationRequestRef.current;
6071
+ const workspace = await getWorkspace(document2.workspaceId);
6072
+ if (requestId !== navigationRequestRef.current) return;
6073
+ if (!workspace) {
6074
+ await confirmMissingPinnedDocument(document2);
6075
+ return;
6076
+ }
6077
+ let nextProvider = activeWorkspaceId === workspace.id ? provider : null;
6078
+ let ownsNextProvider = false;
6079
+ let adoptedNextProvider = nextProvider !== null;
6080
+ try {
6081
+ if (!nextProvider) {
6082
+ const transient = getTransientWorkspace(workspace.id);
6083
+ if (transient) {
6084
+ nextProvider = transient.provider;
6085
+ } else if (workspace.type === "electron-native") {
6086
+ nextProvider = isElectronHost3() ? await createElectronProviderFromWorkspace(workspace) : null;
6087
+ ownsNextProvider = nextProvider !== null;
6088
+ } else if (workspace.type === "native") {
6089
+ nextProvider = await (await loadNativeFileSystem()).restoreNativeFolder(workspace.id);
6090
+ ownsNextProvider = nextProvider !== null;
6091
+ } else {
6092
+ nextProvider = await createIndexedDbFileSystemProvider(workspace.id, workspace.name);
6093
+ ownsNextProvider = true;
6094
+ }
6095
+ }
6096
+ if (requestId !== navigationRequestRef.current) return;
6097
+ if (!nextProvider) {
6098
+ setPinnedAvailability(document2, "unknown");
6099
+ showToast(
6100
+ "error",
6101
+ `DocBlocks could not open \u201C${workspace.name}\u201D. Open that workspace and grant access, then try again.`
6102
+ );
6103
+ return;
6104
+ }
6105
+ const openedProvider = nextProvider;
6106
+ let exists;
6107
+ try {
6108
+ exists = await providerEntryExists(openedProvider, document2.path);
6109
+ } catch (error) {
6110
+ setPinnedAvailability(document2, "unknown");
6111
+ showToast(
6112
+ "error",
6113
+ error instanceof Error ? error.message : "Could not check the pinned document."
6114
+ );
6115
+ return;
6116
+ }
6117
+ if (requestId !== navigationRequestRef.current) return;
6118
+ if (!exists) {
6119
+ await confirmMissingPinnedDocument(document2);
6120
+ return;
6121
+ }
6122
+ let disappearedDuringRead = false;
6123
+ const transitioned = await documentSession.transitionWithLoad(async () => {
6124
+ if (requestId !== navigationRequestRef.current) return null;
6125
+ const content = await readProviderText(openedProvider, document2.path);
6126
+ if (content === null) {
6127
+ disappearedDuringRead = true;
6128
+ return null;
6129
+ }
6130
+ if (requestId !== navigationRequestRef.current) return null;
6131
+ return {
6132
+ target: createDocumentTarget(openedProvider, workspace.id, document2.path),
6133
+ content
6134
+ };
6135
+ });
6136
+ if (!transitioned) {
6137
+ if (disappearedDuringRead) await confirmMissingPinnedDocument(document2);
6138
+ return;
6139
+ }
6140
+ if (requestId !== navigationRequestRef.current) return;
6141
+ adoptedNextProvider = true;
6142
+ setProvider(openedProvider);
6143
+ setActiveWorkspaceId(workspace.id);
6144
+ setActiveWorkspaceDescriptor(workspace);
6145
+ setSelectedFile(document2.path);
6146
+ setSelectedFolder(null);
6147
+ setFolderEntries([]);
6148
+ setInitialView("wysiwyg");
6149
+ setInitialSharedMode(null);
6150
+ setExplorerKey((key) => key + 1);
6151
+ setPinnedAvailability(document2, "available");
6152
+ closeWelcomeGateway();
6153
+ pushHash(workspace.id, document2.path);
6154
+ saveLastState({ workspaceId: workspace.id, filePath: document2.path, view: "wysiwyg" });
6155
+ if (effectiveCompact) setMobileShowEditor(true);
6156
+ void touchWorkspace2(workspace.id).catch(() => void 0);
6157
+ } catch (error) {
6158
+ showToast(
6159
+ "error",
6160
+ error instanceof Error ? error.message : "Could not open the pinned document."
6161
+ );
6162
+ } finally {
6163
+ if (nextProvider && ownsNextProvider && !adoptedNextProvider) {
6164
+ try {
6165
+ await getFileSystemProviderV25(nextProvider)?.dispose();
6166
+ } catch {
6167
+ }
6168
+ }
6169
+ }
6170
+ },
6171
+ [
6172
+ activeWorkspaceId,
6173
+ closeWelcomeGateway,
6174
+ confirmMissingPinnedDocument,
6175
+ createDocumentTarget,
6176
+ documentSession,
6177
+ effectiveCompact,
6178
+ provider,
6179
+ pushHash,
6180
+ setPinnedAvailability,
6181
+ showToast
6182
+ ]
6183
+ );
6184
+ const handleMoveTransientWorkspace = useCallback15(
5248
6185
  async (destinationWorkspaceId) => {
5249
6186
  if (!activeWorkspaceId || !provider || !selectedFile) {
5250
6187
  throw new Error("Select the temporary document before moving it.");
@@ -5300,6 +6237,12 @@ function DocBlocksShell({
5300
6237
  setActiveWorkspaceId(destination.id);
5301
6238
  setActiveWorkspaceDescriptor(destination);
5302
6239
  setSelectedFile(selectedFile);
6240
+ setPinnedDocuments(
6241
+ (current) => movePinnedDocumentsToWorkspace(current, activeWorkspaceId, {
6242
+ workspaceId: destination.id,
6243
+ workspaceName: destination.name
6244
+ })
6245
+ );
5303
6246
  setSelectedFolder(null);
5304
6247
  setFolderEntries([]);
5305
6248
  setInitialView("wysiwyg");
@@ -5350,7 +6293,7 @@ function DocBlocksShell({
5350
6293
  transientMoveDestinations
5351
6294
  ]
5352
6295
  );
5353
- const handleOpenFolder = useCallback14(async () => {
6296
+ const handleOpenFolder = useCallback15(async () => {
5354
6297
  const requestId = ++navigationRequestRef.current;
5355
6298
  try {
5356
6299
  if (isElectronHost3()) {
@@ -5396,7 +6339,7 @@ function DocBlocksShell({
5396
6339
  } catch {
5397
6340
  }
5398
6341
  }, [pushHash, transitionAwayFromDocument]);
5399
- const handleWorkspaceCloned = useCallback14(
6342
+ const handleWorkspaceCloned = useCallback15(
5400
6343
  (info) => {
5401
6344
  const requestId = ++navigationRequestRef.current;
5402
6345
  void (async () => {
@@ -5425,7 +6368,7 @@ function DocBlocksShell({
5425
6368
  },
5426
6369
  [pushHash, transitionAwayFromDocument]
5427
6370
  );
5428
- const handleNewFile = useCallback14(async () => {
6371
+ const handleNewFile = useCallback15(async () => {
5429
6372
  if (!provider) return;
5430
6373
  const name = await promptForText({
5431
6374
  title: "New document",
@@ -5491,7 +6434,7 @@ function DocBlocksShell({
5491
6434
  promptForText,
5492
6435
  showToast
5493
6436
  ]);
5494
- const handleNewFolder = useCallback14(async () => {
6437
+ const handleNewFolder = useCallback15(async () => {
5495
6438
  if (!provider) return;
5496
6439
  const name = await promptForText({
5497
6440
  title: "New folder",
@@ -5537,8 +6480,8 @@ function DocBlocksShell({
5537
6480
  pushHash,
5538
6481
  effectiveCompact
5539
6482
  ]);
5540
- const actionNewHandledRef = useRef14(false);
5541
- useEffect14(() => {
6483
+ const actionNewHandledRef = useRef15(false);
6484
+ useEffect15(() => {
5542
6485
  if (!provider || actionNewHandledRef.current) return;
5543
6486
  if (typeof window === "undefined") return;
5544
6487
  const params = new URLSearchParams(window.location.search);
@@ -5553,14 +6496,14 @@ function DocBlocksShell({
5553
6496
  );
5554
6497
  void handleNewFile();
5555
6498
  }, [provider, handleNewFile]);
5556
- const handleRevealWorkspace = useCallback14(async () => {
6499
+ const handleRevealWorkspace = useCallback15(async () => {
5557
6500
  if (!isElectronHost3() || !activeWorkspaceId) return;
5558
6501
  const ws = await getWorkspace(activeWorkspaceId);
5559
6502
  if (ws?.type === "electron-native" && ws.rootPath) {
5560
6503
  await getDocBlocksHost().shell.revealInFolder(ws.id);
5561
6504
  }
5562
6505
  }, [activeWorkspaceId]);
5563
- useEffect14(() => {
6506
+ useEffect15(() => {
5564
6507
  if (!isElectronHost3()) return;
5565
6508
  const host = getDocBlocksHost();
5566
6509
  return host.onMenuCommand((cmd) => {
@@ -5624,7 +6567,7 @@ function DocBlocksShell({
5624
6567
  }
5625
6568
  });
5626
6569
  }, [handleNewFile, handleOpenFolder, handleRevealWorkspace, acknowledge]);
5627
- const handleSelect = useCallback14(
6570
+ const handleSelect = useCallback15(
5628
6571
  async (path, kind) => {
5629
6572
  if (!provider || !activeWorkspaceId) return;
5630
6573
  const requestId = ++navigationRequestRef.current;
@@ -5681,7 +6624,7 @@ function DocBlocksShell({
5681
6624
  showToast
5682
6625
  ]
5683
6626
  );
5684
- const handleEditorLinkClick = useCallback14(
6627
+ const handleEditorLinkClick = useCallback15(
5685
6628
  (href) => {
5686
6629
  const target = resolveShellEditorLinkTarget(href, selectedFile);
5687
6630
  if (!target) {
@@ -5731,7 +6674,7 @@ function DocBlocksShell({
5731
6674
  },
5732
6675
  [handleSelect, provider, selectedFile, showToast]
5733
6676
  );
5734
- const handleTreeMutation = useCallback14(
6677
+ const handleTreeMutation = useCallback15(
5735
6678
  async (change, mutate) => {
5736
6679
  if (!provider || !activeWorkspaceId || !selectedFile) {
5737
6680
  await mutate();
@@ -5755,11 +6698,16 @@ function DocBlocksShell({
5755
6698
  },
5756
6699
  [provider, activeWorkspaceId, selectedFile, documentSession, createDocumentTarget]
5757
6700
  );
5758
- const handleTreeChange = useCallback14(
6701
+ const handleTreeChange = useCallback15(
5759
6702
  async (change) => {
5760
6703
  if (!provider) return;
5761
6704
  setDocumentLinkEpoch((epoch) => epoch + 1);
5762
6705
  if (change?.type === "move" && change.oldPath && change.newPath) {
6706
+ if (activeWorkspaceId) {
6707
+ setPinnedDocuments(
6708
+ (current) => relocatePinnedDocuments(current, activeWorkspaceId, change.oldPath, change.newPath)
6709
+ );
6710
+ }
5763
6711
  const nextFile = selectedFile ? relocateProviderPath(selectedFile, change.oldPath, change.newPath) : null;
5764
6712
  const nextFolder = selectedFolder ? relocateProviderPath(selectedFolder, change.oldPath, change.newPath) : null;
5765
6713
  if (nextFile !== selectedFile) {
@@ -5779,6 +6727,13 @@ function DocBlocksShell({
5779
6727
  if (nextFolder) setFolderEntries(await readProviderDirectory2(provider, nextFolder));
5780
6728
  return;
5781
6729
  }
6730
+ if (change?.type === "delete" && activeWorkspaceId) {
6731
+ for (const document2 of pinnedDocuments) {
6732
+ if (document2.workspaceId === activeWorkspaceId && pathContains(change.path, document2.path)) {
6733
+ setPinnedAvailability(document2, "missing");
6734
+ }
6735
+ }
6736
+ }
5782
6737
  if (selectedFile) {
5783
6738
  const exists = await providerEntryExists(provider, selectedFile);
5784
6739
  if (!exists) {
@@ -5791,9 +6746,210 @@ function DocBlocksShell({
5791
6746
  setFolderEntries(entries);
5792
6747
  }
5793
6748
  },
5794
- [provider, selectedFile, selectedFolder, activeWorkspaceId, pushHash]
6749
+ [
6750
+ provider,
6751
+ selectedFile,
6752
+ selectedFolder,
6753
+ activeWorkspaceId,
6754
+ pinnedDocuments,
6755
+ pushHash,
6756
+ setPinnedAvailability
6757
+ ]
6758
+ );
6759
+ const acquirePinnedDocumentProvider = useCallback15(
6760
+ async (document2) => {
6761
+ const workspace = await getWorkspace(document2.workspaceId);
6762
+ if (!workspace) return { kind: "missing-workspace" };
6763
+ if (activeWorkspaceId === workspace.id && provider) {
6764
+ return { kind: "ready", workspace, provider, owned: false };
6765
+ }
6766
+ const transient = getTransientWorkspace(workspace.id);
6767
+ if (transient) {
6768
+ return { kind: "ready", workspace, provider: transient.provider, owned: false };
6769
+ }
6770
+ let pinnedProvider = null;
6771
+ if (workspace.type === "electron-native") {
6772
+ pinnedProvider = isElectronHost3() ? await createElectronProviderFromWorkspace(workspace) : null;
6773
+ } else if (workspace.type === "native") {
6774
+ pinnedProvider = await (await loadNativeFileSystem()).restoreNativeFolder(workspace.id);
6775
+ } else {
6776
+ pinnedProvider = await createIndexedDbFileSystemProvider(workspace.id, workspace.name);
6777
+ }
6778
+ if (!pinnedProvider) return { kind: "unavailable", workspace };
6779
+ return { kind: "ready", workspace, provider: pinnedProvider, owned: true };
6780
+ },
6781
+ [activeWorkspaceId, provider]
6782
+ );
6783
+ const markRelocatedPinnedDocument = useCallback15((document2, newPath) => {
6784
+ const relocated = {
6785
+ workspaceId: document2.workspaceId,
6786
+ workspaceName: document2.workspaceName,
6787
+ path: newPath
6788
+ };
6789
+ setPinnedDocuments(
6790
+ (current) => relocatePinnedDocuments(current, document2.workspaceId, document2.path, newPath)
6791
+ );
6792
+ setPinnedDocumentAvailability((current) => {
6793
+ const oldKey = pinnedDocumentKey(document2);
6794
+ const nextKey = pinnedDocumentKey(relocated);
6795
+ const next = new Map(current);
6796
+ next.delete(oldKey);
6797
+ next.set(nextKey, "available");
6798
+ return next;
6799
+ });
6800
+ }, []);
6801
+ const handlePinnedDocumentRename = useCallback15(
6802
+ async (document2) => {
6803
+ if (document2.availability === "missing") {
6804
+ await confirmMissingPinnedDocument(document2);
6805
+ return;
6806
+ }
6807
+ const oldName = basenameOf(document2.path);
6808
+ const response = await promptForText({
6809
+ title: "Rename document",
6810
+ label: "Document name",
6811
+ initialValue: oldName,
6812
+ confirmLabel: "Rename"
6813
+ });
6814
+ if (response === null) return;
6815
+ const newName = response.trim();
6816
+ if (!newName || /[\\/]/.test(newName)) {
6817
+ showToast("error", "Use a document name without slashes.");
6818
+ return;
6819
+ }
6820
+ const parent = dirnameOf(document2.path);
6821
+ let newPath;
6822
+ try {
6823
+ newPath = parseWorkspacePath5(parent ? `${parent}/${newName}` : newName);
6824
+ } catch {
6825
+ showToast("error", "Use a valid document name.");
6826
+ return;
6827
+ }
6828
+ if (newPath === document2.path) return;
6829
+ const access = await acquirePinnedDocumentProvider(document2);
6830
+ if (access.kind === "missing-workspace") {
6831
+ await confirmMissingPinnedDocument(document2);
6832
+ return;
6833
+ }
6834
+ if (access.kind === "unavailable") {
6835
+ setPinnedAvailability(document2, "unknown");
6836
+ showToast(
6837
+ "error",
6838
+ `DocBlocks could not open "${access.workspace.name}". Open that workspace and grant access, then try again.`
6839
+ );
6840
+ return;
6841
+ }
6842
+ try {
6843
+ if (!await pinnedProviderFileExists(access.provider, document2.path)) {
6844
+ await confirmMissingPinnedDocument(document2);
6845
+ return;
6846
+ }
6847
+ const change = {
6848
+ type: "move",
6849
+ oldPath: document2.path,
6850
+ newPath,
6851
+ kind: "file"
6852
+ };
6853
+ const mutate = () => moveFileSystemEntry2(access.provider, document2.path, newPath, "file");
6854
+ if (access.provider === provider && access.workspace.id === activeWorkspaceId) {
6855
+ await handleTreeMutation(change, mutate);
6856
+ await handleTreeChange(change);
6857
+ setExplorerKey((key) => key + 1);
6858
+ } else {
6859
+ await mutate();
6860
+ }
6861
+ markRelocatedPinnedDocument(document2, newPath);
6862
+ } finally {
6863
+ if (access.owned) {
6864
+ try {
6865
+ await getFileSystemProviderV25(access.provider)?.dispose();
6866
+ } catch {
6867
+ }
6868
+ }
6869
+ }
6870
+ },
6871
+ [
6872
+ acquirePinnedDocumentProvider,
6873
+ activeWorkspaceId,
6874
+ confirmMissingPinnedDocument,
6875
+ handleTreeChange,
6876
+ handleTreeMutation,
6877
+ markRelocatedPinnedDocument,
6878
+ promptForText,
6879
+ provider,
6880
+ setPinnedAvailability,
6881
+ showToast
6882
+ ]
6883
+ );
6884
+ const handlePinnedDocumentDelete = useCallback15(
6885
+ async (document2) => {
6886
+ if (document2.availability === "missing") {
6887
+ await confirmMissingPinnedDocument(document2);
6888
+ return;
6889
+ }
6890
+ const access = await acquirePinnedDocumentProvider(document2);
6891
+ if (access.kind === "missing-workspace") {
6892
+ await confirmMissingPinnedDocument(document2);
6893
+ return;
6894
+ }
6895
+ if (access.kind === "unavailable") {
6896
+ setPinnedAvailability(document2, "unknown");
6897
+ showToast(
6898
+ "error",
6899
+ `DocBlocks could not open "${access.workspace.name}". Open that workspace and grant access, then try again.`
6900
+ );
6901
+ return;
6902
+ }
6903
+ try {
6904
+ if (!await pinnedProviderFileExists(access.provider, document2.path)) {
6905
+ await confirmMissingPinnedDocument(document2);
6906
+ return;
6907
+ }
6908
+ if (!await confirmDeleteEntry(
6909
+ `Delete the document "${basenameOf(document2.path)}"? This cannot be undone.`
6910
+ )) {
6911
+ return;
6912
+ }
6913
+ const change = {
6914
+ type: "delete",
6915
+ path: document2.path,
6916
+ kind: "file"
6917
+ };
6918
+ const mutate = async () => {
6919
+ if (!await removePinnedProviderFile(access.provider, document2.path)) {
6920
+ throw new Error("The document disappeared before it could be deleted.");
6921
+ }
6922
+ };
6923
+ if (access.provider === provider && access.workspace.id === activeWorkspaceId) {
6924
+ await handleTreeMutation(change, mutate);
6925
+ await handleTreeChange(change);
6926
+ setExplorerKey((key) => key + 1);
6927
+ } else {
6928
+ await mutate();
6929
+ }
6930
+ setPinnedAvailability(document2, "missing");
6931
+ } finally {
6932
+ if (access.owned) {
6933
+ try {
6934
+ await getFileSystemProviderV25(access.provider)?.dispose();
6935
+ } catch {
6936
+ }
6937
+ }
6938
+ }
6939
+ },
6940
+ [
6941
+ acquirePinnedDocumentProvider,
6942
+ activeWorkspaceId,
6943
+ confirmDeleteEntry,
6944
+ confirmMissingPinnedDocument,
6945
+ handleTreeChange,
6946
+ handleTreeMutation,
6947
+ provider,
6948
+ setPinnedAvailability,
6949
+ showToast
6950
+ ]
5795
6951
  );
5796
- const persistImportedMedia = useCallback14(
6952
+ const persistImportedMedia = useCallback15(
5797
6953
  async (source, target, importedMarkdownPath) => {
5798
6954
  const parentDir = dirnameOf(importedMarkdownPath);
5799
6955
  const folder = basenameOf(importedMarkdownPath).replace(/\.[^.]+$/, "") + "_files";
@@ -5818,7 +6974,7 @@ function DocBlocksShell({
5818
6974
  },
5819
6975
  []
5820
6976
  );
5821
- const handleImportFiles = useCallback14(
6977
+ const handleImportFiles = useCallback15(
5822
6978
  async (files) => {
5823
6979
  if (!provider) return;
5824
6980
  const result = await importDroppedFiles(files, provider, {
@@ -5830,7 +6986,7 @@ function DocBlocksShell({
5830
6986
  },
5831
6987
  [provider, persistImportedMedia, showToast]
5832
6988
  );
5833
- const handleEditorChange = useCallback14(
6989
+ const handleEditorChange = useCallback15(
5834
6990
  (source) => {
5835
6991
  if (!editorSessionScope) return;
5836
6992
  try {
@@ -5840,7 +6996,7 @@ function DocBlocksShell({
5840
6996
  },
5841
6997
  [documentSession, editorSessionScope]
5842
6998
  );
5843
- const handleRenameWorkspace = useCallback14(async () => {
6999
+ const handleRenameWorkspace = useCallback15(async () => {
5844
7000
  if (!activeWorkspaceId) return;
5845
7001
  const ws = await getWorkspace(activeWorkspaceId);
5846
7002
  if (!ws) return;
@@ -5852,9 +7008,12 @@ function DocBlocksShell({
5852
7008
  });
5853
7009
  if (!newName || newName === ws.name) return;
5854
7010
  await saveWorkspace2({ ...ws, name: newName });
7011
+ setPinnedDocuments(
7012
+ (current) => renamePinnedDocumentWorkspace(current, activeWorkspaceId, newName)
7013
+ );
5855
7014
  setDescriptorRefreshKey((key) => key + 1);
5856
7015
  }, [activeWorkspaceId, promptForText]);
5857
- const openTransient = useCallback14(
7016
+ const openTransient = useCallback15(
5858
7017
  async (req, navigationRequestId) => {
5859
7018
  const isCurrent = () => navigationRequestId === navigationRequestRef.current;
5860
7019
  const host = getDocBlocksHost();
@@ -5929,7 +7088,7 @@ function DocBlocksShell({
5929
7088
  },
5930
7089
  [adoptTransientWorkspace]
5931
7090
  );
5932
- useEffect14(() => {
7091
+ useEffect15(() => {
5933
7092
  if (!isElectronHost3()) return;
5934
7093
  const host = getDocBlocksHost();
5935
7094
  return host.onOpenRequest((req) => {
@@ -5953,7 +7112,7 @@ function DocBlocksShell({
5953
7112
  });
5954
7113
  });
5955
7114
  }, [openFromIds, openTransient, workspaceAuthorityBarrier, showToast]);
5956
- const openTransientFromHandle = useCallback14(
7115
+ const openTransientFromHandle = useCallback15(
5957
7116
  async (handle) => {
5958
7117
  const name = handle.name;
5959
7118
  const isBundle = /\.(dbk|zip)$/i.test(name);
@@ -5995,7 +7154,7 @@ function DocBlocksShell({
5995
7154
  },
5996
7155
  [adoptTransientWorkspace]
5997
7156
  );
5998
- useEffect14(() => {
7157
+ useEffect15(() => {
5999
7158
  if (isElectronHost3() || typeof window === "undefined") return;
6000
7159
  const launchQueue = window.launchQueue;
6001
7160
  if (!launchQueue) return;
@@ -6011,7 +7170,7 @@ function DocBlocksShell({
6011
7170
  });
6012
7171
  }, [openTransientFromHandle, showToast]);
6013
7172
  const updateStatusBarVisible = viewPreferences.showStatusBar === true && selectedFile !== null && mediaProvider !== null;
6014
- const handleDownloadWorkspace = useCallback14(async () => {
7173
+ const handleDownloadWorkspace = useCallback15(async () => {
6015
7174
  if (!provider) return;
6016
7175
  try {
6017
7176
  await documentSession.flush("backup");
@@ -6034,7 +7193,7 @@ function DocBlocksShell({
6034
7193
  showToast("error", "Failed to download workspace. See console for details.");
6035
7194
  }
6036
7195
  }, [provider, documentSession, showToast]);
6037
- const handleDownloadAllWorkspaces = useCallback14(async () => {
7196
+ const handleDownloadAllWorkspaces = useCallback15(async () => {
6038
7197
  try {
6039
7198
  await documentSession.flush("backup");
6040
7199
  const [{ MemoryContentContainer }, { containerToZip }] = await Promise.all([
@@ -6113,7 +7272,7 @@ function DocBlocksShell({
6113
7272
  showToast("error", "Failed to download all workspaces. See console for details.");
6114
7273
  }
6115
7274
  }, [documentSession, acknowledge, showToast]);
6116
- const handleKeepBrowserData = useCallback14(async () => {
7275
+ const handleKeepBrowserData = useCallback15(async () => {
6117
7276
  if (typeof navigator === "undefined") return;
6118
7277
  const storage = navigator.storage;
6119
7278
  if (!storage || typeof storage.persist !== "function") {
@@ -6150,7 +7309,7 @@ function DocBlocksShell({
6150
7309
  });
6151
7310
  }
6152
7311
  }, [acknowledge, showToast]);
6153
- const getBrowserStorageEstimate = useCallback14(async () => {
7312
+ const getBrowserStorageEstimate = useCallback15(async () => {
6154
7313
  if (typeof navigator === "undefined") return null;
6155
7314
  const storage = navigator.storage;
6156
7315
  if (!storage || typeof storage.estimate !== "function") return null;
@@ -6162,7 +7321,7 @@ function DocBlocksShell({
6162
7321
  return null;
6163
7322
  }
6164
7323
  }, []);
6165
- const handleRemoveWorkspace = useCallback14(async () => {
7324
+ const handleRemoveWorkspace = useCallback15(async () => {
6166
7325
  if (!activeWorkspaceId) return;
6167
7326
  const ws = await getWorkspace(activeWorkspaceId);
6168
7327
  const destroysDocuments = ws?.type === "indexeddb";
@@ -6195,6 +7354,11 @@ function DocBlocksShell({
6195
7354
  await (await loadIndexedDbFileSystem()).deleteIndexedDBWorkspaceData(activeWorkspaceId);
6196
7355
  }
6197
7356
  await removeWorkspace(activeWorkspaceId);
7357
+ for (const document2 of pinnedDocuments) {
7358
+ if (document2.workspaceId === activeWorkspaceId) {
7359
+ setPinnedAvailability(document2, "missing");
7360
+ }
7361
+ }
6198
7362
  if (requestId !== navigationRequestRef.current) return;
6199
7363
  const electron = isElectronHost3();
6200
7364
  const remaining = (await listWorkspaces2()).filter(
@@ -6228,19 +7392,26 @@ function DocBlocksShell({
6228
7392
  setSelectedFolder(null);
6229
7393
  setFolderEntries([]);
6230
7394
  }
6231
- }, [activeWorkspaceId, handleWorkspaceSelect, transitionAwayFromDocument, confirmAction]);
6232
- return /* @__PURE__ */ jsx12(
7395
+ }, [
7396
+ activeWorkspaceId,
7397
+ confirmAction,
7398
+ handleWorkspaceSelect,
7399
+ pinnedDocuments,
7400
+ setPinnedAvailability,
7401
+ transitionAwayFromDocument
7402
+ ]);
7403
+ return /* @__PURE__ */ jsx13(
6233
7404
  "div",
6234
7405
  {
6235
7406
  className: `db-shell${effectiveCompact ? " db-shell--mobile" : ""}`,
6236
7407
  "data-theme": resolvedTheme,
6237
7408
  "data-accent": accentColor,
6238
7409
  "data-document-status": documentSnapshot.status,
6239
- children: /* @__PURE__ */ jsxs11(GitContext.Provider, { value: git, children: [
7410
+ children: /* @__PURE__ */ jsxs12(GitContext.Provider, { value: git, children: [
6240
7411
  promptDialog,
6241
7412
  confirmDialog,
6242
- workspaceStartupError && /* @__PURE__ */ jsx12("div", { className: "db-save-toast db-save-toast--error", role: "alert", "aria-live": "assertive", children: workspaceStartupError }),
6243
- saveToast && /* @__PURE__ */ jsx12(
7413
+ workspaceStartupError && /* @__PURE__ */ jsx13("div", { className: "db-save-toast db-save-toast--error", role: "alert", "aria-live": "assertive", children: workspaceStartupError }),
7414
+ saveToast && /* @__PURE__ */ jsx13(
6244
7415
  "div",
6245
7416
  {
6246
7417
  className: "db-save-toast db-save-toast--" + saveToast.kind,
@@ -6249,22 +7420,22 @@ function DocBlocksShell({
6249
7420
  children: saveToast.message
6250
7421
  }
6251
7422
  ),
6252
- offlineReadyToast && !saveToast && /* @__PURE__ */ jsx12("div", { className: "db-save-toast db-save-toast--success", role: "status", "aria-live": "polite", children: "DocBlocks is ready to work offline." }),
6253
- documentSnapshot.conflict && /* @__PURE__ */ jsxs11("div", { className: "db-document-conflict", role: "alert", children: [
6254
- /* @__PURE__ */ jsx12("span", { children: "This document changed outside DocBlocks. Your unsaved version is still intact." }),
6255
- /* @__PURE__ */ jsxs11("div", { className: "db-document-conflict-actions", children: [
6256
- /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => void handleKeepLocalDocument(), children: "Keep mine" }),
6257
- /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => void handleUseExternalDocument(), children: "Reload external" })
7423
+ offlineReadyToast && !saveToast && /* @__PURE__ */ jsx13("div", { className: "db-save-toast db-save-toast--success", role: "status", "aria-live": "polite", children: "DocBlocks is ready to work offline." }),
7424
+ documentSnapshot.conflict && /* @__PURE__ */ jsxs12("div", { className: "db-document-conflict", role: "alert", children: [
7425
+ /* @__PURE__ */ jsx13("span", { children: "This document changed outside DocBlocks. Your unsaved version is still intact." }),
7426
+ /* @__PURE__ */ jsxs12("div", { className: "db-document-conflict-actions", children: [
7427
+ /* @__PURE__ */ jsx13("button", { type: "button", onClick: () => void handleKeepLocalDocument(), children: "Keep mine" }),
7428
+ /* @__PURE__ */ jsx13("button", { type: "button", onClick: () => void handleUseExternalDocument(), children: "Reload external" })
6258
7429
  ] })
6259
7430
  ] }),
6260
- storageFull && !documentSnapshot.conflict && /* @__PURE__ */ jsxs11("div", { className: "db-storage-full-banner", role: "alert", children: [
6261
- /* @__PURE__ */ jsx12("span", { children: "Browser storage is full -- changes can\u2019t be saved. Free up space or back up your work now." }),
6262
- /* @__PURE__ */ jsxs11("div", { className: "db-storage-full-banner-actions", children: [
6263
- /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => void handleDownloadAllWorkspaces(), children: "Download all workspaces" }),
6264
- /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => setStorageFull(false), children: "Dismiss" })
7431
+ storageFull && !documentSnapshot.conflict && /* @__PURE__ */ jsxs12("div", { className: "db-storage-full-banner", role: "alert", children: [
7432
+ /* @__PURE__ */ jsx13("span", { children: "Browser storage is full -- changes can\u2019t be saved. Free up space or back up your work now." }),
7433
+ /* @__PURE__ */ jsxs12("div", { className: "db-storage-full-banner-actions", children: [
7434
+ /* @__PURE__ */ jsx13("button", { type: "button", onClick: () => void handleDownloadAllWorkspaces(), children: "Download all workspaces" }),
7435
+ /* @__PURE__ */ jsx13("button", { type: "button", onClick: () => setStorageFull(false), children: "Dismiss" })
6265
7436
  ] })
6266
7437
  ] }),
6267
- workspaceSettingsOpen && activeWorkspaceDescriptor && /* @__PURE__ */ jsx12(
7438
+ workspaceSettingsOpen && activeWorkspaceDescriptor && /* @__PURE__ */ jsx13(
6268
7439
  WorkspaceSettingsDialog,
6269
7440
  {
6270
7441
  workspace: activeWorkspaceDescriptor,
@@ -6273,8 +7444,8 @@ function DocBlocksShell({
6273
7444
  onClose: () => setWorkspaceSettingsOpen(false)
6274
7445
  }
6275
7446
  ),
6276
- /* @__PURE__ */ jsxs11("div", { style: { display: "flex", flex: 1, overflow: "hidden" }, children: [
6277
- (!effectiveCompact || !mobileShowEditor) && /* @__PURE__ */ jsxs11(
7447
+ /* @__PURE__ */ jsxs12("div", { style: { display: "flex", flex: 1, overflow: "hidden" }, children: [
7448
+ (!effectiveCompact || !mobileShowEditor) && /* @__PURE__ */ jsxs12(
6278
7449
  "aside",
6279
7450
  {
6280
7451
  ref: sidebarRef,
@@ -6282,8 +7453,8 @@ function DocBlocksShell({
6282
7453
  "aria-label": "Workspace and files",
6283
7454
  style: effectiveCompact ? void 0 : { width: `${sidebarWidth}px` },
6284
7455
  children: [
6285
- /* @__PURE__ */ jsxs11("div", { className: "db-shell-sidebar-header", children: [
6286
- /* @__PURE__ */ jsx12(
7456
+ /* @__PURE__ */ jsxs12("div", { className: "db-shell-sidebar-header", children: [
7457
+ /* @__PURE__ */ jsx13(
6287
7458
  AppMenu,
6288
7459
  {
6289
7460
  logoUrl,
@@ -6304,7 +7475,7 @@ function DocBlocksShell({
6304
7475
  appBuildDate
6305
7476
  }
6306
7477
  ),
6307
- /* @__PURE__ */ jsx12(
7478
+ /* @__PURE__ */ jsx13(
6308
7479
  WorkspacePicker,
6309
7480
  {
6310
7481
  activeWorkspaceId,
@@ -6314,7 +7485,7 @@ function DocBlocksShell({
6314
7485
  onCloneRepository: git.available ? () => git.openDialog({ kind: "clone" }) : void 0
6315
7486
  }
6316
7487
  ),
6317
- /* @__PURE__ */ jsx12(
7488
+ /* @__PURE__ */ jsx13(
6318
7489
  WorkspaceSettingsButton,
6319
7490
  {
6320
7491
  onSettings: handleOpenWorkspaceSettings,
@@ -6323,37 +7494,38 @@ function DocBlocksShell({
6323
7494
  onRemove: handleRemoveWorkspace
6324
7495
  }
6325
7496
  ),
6326
- compactLayout && !isMobile && /* @__PURE__ */ jsx12(
7497
+ compactLayout && !isMobile && /* @__PURE__ */ jsx13(
6327
7498
  "button",
6328
7499
  {
6329
7500
  className: "db-restore-split",
6330
7501
  onClick: () => setCompactLayout(false),
6331
7502
  "aria-label": "Restore split view",
6332
7503
  title: "Restore split view",
6333
- children: /* @__PURE__ */ jsx12(SplitViewIcon, {})
7504
+ children: /* @__PURE__ */ jsx13(SplitViewIcon, {})
6334
7505
  }
6335
7506
  ),
6336
- /* @__PURE__ */ jsx12(
6337
- "span",
6338
- {
6339
- className: "db-window-drag-grip",
6340
- "data-tooltip": "Click and drag here to move your window around.",
6341
- "aria-hidden": true
6342
- }
6343
- )
7507
+ /* @__PURE__ */ jsx13("span", { className: "db-window-drag-grip", "aria-hidden": true })
6344
7508
  ] }),
6345
- git.available && /* @__PURE__ */ jsx12(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx12(
7509
+ git.available && /* @__PURE__ */ jsx13(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx13(
6346
7510
  GitUI,
6347
7511
  {
6348
7512
  onOpenFile: (path) => void handleSelect(path, "file"),
6349
7513
  onWorkspaceCloned: handleWorkspaceCloned
6350
7514
  }
6351
7515
  ) }),
6352
- /* @__PURE__ */ jsx12(
7516
+ /* @__PURE__ */ jsx13(
6353
7517
  FileExplorer,
6354
7518
  {
6355
7519
  provider,
7520
+ activeWorkspaceId,
6356
7521
  activeFilePath: selectedFile,
7522
+ pinnedDocuments: pinnedDocumentItems,
7523
+ pinnedPaths: activeWorkspacePinnedPaths,
7524
+ onPinnedDocumentSelect: (document2) => void handlePinnedDocumentSelect(document2),
7525
+ onPinnedDocumentUnpin: unpinDocument,
7526
+ onPinnedDocumentRename: handlePinnedDocumentRename,
7527
+ onPinnedDocumentDelete: handlePinnedDocumentDelete,
7528
+ onTogglePin: handleTogglePin,
6357
7529
  onSelect: handleSelect,
6358
7530
  onTreeMutation: handleTreeMutation,
6359
7531
  onTreeChange: handleTreeChange,
@@ -6364,17 +7536,17 @@ function DocBlocksShell({
6364
7536
  },
6365
7537
  explorerKey
6366
7538
  ),
6367
- isMobile && showWelcomeGateway && /* @__PURE__ */ jsxs11(
7539
+ isMobile && showWelcomeGateway && /* @__PURE__ */ jsxs12(
6368
7540
  "section",
6369
7541
  {
6370
7542
  className: "db-mobile-first-run",
6371
7543
  "aria-labelledby": "db-mobile-first-run-title",
6372
7544
  children: [
6373
- /* @__PURE__ */ jsx12("p", { className: "db-mobile-first-run-eyebrow", children: "Local-first Markdown editor" }),
6374
- /* @__PURE__ */ jsx12("h1", { id: "db-mobile-first-run-title", children: "Welcome to DocBlocks" }),
6375
- /* @__PURE__ */ jsx12("p", { children: "Write visually, keep plain Markdown underneath, and export the same document in useful formats. Your browser workspace stays on this device." }),
6376
- /* @__PURE__ */ jsxs11("div", { className: "db-mobile-first-run-actions", children: [
6377
- /* @__PURE__ */ jsx12(
7545
+ /* @__PURE__ */ jsx13("p", { className: "db-mobile-first-run-eyebrow", children: "Local-first Markdown editor" }),
7546
+ /* @__PURE__ */ jsx13("h1", { id: "db-mobile-first-run-title", children: "Welcome to DocBlocks" }),
7547
+ /* @__PURE__ */ jsx13("p", { children: "Write visually, keep plain Markdown underneath, and export the same document in useful formats. Your browser workspace stays on this device." }),
7548
+ /* @__PURE__ */ jsxs12("div", { className: "db-mobile-first-run-actions", children: [
7549
+ /* @__PURE__ */ jsx13(
6378
7550
  "button",
6379
7551
  {
6380
7552
  type: "button",
@@ -6385,7 +7557,7 @@ function DocBlocksShell({
6385
7557
  children: "Tour the welcome document"
6386
7558
  }
6387
7559
  ),
6388
- /* @__PURE__ */ jsx12(
7560
+ /* @__PURE__ */ jsx13(
6389
7561
  "button",
6390
7562
  {
6391
7563
  type: "button",
@@ -6398,22 +7570,22 @@ function DocBlocksShell({
6398
7570
  ]
6399
7571
  }
6400
7572
  ),
6401
- /* @__PURE__ */ jsxs11("div", { className: "db-shell-sidebar-footer", children: [
6402
- /* @__PURE__ */ jsx12("a", { href: "https://docblocks.com/docs/", target: "_blank", rel: "noopener noreferrer", children: "Docs" }),
6403
- /* @__PURE__ */ jsx12("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
6404
- /* @__PURE__ */ jsx12("a", { href: "https://docblocks.com/terms/", target: "_blank", rel: "noopener noreferrer", children: "Terms" }),
6405
- /* @__PURE__ */ jsx12("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
6406
- /* @__PURE__ */ jsx12("a", { href: issueReportUrl, target: "_blank", rel: "noopener noreferrer", children: "Report issue" }),
6407
- showBrowserStorageWarning && /* @__PURE__ */ jsxs11(Fragment7, { children: [
6408
- /* @__PURE__ */ jsx12("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
6409
- /* @__PURE__ */ jsx12(
7573
+ /* @__PURE__ */ jsxs12("div", { className: "db-shell-sidebar-footer", children: [
7574
+ /* @__PURE__ */ jsx13("a", { href: "https://docblocks.com/docs/", target: "_blank", rel: "noopener noreferrer", children: "Docs" }),
7575
+ /* @__PURE__ */ jsx13("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
7576
+ /* @__PURE__ */ jsx13("a", { href: "https://docblocks.com/terms/", target: "_blank", rel: "noopener noreferrer", children: "Terms" }),
7577
+ /* @__PURE__ */ jsx13("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
7578
+ /* @__PURE__ */ jsx13("a", { href: issueReportUrl, target: "_blank", rel: "noopener noreferrer", children: "Report issue" }),
7579
+ showBrowserStorageWarning && /* @__PURE__ */ jsxs12(Fragment7, { children: [
7580
+ /* @__PURE__ */ jsx13("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
7581
+ /* @__PURE__ */ jsx13(
6410
7582
  "button",
6411
7583
  {
6412
7584
  type: "button",
6413
7585
  className: "db-shell-sidebar-footer-action",
6414
7586
  onClick: () => void handleDownloadAllWorkspaces(),
6415
- title: "Browser docs can get auto-removed. Download all workspaces.",
6416
- children: "Backup browser docs frequently"
7587
+ title: "Browser documents can be removed automatically. Download all workspaces now.",
7588
+ children: "Back up browser docs"
6417
7589
  }
6418
7590
  )
6419
7591
  ] })
@@ -6421,7 +7593,7 @@ function DocBlocksShell({
6421
7593
  ]
6422
7594
  }
6423
7595
  ),
6424
- !effectiveCompact && /* @__PURE__ */ jsx12(
7596
+ !effectiveCompact && /* @__PURE__ */ jsx13(
6425
7597
  "div",
6426
7598
  {
6427
7599
  className: "db-shell-sidebar-resizer",
@@ -6431,7 +7603,7 @@ function DocBlocksShell({
6431
7603
  onPointerDown: handleResizerPointerDown
6432
7604
  }
6433
7605
  ),
6434
- (!effectiveCompact || mobileShowEditor) && /* @__PURE__ */ jsxs11(
7606
+ (!effectiveCompact || mobileShowEditor) && /* @__PURE__ */ jsxs12(
6435
7607
  "main",
6436
7608
  {
6437
7609
  "aria-label": "Document editor",
@@ -6444,22 +7616,23 @@ function DocBlocksShell({
6444
7616
  position: "relative"
6445
7617
  },
6446
7618
  children: [
6447
- selectedFile && mediaProvider ? /* @__PURE__ */ jsxs11(MediaContext.Provider, { value: mediaProvider, children: [
6448
- /* @__PURE__ */ jsx12(
7619
+ selectedFile && mediaProvider ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
7620
+ /* @__PURE__ */ jsx13(
6449
7621
  Suspense2,
6450
7622
  {
6451
- fallback: /* @__PURE__ */ jsx12("div", { className: "db-shell-empty", role: "status", children: "Loading your document\u2026" }),
6452
- children: /* @__PURE__ */ jsx12(
7623
+ fallback: /* @__PURE__ */ jsx13("div", { className: "db-shell-empty", role: "status", children: "Loading your document\u2026" }),
7624
+ children: /* @__PURE__ */ jsx13(
6453
7625
  EditorShell,
6454
7626
  {
6455
7627
  initialMarkdown: editorContent,
6456
7628
  initialView,
7629
+ defaultViewportPreset: defaultPreviewViewportPreset,
6457
7630
  articleId: selectedFile,
6458
7631
  fileName: selectedFile,
6459
7632
  onChange: handleEditorChange,
6460
7633
  onLinkClick: handleEditorLinkClick,
6461
7634
  colorScheme: resolvedTheme,
6462
- writeCanvasSettings,
7635
+ writeCanvasSettings: editorWriteCanvasSettings,
6463
7636
  height: "100%",
6464
7637
  placeholder: editorPlaceholder,
6465
7638
  outlineWidth: 280,
@@ -6473,28 +7646,29 @@ function DocBlocksShell({
6473
7646
  versioningPrunePolicy,
6474
7647
  versioningAutoSaveIdleMs,
6475
7648
  onSaveVersion,
6476
- toolbarSlotLeft: effectiveCompact ? /* @__PURE__ */ jsx12(
7649
+ statusBarSlotRight,
7650
+ toolbarSlotLeft: effectiveCompact ? /* @__PURE__ */ jsx13(
6477
7651
  "button",
6478
7652
  {
6479
7653
  className: "db-mobile-back",
6480
7654
  onClick: () => setMobileShowEditor(false),
6481
7655
  "aria-label": "Show file list",
6482
- children: /* @__PURE__ */ jsx12("span", { className: "db-mobile-files-icon", children: /* @__PURE__ */ jsx12(FolderGlyph, {}) })
7656
+ children: /* @__PURE__ */ jsx13("span", { className: "db-mobile-files-icon", children: /* @__PURE__ */ jsx13(FolderGlyph, {}) })
6483
7657
  }
6484
7658
  ) : void 0,
6485
- toolbarSlotRight: /* @__PURE__ */ jsxs11(Fragment7, { children: [
6486
- compactLayout && !isMobile && /* @__PURE__ */ jsx12(
7659
+ toolbarSlotRight: /* @__PURE__ */ jsxs12(Fragment7, { children: [
7660
+ compactLayout && !isMobile && /* @__PURE__ */ jsx13(
6487
7661
  "button",
6488
7662
  {
6489
7663
  className: "db-restore-split",
6490
7664
  onClick: () => setCompactLayout(false),
6491
7665
  "aria-label": "Restore split view",
6492
7666
  title: "Restore split view",
6493
- children: /* @__PURE__ */ jsx12(SplitViewIcon, {})
7667
+ children: /* @__PURE__ */ jsx13(SplitViewIcon, {})
6494
7668
  }
6495
7669
  ),
6496
- git.repo && /* @__PURE__ */ jsx12(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx12(GitToolbarControl, { selectedFile }) }),
6497
- /* @__PURE__ */ jsx12(
7670
+ git.repo && /* @__PURE__ */ jsx13(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx13(GitToolbarControl, { selectedFile }) }),
7671
+ /* @__PURE__ */ jsx13(
6498
7672
  ExportToolbarControls,
6499
7673
  {
6500
7674
  selectedFile,
@@ -6512,15 +7686,15 @@ function DocBlocksShell({
6512
7686
  )
6513
7687
  }
6514
7688
  ),
6515
- showWelcomeGateway && !isMobile && /* @__PURE__ */ jsxs11("div", { className: "db-welcome-gateway", role: "note", "aria-label": "Welcome tip", children: [
6516
- /* @__PURE__ */ jsxs11("span", { className: "db-welcome-gateway-text", children: [
7689
+ showWelcomeGateway && !isMobile && /* @__PURE__ */ jsxs12("div", { className: "db-welcome-gateway", role: "note", "aria-label": "Welcome tip", children: [
7690
+ /* @__PURE__ */ jsxs12("span", { className: "db-welcome-gateway-text", children: [
6517
7691
  "You\u2019re watching this welcome doc in ",
6518
- /* @__PURE__ */ jsx12("strong", { children: "Slideshow" }),
7692
+ /* @__PURE__ */ jsx13("strong", { children: "Slideshow" }),
6519
7693
  " ",
6520
7694
  "view\u2014 it\u2019s a regular markdown file, and so is everything you\u2019ll write."
6521
7695
  ] }),
6522
- /* @__PURE__ */ jsx12("button", { className: "db-welcome-gateway-cta", onClick: handleStartWriting, children: "Start writing" }),
6523
- /* @__PURE__ */ jsx12(
7696
+ /* @__PURE__ */ jsx13("button", { className: "db-welcome-gateway-cta", onClick: handleStartWriting, children: "Start writing" }),
7697
+ /* @__PURE__ */ jsx13(
6524
7698
  "button",
6525
7699
  {
6526
7700
  className: "db-welcome-gateway-dismiss",
@@ -6531,39 +7705,39 @@ function DocBlocksShell({
6531
7705
  }
6532
7706
  )
6533
7707
  ] })
6534
- ] }) : selectedFolder ? /* @__PURE__ */ jsxs11("div", { className: "db-folder-view", children: [
6535
- effectiveCompact && /* @__PURE__ */ jsxs11("button", { className: "db-mobile-back", onClick: () => setMobileShowEditor(false), children: [
6536
- /* @__PURE__ */ jsx12("span", { className: "db-mobile-files-icon", children: /* @__PURE__ */ jsx12(FolderGlyph, {}) }),
7708
+ ] }) : selectedFolder ? /* @__PURE__ */ jsxs12("div", { className: "db-folder-view", children: [
7709
+ effectiveCompact && /* @__PURE__ */ jsxs12("button", { className: "db-mobile-back", onClick: () => setMobileShowEditor(false), children: [
7710
+ /* @__PURE__ */ jsx13("span", { className: "db-mobile-files-icon", children: /* @__PURE__ */ jsx13(FolderGlyph, {}) }),
6537
7711
  "Back to files"
6538
7712
  ] }),
6539
- /* @__PURE__ */ jsxs11("div", { className: "db-folder-view-header", children: [
6540
- /* @__PURE__ */ jsx12("span", { className: "db-folder-view-icon", children: /* @__PURE__ */ jsx12(FolderGlyph, {}) }),
6541
- /* @__PURE__ */ jsx12("span", { className: "db-folder-view-path", children: selectedFolder })
7713
+ /* @__PURE__ */ jsxs12("div", { className: "db-folder-view-header", children: [
7714
+ /* @__PURE__ */ jsx13("span", { className: "db-folder-view-icon", children: /* @__PURE__ */ jsx13(FolderGlyph, {}) }),
7715
+ /* @__PURE__ */ jsx13("span", { className: "db-folder-view-path", children: selectedFolder })
6542
7716
  ] }),
6543
- folderEntries.length === 0 ? /* @__PURE__ */ jsx12("p", { className: "db-folder-view-empty", children: "This folder is empty." }) : /* @__PURE__ */ jsx12("ul", { className: "db-folder-view-list", children: folderEntries.map((entry) => /* @__PURE__ */ jsxs11(
7717
+ visibleFolderEntries.length === 0 ? /* @__PURE__ */ jsx13("p", { className: "db-folder-view-empty", children: "This folder is empty." }) : /* @__PURE__ */ jsx13("ul", { className: "db-folder-view-list", children: visibleFolderEntries.map((entry) => /* @__PURE__ */ jsxs12(
6544
7718
  "li",
6545
7719
  {
6546
7720
  className: "db-folder-view-item",
6547
7721
  onClick: () => handleSelect(entry.path, entry.kind),
6548
7722
  children: [
6549
- /* @__PURE__ */ jsx12("span", { className: "db-folder-view-item-icon", children: entry.kind === "directory" ? /* @__PURE__ */ jsx12(FolderGlyph, {}) : /* @__PURE__ */ jsx12(FileGlyph, {}) }),
7723
+ /* @__PURE__ */ jsx13("span", { className: "db-folder-view-item-icon", children: entry.kind === "directory" ? /* @__PURE__ */ jsx13(FolderGlyph, {}) : /* @__PURE__ */ jsx13(FileGlyph, {}) }),
6550
7724
  entry.name
6551
7725
  ]
6552
7726
  },
6553
7727
  entry.path
6554
7728
  )) })
6555
- ] }) : /* @__PURE__ */ jsxs11("div", { className: "db-shell-empty db-shell-empty--workspace", children: [
6556
- effectiveCompact && /* @__PURE__ */ jsxs11("button", { className: "db-mobile-back", onClick: () => setMobileShowEditor(false), children: [
6557
- /* @__PURE__ */ jsx12("span", { className: "db-mobile-files-icon", children: /* @__PURE__ */ jsx12(FolderGlyph, {}) }),
7729
+ ] }) : /* @__PURE__ */ jsxs12("div", { className: "db-shell-empty db-shell-empty--workspace", children: [
7730
+ effectiveCompact && /* @__PURE__ */ jsxs12("button", { className: "db-mobile-back", onClick: () => setMobileShowEditor(false), children: [
7731
+ /* @__PURE__ */ jsx13("span", { className: "db-mobile-files-icon", children: /* @__PURE__ */ jsx13(FolderGlyph, {}) }),
6558
7732
  "Back to files"
6559
7733
  ] }),
6560
- /* @__PURE__ */ jsxs11("div", { className: "db-workspace-empty-content", children: [
6561
- /* @__PURE__ */ jsx12("span", { className: "db-workspace-empty-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx12(FileGlyph, {}) }),
6562
- /* @__PURE__ */ jsx12("h1", { children: activeWorkspaceDescriptor?.name ? `${activeWorkspaceDescriptor.name} is ready` : "Your workspace is ready" }),
6563
- /* @__PURE__ */ jsx12("p", { children: "Create your first Markdown document, or open a folder you already use." }),
6564
- /* @__PURE__ */ jsxs11("div", { className: "db-workspace-empty-actions", children: [
6565
- /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => void handleNewFile(), children: "Create your first document" }),
6566
- /* @__PURE__ */ jsx12(
7734
+ /* @__PURE__ */ jsxs12("div", { className: "db-workspace-empty-content", children: [
7735
+ /* @__PURE__ */ jsx13("span", { className: "db-workspace-empty-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(FileGlyph, {}) }),
7736
+ /* @__PURE__ */ jsx13("h1", { children: activeWorkspaceDescriptor?.name ?? "Workspace" }),
7737
+ /* @__PURE__ */ jsx13("p", { children: "Choose a Markdown document from the sidebar, or create a new one." }),
7738
+ /* @__PURE__ */ jsxs12("div", { className: "db-workspace-empty-actions", children: [
7739
+ /* @__PURE__ */ jsx13("button", { type: "button", onClick: () => void handleNewFile(), children: "New document" }),
7740
+ /* @__PURE__ */ jsx13(
6567
7741
  "button",
6568
7742
  {
6569
7743
  type: "button",
@@ -6572,7 +7746,7 @@ function DocBlocksShell({
6572
7746
  children: "New folder"
6573
7747
  }
6574
7748
  ),
6575
- (isElectronHost3() || typeof globalThis.showDirectoryPicker === "function") && /* @__PURE__ */ jsx12(
7749
+ (isElectronHost3() || typeof globalThis.showDirectoryPicker === "function") && /* @__PURE__ */ jsx13(
6576
7750
  "button",
6577
7751
  {
6578
7752
  type: "button",
@@ -6582,10 +7756,10 @@ function DocBlocksShell({
6582
7756
  }
6583
7757
  )
6584
7758
  ] }),
6585
- /* @__PURE__ */ jsx12("p", { className: "db-workspace-empty-hint", children: "Browser workspaces stay on this device. Use the sidebar backup action to keep a portable copy." })
7759
+ /* @__PURE__ */ jsx13("p", { className: "db-workspace-empty-hint", children: "Browser workspaces stay on this device. Use the sidebar backup action to keep a portable copy." })
6586
7760
  ] })
6587
7761
  ] }),
6588
- /* @__PURE__ */ jsx12(
7762
+ /* @__PURE__ */ jsx13(
6589
7763
  UpdateAvailableNotice,
6590
7764
  {
6591
7765
  available: updateAvailable,
@@ -6606,6 +7780,7 @@ export {
6606
7780
  AccentColorSettings,
6607
7781
  AppMenu,
6608
7782
  DEFAULT_OPTIONS,
7783
+ DEFAULT_WRITE_CANVAS_FONT_SCHEME,
6609
7784
  DocBlocksShell,
6610
7785
  ExportDialog,
6611
7786
  ExportToolbarControls,
@@ -6613,10 +7788,12 @@ export {
6613
7788
  FileTreeNode,
6614
7789
  SettingsDialog,
6615
7790
  ThemeSettings,
7791
+ WRITE_CANVAS_FONT_SCHEMES,
6616
7792
  WorkspacePicker,
6617
7793
  WriteCanvasSettingsControls,
6618
7794
  buildExportFilename,
6619
7795
  loadLastExportOptions,
7796
+ resolveWriteCanvasFonts,
6620
7797
  runExport,
6621
7798
  updateExportTargetExtension,
6622
7799
  useDocumentSession,