@bendyline/docblocks-react 2.7.2 → 2.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/THIRD_PARTY_NOTICES.txt +215 -23
  2. package/dist/{AiDiagrams-LFV4YU6W.js → AiDiagrams-QZMJNGDT.js} +3 -3
  3. package/dist/{AiEditorAssistant-GN2KXKZD.js → AiEditorAssistant-4HFFGVU4.js} +205 -166
  4. package/dist/AiStudyDialog-P3E7PJV5.js +1150 -0
  5. package/dist/{AudioExportDialog-F23QXNPB.js → AudioExportDialog-IOYIEAER.js} +1 -1
  6. package/dist/{DocumentConflictDialog-5LRVIGXS.js → DocumentConflictDialog-DZ2BV2IH.js} +1 -1
  7. package/dist/ExportDialog-LGWBXIWN.js +9 -0
  8. package/dist/{ExportToolbarControls-2FEIHNMW.js → ExportToolbarControls-HOV424TO.js} +114 -14
  9. package/dist/{GitUI-RCWUZVS6.js → GitUI-F3CNBURE.js} +1 -1
  10. package/dist/{KeyboardShortcutsDialog-NOD4P25G.js → KeyboardShortcutsDialog-HVKDFP5Z.js} +2 -2
  11. package/dist/{PresentationMaker-ITNP5SDO.js → PresentationMaker-MUMFAXEI.js} +3 -3
  12. package/dist/{QuickOpenDialog-2KK4SEBU.js → QuickOpenDialog-ENVFGIHS.js} +1 -1
  13. package/dist/{Settings-RDYvDDI0.d.ts → Settings-CfhY489G.d.ts} +3 -5
  14. package/dist/{ShareDialog-XUA2OVGI.js → ShareDialog-PF7D7ORP.js} +1 -1
  15. package/dist/{SpeechToolbarControl-CLU7RB2G.js → SpeechToolbarControl-KNI2C4JJ.js} +1 -1
  16. package/dist/{TextFromNarration-NITJQ4IB.js → TextFromNarration-EYOMW2IW.js} +1 -1
  17. package/dist/{chunk-HRWEGPTY.js → chunk-7CSBRAMT.js} +109 -24
  18. package/dist/{chunk-NAH52736.js → chunk-7V34VUK6.js} +969 -98
  19. package/dist/{chunk-K7XJDJUJ.js → chunk-ACQF63SB.js} +14 -4
  20. package/dist/{chunk-X3DDTJKR.js → chunk-D4HIOS7E.js} +256 -18
  21. package/dist/{chunk-BLUEZG7T.js → chunk-FBPCURCS.js} +9 -2
  22. package/dist/{chunk-Y5OE3HJC.js → chunk-GFK4TCKN.js} +11 -5
  23. package/dist/chunk-H3BK5MK6.js +34 -0
  24. package/dist/{chunk-6B7G33D5.js → chunk-KBUHWZCF.js} +1 -1
  25. package/dist/{chunk-SSB43524.js → chunk-PNZFIIWW.js} +91 -12
  26. package/dist/{chunk-56KO2LYN.js → chunk-VJPGZCFS.js} +4 -0
  27. package/dist/chunk-XNT66JC2.js +168 -0
  28. package/dist/editor.js +4 -4
  29. package/dist/export/index.d.ts +19 -4
  30. package/dist/export/index.js +4 -4
  31. package/dist/index.d.ts +19 -5
  32. package/dist/index.js +858 -557
  33. package/dist/{run-export-CS3HRNAZ.js → run-export-SPNNH32M.js} +1 -1
  34. package/dist/settings/index.d.ts +1 -1
  35. package/dist/settings/index.js +2 -2
  36. package/package.json +8 -8
  37. package/src/styles/docblocks.css +526 -63
  38. package/dist/ExportDialog-JRVZCG3J.js +0 -9
  39. package/dist/chunk-YBEYTVU2.js +0 -12
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ import {
2
+ ProgressivePlayer
3
+ } from "./chunk-2IYBBFQX.js";
1
4
  import {
2
5
  BackupIcon,
3
6
  FolderIcon,
@@ -14,13 +17,10 @@ import {
14
17
  } from "./chunk-DICE7VBJ.js";
15
18
  import {
16
19
  useConfirmDialog
17
- } from "./chunk-6B7G33D5.js";
20
+ } from "./chunk-KBUHWZCF.js";
18
21
  import {
19
22
  createSpeechInputProvider
20
23
  } from "./chunk-KBDZDFPU.js";
21
- import {
22
- ProgressivePlayer
23
- } from "./chunk-2IYBBFQX.js";
24
24
  import {
25
25
  sharedDocumentFilename
26
26
  } from "./chunk-SPUPWCSI.js";
@@ -51,7 +51,7 @@ import {
51
51
  } from "./chunk-KWG6UP5O.js";
52
52
  import {
53
53
  runExport
54
- } from "./chunk-HRWEGPTY.js";
54
+ } from "./chunk-7CSBRAMT.js";
55
55
  import {
56
56
  createImageSaveOutput
57
57
  } from "./chunk-UONFWKJ7.js";
@@ -87,7 +87,7 @@ import {
87
87
  saveProofingPreferences,
88
88
  saveThemePreference,
89
89
  saveWriteCanvasPreferences
90
- } from "./chunk-BLUEZG7T.js";
90
+ } from "./chunk-FBPCURCS.js";
91
91
  import {
92
92
  FindButton,
93
93
  focusFindInput,
@@ -99,14 +99,19 @@ import {
99
99
  isApplePlatform,
100
100
  matchShortcut,
101
101
  shortcutLabel
102
- } from "./chunk-K7XJDJUJ.js";
103
- import "./chunk-SSB43524.js";
104
- import "./chunk-NAH52736.js";
102
+ } from "./chunk-ACQF63SB.js";
103
+ import "./chunk-PNZFIIWW.js";
104
+ import {
105
+ buildIssueReportUrl,
106
+ displayBuildDate,
107
+ localDateStamp,
108
+ openExternalUrl
109
+ } from "./chunk-7V34VUK6.js";
105
110
  import "./chunk-K4I3NJFL.js";
106
111
  import {
107
112
  ExportDialog
108
- } from "./chunk-Y5OE3HJC.js";
109
- import "./chunk-YBEYTVU2.js";
113
+ } from "./chunk-GFK4TCKN.js";
114
+ import "./chunk-H3BK5MK6.js";
110
115
  import {
111
116
  DEFAULT_OPTIONS,
112
117
  loadLastExportOptions
@@ -114,13 +119,14 @@ import {
114
119
  import {
115
120
  Dialog,
116
121
  dismissTopmostDialog
117
- } from "./chunk-56KO2LYN.js";
122
+ } from "./chunk-VJPGZCFS.js";
118
123
 
119
124
  // src/FileExplorer/FileExplorer.tsx
120
125
  import { useCallback as useCallback5, useEffect as useEffect5, useMemo, useRef as useRef5, useState as useState4 } from "react";
121
126
  import {
122
127
  isFileSystemMoveStateError,
123
- parseWorkspacePath as parseWorkspacePath4
128
+ parseWorkspacePath as parseWorkspacePath4,
129
+ tryParseWorkspacePath as tryParseWorkspacePath2
124
130
  } from "@bendyline/docblocks/filesystem";
125
131
 
126
132
  // src/FileExplorer/useFileTree.ts
@@ -134,6 +140,7 @@ import {
134
140
  workspacePathDirname
135
141
  } from "@bendyline/docblocks/filesystem";
136
142
  var DIRECTORY_NOT_FOUND_RETRY_DELAYS_MS = Object.freeze([40, 120]);
143
+ var AUTO_RETRY_DELAYS_MS = Object.freeze([1e3, 4e3, 15e3]);
137
144
  function normalisePath(path) {
138
145
  return parseWorkspacePath(path);
139
146
  }
@@ -162,10 +169,13 @@ function sameEntry(left, right) {
162
169
  if (left.kind !== right.kind || left.name !== right.name || left.path !== right.path)
163
170
  return false;
164
171
  if (left.kind === "file" && right.kind === "file") {
165
- return left.lastModified === right.lastModified;
172
+ return left.lastModified === right.lastModified && left.readIssue?.code === right.readIssue?.code && left.readIssue?.message === right.readIssue?.message;
166
173
  }
167
174
  return true;
168
175
  }
176
+ function hasUnreadableFile(entries) {
177
+ return entries.some((entry) => entry.kind === "file" && entry.readIssue !== void 0);
178
+ }
169
179
  function reconcileEntries(current, incoming) {
170
180
  const currentByPath = new Map(current.map((entry) => [entry.path, entry]));
171
181
  const reconciled = incoming.map((entry) => {
@@ -184,7 +194,8 @@ async function readProviderDirectory(provider, path) {
184
194
  kind: entry.kind,
185
195
  name: entry.name,
186
196
  path: entry.path,
187
- ...entry.kind === "file" ? { lastModified: entry.lastModified } : {}
197
+ ...entry.kind === "file" ? { lastModified: entry.lastModified } : {},
198
+ ...entry.kind === "file" && entry.readIssue ? { readIssue: entry.readIssue } : {}
188
199
  }));
189
200
  };
190
201
  for (let attempt = 0; ; attempt += 1) {
@@ -232,64 +243,111 @@ function useFileTree(provider, metadataRefreshKey, metadataRefreshPath) {
232
243
  providerRef.current = provider;
233
244
  const directoryRequestSequenceRef = useRef(0);
234
245
  const directoryRequestsRef = useRef(/* @__PURE__ */ new Map());
246
+ const autoRetriesRef = useRef(/* @__PURE__ */ new Map());
247
+ const autoRetryLoadRef = useRef(async () => void 0);
235
248
  const reportRootIssue = useCallback((caught) => {
236
249
  setRootIssue(readIssue(caught, ""));
237
250
  }, []);
238
- const loadRoot = useCallback(async (showLoading = true) => {
239
- const sourceProvider = providerRef.current;
240
- if (!sourceProvider) {
241
- setEntries([]);
242
- setLoading(false);
243
- return;
244
- }
245
- const requestId = ++directoryRequestSequenceRef.current;
246
- directoryRequestsRef.current.set("", requestId);
247
- const isCurrent = () => providerRef.current === sourceProvider && directoryRequestsRef.current.get("") === requestId;
248
- if (showLoading) setLoading(true);
249
- try {
250
- const root = await readProviderDirectory(sourceProvider, "");
251
- if (!isCurrent()) return;
252
- setEntries((current) => reconcileEntries(current, root));
253
- setRootIssue(null);
254
- } catch (caught) {
255
- if (isCurrent()) setRootIssue(readIssue(caught, ""));
256
- } finally {
257
- if (isCurrent()) setLoading(false);
258
- }
251
+ const cancelAutoRetry = useCallback((directoryPath) => {
252
+ const state = autoRetriesRef.current.get(directoryPath);
253
+ if (state?.timer) clearTimeout(state.timer);
254
+ autoRetriesRef.current.delete(directoryPath);
259
255
  }, []);
260
- const loadChildren = useCallback(async (dirPath) => {
261
- const sourceProvider = providerRef.current;
262
- if (!sourceProvider) return;
263
- const canonical = normalisePath(dirPath);
264
- const requestId = ++directoryRequestSequenceRef.current;
265
- directoryRequestsRef.current.set(canonical, requestId);
266
- const isCurrent = () => providerRef.current === sourceProvider && directoryRequestsRef.current.get(canonical) === requestId;
267
- try {
268
- const children = await readProviderDirectory(sourceProvider, dirPath);
269
- if (!isCurrent()) return;
270
- setChildEntries((prev) => {
271
- const current = prev.get(canonical) ?? [];
272
- const reconciled = reconcileEntries(current, children);
273
- if (prev.has(canonical) && reconciled === current) return prev;
274
- const next = new Map(prev);
275
- next.set(canonical, reconciled);
276
- return next;
277
- });
278
- setChildIssues((prev) => {
279
- if (!prev.has(canonical)) return prev;
280
- const next = new Map(prev);
281
- next.delete(canonical);
282
- return next;
283
- });
284
- } catch (caught) {
285
- if (!isCurrent()) return;
286
- setChildIssues((prev) => {
287
- const next = new Map(prev);
288
- next.set(canonical, readIssue(caught, dirPath));
289
- return next;
290
- });
256
+ const cancelAllAutoRetries = useCallback(() => {
257
+ for (const state of autoRetriesRef.current.values()) {
258
+ if (state.timer) clearTimeout(state.timer);
291
259
  }
260
+ autoRetriesRef.current.clear();
292
261
  }, []);
262
+ const settleAutoRetry = useCallback(
263
+ (directoryPath, needsRetry) => {
264
+ if (!needsRetry) {
265
+ cancelAutoRetry(directoryPath);
266
+ return;
267
+ }
268
+ const current = autoRetriesRef.current.get(directoryPath);
269
+ if (current?.timer) return;
270
+ const attempts = current?.attempts ?? 0;
271
+ const delay = AUTO_RETRY_DELAYS_MS[attempts];
272
+ if (delay === void 0) return;
273
+ const next = { attempts: attempts + 1, timer: null };
274
+ next.timer = setTimeout(() => {
275
+ next.timer = null;
276
+ void autoRetryLoadRef.current(directoryPath);
277
+ }, delay);
278
+ autoRetriesRef.current.set(directoryPath, next);
279
+ },
280
+ [cancelAutoRetry]
281
+ );
282
+ const loadRoot = useCallback(
283
+ async (showLoading = true) => {
284
+ const sourceProvider = providerRef.current;
285
+ if (!sourceProvider) {
286
+ setEntries([]);
287
+ setLoading(false);
288
+ return;
289
+ }
290
+ const requestId = ++directoryRequestSequenceRef.current;
291
+ directoryRequestsRef.current.set("", requestId);
292
+ const isCurrent = () => providerRef.current === sourceProvider && directoryRequestsRef.current.get("") === requestId;
293
+ if (showLoading) setLoading(true);
294
+ try {
295
+ const root = await readProviderDirectory(sourceProvider, "");
296
+ if (!isCurrent()) return;
297
+ setEntries((current) => reconcileEntries(current, root));
298
+ setRootIssue(null);
299
+ settleAutoRetry("", hasUnreadableFile(root));
300
+ } catch (caught) {
301
+ if (isCurrent()) {
302
+ const issue = readIssue(caught, "");
303
+ setRootIssue(issue);
304
+ settleAutoRetry("", issue.retryable);
305
+ }
306
+ } finally {
307
+ if (isCurrent()) setLoading(false);
308
+ }
309
+ },
310
+ [settleAutoRetry]
311
+ );
312
+ const loadChildren = useCallback(
313
+ async (dirPath) => {
314
+ const sourceProvider = providerRef.current;
315
+ if (!sourceProvider) return;
316
+ const canonical = normalisePath(dirPath);
317
+ const requestId = ++directoryRequestSequenceRef.current;
318
+ directoryRequestsRef.current.set(canonical, requestId);
319
+ const isCurrent = () => providerRef.current === sourceProvider && directoryRequestsRef.current.get(canonical) === requestId;
320
+ try {
321
+ const children = await readProviderDirectory(sourceProvider, dirPath);
322
+ if (!isCurrent()) return;
323
+ setChildEntries((prev) => {
324
+ const current = prev.get(canonical) ?? [];
325
+ const reconciled = reconcileEntries(current, children);
326
+ if (prev.has(canonical) && reconciled === current) return prev;
327
+ const next = new Map(prev);
328
+ next.set(canonical, reconciled);
329
+ return next;
330
+ });
331
+ setChildIssues((prev) => {
332
+ if (!prev.has(canonical)) return prev;
333
+ const next = new Map(prev);
334
+ next.delete(canonical);
335
+ return next;
336
+ });
337
+ settleAutoRetry(canonical, hasUnreadableFile(children));
338
+ } catch (caught) {
339
+ if (!isCurrent()) return;
340
+ const issue = readIssue(caught, dirPath);
341
+ setChildIssues((prev) => {
342
+ const next = new Map(prev);
343
+ next.set(canonical, issue);
344
+ return next;
345
+ });
346
+ settleAutoRetry(canonical, issue.retryable);
347
+ }
348
+ },
349
+ [settleAutoRetry]
350
+ );
293
351
  useEffect(() => {
294
352
  setEntries([]);
295
353
  setExpanded(/* @__PURE__ */ new Set());
@@ -299,8 +357,10 @@ function useFileTree(provider, metadataRefreshKey, metadataRefreshPath) {
299
357
  setSelectedKind(null);
300
358
  setRootIssue(null);
301
359
  directoryRequestsRef.current.clear();
360
+ cancelAllAutoRetries();
302
361
  void loadRoot();
303
- }, [provider, loadRoot]);
362
+ return cancelAllAutoRetries;
363
+ }, [provider, loadRoot, cancelAllAutoRetries]);
304
364
  const toggleExpand = useCallback(
305
365
  (path) => {
306
366
  setExpanded((prev) => {
@@ -347,10 +407,12 @@ function useFileTree(provider, metadataRefreshKey, metadataRefreshPath) {
347
407
  }, [loadRoot, loadChildren, expanded]);
348
408
  const retryDirectory = useCallback(
349
409
  async (path) => {
350
- if (normalisePath(path) === "") await loadRoot();
410
+ const canonical = normalisePath(path);
411
+ cancelAutoRetry(canonical);
412
+ if (canonical === "") await loadRoot();
351
413
  else await loadChildren(path);
352
414
  },
353
- [loadRoot, loadChildren]
415
+ [loadRoot, loadChildren, cancelAutoRetry]
354
416
  );
355
417
  const refreshRef = useRef(refresh);
356
418
  refreshRef.current = refresh;
@@ -361,6 +423,7 @@ function useFileTree(provider, metadataRefreshKey, metadataRefreshPath) {
361
423
  },
362
424
  [loadChildren, loadRoot]
363
425
  );
426
+ autoRetryLoadRef.current = refreshDirectory;
364
427
  const previousMetadataRefreshKeyRef = useRef(metadataRefreshKey);
365
428
  useEffect(() => {
366
429
  if (Object.is(previousMetadataRefreshKeyRef.current, metadataRefreshKey)) return;
@@ -537,12 +600,13 @@ function useFileTree(provider, metadataRefreshKey, metadataRefreshPath) {
537
600
  setExpanded(nextExpanded);
538
601
  setChildEntries(/* @__PURE__ */ new Map());
539
602
  setChildIssues(/* @__PURE__ */ new Map());
603
+ cancelAllAutoRetries();
540
604
  await loadRoot();
541
605
  for (const dirPath of nextExpanded) {
542
606
  await loadChildren(dirPath);
543
607
  }
544
608
  },
545
- [selectedPath, entries, childEntries, expanded, loadRoot, loadChildren]
609
+ [selectedPath, entries, childEntries, expanded, loadRoot, loadChildren, cancelAllAutoRetries]
546
610
  );
547
611
  return {
548
612
  entries,
@@ -783,6 +847,11 @@ function FileTreeNode({
783
847
  }) {
784
848
  const [renaming, setRenaming] = useState2(false);
785
849
  const [renameValue, setRenameValue] = useState2(entry.name);
850
+ const readIssue2 = entry.kind === "file" ? entry.readIssue : void 0;
851
+ const rowStatus = [
852
+ badge?.label,
853
+ readIssue2 ? `could not be read: ${readIssue2.message}` : void 0
854
+ ].filter((part) => part !== void 0);
786
855
  const [showContext, setShowContext] = useState2(false);
787
856
  const [contextPos, setContextPos] = useState2({ x: 0, y: 0 });
788
857
  const [actionError, setActionError] = useState2(null);
@@ -1029,7 +1098,7 @@ function FileTreeNode({
1029
1098
  "data-path": entry.path,
1030
1099
  "aria-expanded": isDir ? expanded : void 0,
1031
1100
  "aria-selected": selected,
1032
- "aria-label": badge ? `${entry.name}, ${badge.label}` : void 0,
1101
+ "aria-label": rowStatus.length > 0 ? `${entry.name}, ${rowStatus.join(", ")}` : void 0,
1033
1102
  "aria-level": depth + 1,
1034
1103
  "aria-posinset": posInSet,
1035
1104
  "aria-setsize": setSize,
@@ -1081,6 +1150,15 @@ function FileTreeNode({
1081
1150
  ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1082
1151
  /* @__PURE__ */ jsx2("span", { className: "db-tree-label", children: displayFileName(entry.name) }),
1083
1152
  badge && /* @__PURE__ */ jsx2("span", { className: `db-git-badge db-git-badge--${badge.kind}`, "aria-hidden": "true", children: badge.glyph }),
1153
+ readIssue2 && /* @__PURE__ */ jsx2(
1154
+ "span",
1155
+ {
1156
+ className: "db-tree-read-issue",
1157
+ title: `Could not read this file: ${readIssue2.message}`,
1158
+ "aria-hidden": "true",
1159
+ children: "!"
1160
+ }
1161
+ ),
1084
1162
  entry.kind === "file" && entry.lastModified && /* @__PURE__ */ jsx2(LastModifiedTime, { value: entry.lastModified }),
1085
1163
  /* @__PURE__ */ jsx2(
1086
1164
  "button",
@@ -1712,6 +1790,7 @@ import {
1712
1790
  FsError as FsError3,
1713
1791
  SQUISQ_RUNTIME_DIRECTORY_NAME,
1714
1792
  getFileSystemProviderV2 as getFileSystemProviderV23,
1793
+ findWikiArticle,
1715
1794
  parseWorkspacePath as parseWorkspacePath3
1716
1795
  } from "@bendyline/docblocks/filesystem";
1717
1796
  import {
@@ -2144,10 +2223,7 @@ async function writeRuntimeIfNeeded(provider, runtimePath) {
2144
2223
  if (current !== PLAYER_BUNDLE) await writeProviderText(provider, runtimePath, PLAYER_BUNDLE);
2145
2224
  }
2146
2225
  async function renderPlainOutsideInHtml(provider, layout, markdown, themeId) {
2147
- const [{ parseMarkdown }, { markdownDocToPlainHtml }] = await Promise.all([
2148
- import("@bendyline/squisq/markdown"),
2149
- import("@bendyline/squisq-formats/html")
2150
- ]);
2226
+ const { markdownDocToPlainHtml } = await import("@bendyline/squisq-formats/html");
2151
2227
  const container = new FileSystemContentContainer(provider, layout.companionDirectory);
2152
2228
  const images = /* @__PURE__ */ new Map();
2153
2229
  for (const entry of await container.listFiles()) {
@@ -2156,7 +2232,7 @@ async function renderPlainOutsideInHtml(provider, layout, markdown, themeId) {
2156
2232
  images.set(`./${entry.path}`, outputPath);
2157
2233
  }
2158
2234
  return new TextEncoder().encode(
2159
- markdownDocToPlainHtml(parseMarkdown(markdown), {
2235
+ markdownDocToPlainHtml(markdown, {
2160
2236
  title: layout.stem,
2161
2237
  images: images.size > 0 ? images : void 0,
2162
2238
  ...themeId ? { themeId } : {}
@@ -2166,9 +2242,22 @@ async function renderPlainOutsideInHtml(provider, layout, markdown, themeId) {
2166
2242
  async function prepareOutsideInRender(provider, layout, markdown, render) {
2167
2243
  const themeId = await resolveFallbackRenderTheme(markdown, render);
2168
2244
  const htmlOutput = layout.format === "html" ? await readOutsideInHtmlOutput(markdown) : null;
2245
+ let renderedSource = markdown;
2246
+ if (layout.format === "html") {
2247
+ const { parseMarkdown, resolveWikiLinks } = await import("@bendyline/squisq/markdown");
2248
+ renderedSource = await resolveWikiLinks(parseMarkdown(markdown), async (target) => {
2249
+ const article = await findWikiArticle(provider, layout.targetPath, target, ".html");
2250
+ return article.exists ? article.relativePath.split("/").map(encodeURIComponent).join("/") : null;
2251
+ });
2252
+ }
2169
2253
  if (htmlOutput === "static") {
2170
2254
  return {
2171
- bytes: await renderPlainOutsideInHtml(provider, layout, markdown, themeId),
2255
+ bytes: await renderPlainOutsideInHtml(
2256
+ provider,
2257
+ layout,
2258
+ renderedSource,
2259
+ themeId
2260
+ ),
2172
2261
  runtimePath: null
2173
2262
  };
2174
2263
  }
@@ -2176,7 +2265,7 @@ async function prepareOutsideInRender(provider, layout, markdown, render) {
2176
2265
  const outputDirectory = dirname(layout.targetPath);
2177
2266
  const rendered = await renderOutsideInDocument(
2178
2267
  {
2179
- markdown,
2268
+ markdown: renderedSource,
2180
2269
  targetPath: layout.targetPath,
2181
2270
  container: new FileSystemContentContainer(provider, layout.companionDirectory)
2182
2271
  },
@@ -2411,6 +2500,10 @@ var NEW_ITEM_HINT_ID = "db-new-item-hint";
2411
2500
  function normalisePath2(path) {
2412
2501
  return parseWorkspacePath4(path);
2413
2502
  }
2503
+ function readIssueText(issue) {
2504
+ const failingPath = tryParseWorkspacePath2(issue.path);
2505
+ return failingPath && failingPath !== issue.directoryPath ? `${failingPath}: ${issue.message}` : issue.message;
2506
+ }
2414
2507
  function hasEquivalentPath2(paths, path) {
2415
2508
  const canonical = normalisePath2(path);
2416
2509
  return paths.has(path) || paths.has(canonical) || canonical !== "" && paths.has(`/${canonical}`);
@@ -2443,6 +2536,7 @@ function isInternalDrag(dataTransfer) {
2443
2536
  }
2444
2537
  function FileExplorer({
2445
2538
  provider,
2539
+ loading = false,
2446
2540
  metadataRefreshKey,
2447
2541
  activeFilePath,
2448
2542
  sortMode,
@@ -2874,7 +2968,7 @@ function FileExplorer({
2874
2968
  role: "alert",
2875
2969
  "data-directory-path": childIssue.directoryPath,
2876
2970
  children: [
2877
- /* @__PURE__ */ jsx5("span", { children: childIssue.message }),
2971
+ /* @__PURE__ */ jsx5("span", { children: readIssueText(childIssue) }),
2878
2972
  " ",
2879
2973
  /* @__PURE__ */ jsx5(
2880
2974
  "button",
@@ -3155,7 +3249,7 @@ function FileExplorer({
3155
3249
  role: "alert",
3156
3250
  "data-directory-path": tree.rootIssue.directoryPath,
3157
3251
  children: [
3158
- /* @__PURE__ */ jsx5("span", { children: tree.rootIssue.message }),
3252
+ /* @__PURE__ */ jsx5("span", { children: readIssueText(tree.rootIssue) }),
3159
3253
  " ",
3160
3254
  /* @__PURE__ */ jsx5(
3161
3255
  "button",
@@ -3169,7 +3263,7 @@ function FileExplorer({
3169
3263
  ]
3170
3264
  }
3171
3265
  ),
3172
- tree.loading ? /* @__PURE__ */ jsx5("div", { className: "db-tree", role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsx5("div", { className: "db-tree-loading", children: "Loading..." }) }) : filterVisibleFileEntries(tree.entries).length === 0 ? /* @__PURE__ */ jsx5("div", { className: "db-tree", children: /* @__PURE__ */ jsx5("div", { className: "db-tree-empty", children: "No files yet" }) }) : /* @__PURE__ */ jsx5(
3266
+ tree.loading || loading ? /* @__PURE__ */ jsx5("div", { className: "db-tree", role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsx5("div", { className: "db-tree-loading", children: "Loading..." }) }) : filterVisibleFileEntries(tree.entries).length === 0 ? /* @__PURE__ */ jsx5("div", { className: "db-tree", children: /* @__PURE__ */ jsx5("div", { className: "db-tree-empty", children: "No files yet" }) }) : /* @__PURE__ */ jsx5(
3173
3267
  "div",
3174
3268
  {
3175
3269
  ref: treeRef,
@@ -3236,6 +3330,7 @@ function WorkspacePicker({
3236
3330
  onCloneRepository,
3237
3331
  onRemoveWorkspace,
3238
3332
  refreshKey,
3333
+ loading = false,
3239
3334
  className
3240
3335
  }) {
3241
3336
  const [workspaces, setWorkspaces] = useState5([]);
@@ -3392,7 +3487,7 @@ function WorkspacePicker({
3392
3487
  }
3393
3488
  }, [newWorkspaceName, newWorkspacePending, onSelect, refresh, workspaces]);
3394
3489
  const activeWs = workspaces.find((w) => w.id === activeWorkspaceId);
3395
- const activeWorkspaceName = activeWs?.name ?? "No workspace";
3490
+ const activeWorkspaceName = activeWs?.name ?? (loading ? "Loading\u2026" : "No workspace");
3396
3491
  return /* @__PURE__ */ jsxs5("div", { ref: pickerRef, className: `db-workspace-picker ${className ?? ""}`, children: [
3397
3492
  /* @__PURE__ */ jsxs5(
3398
3493
  "button",
@@ -3679,13 +3774,15 @@ import {
3679
3774
  FileSystemContentContainer as FileSystemContentContainer2,
3680
3775
  createFileMediaProvider,
3681
3776
  decodeUtf8Text,
3682
- FsError as FsError5,
3683
- getFileSystemProviderV2 as getFileSystemProviderV210,
3777
+ FsError as FsError6,
3778
+ getFileSystemProviderV2 as getFileSystemProviderV211,
3684
3779
  isQuotaExceededError,
3685
3780
  moveFileSystemEntry as moveFileSystemEntry2,
3686
3781
  rewriteCompanionReferences,
3687
3782
  parseWorkspacePath as parseWorkspacePath8,
3688
- workspacePathContains
3783
+ workspacePathContains,
3784
+ findWikiArticle as findWikiArticle2,
3785
+ suggestWikiArticles
3689
3786
  } from "@bendyline/docblocks/filesystem";
3690
3787
  import {
3691
3788
  createFileSystemDocumentTarget as createFileSystemDocumentTarget2,
@@ -3708,12 +3805,14 @@ import {
3708
3805
  getWorkspace,
3709
3806
  listWorkspaces as listWorkspaces2,
3710
3807
  reconcileElectronWorkspaceDescriptors,
3711
- removeWorkspace,
3712
- saveWorkspace as saveWorkspace2,
3808
+ removeWorkspace as removeWorkspace2,
3809
+ saveWorkspace as saveWorkspace3,
3713
3810
  touchWorkspace as touchWorkspace2,
3714
3811
  registerTransientWorkspace,
3715
3812
  getTransientWorkspace,
3716
- unregisterTransientWorkspace
3813
+ unregisterTransientWorkspace,
3814
+ appendWorkspaceBackup,
3815
+ finishWorkspaceBackup
3717
3816
  } from "@bendyline/docblocks/workspace";
3718
3817
 
3719
3818
  // src/AppMenu/AppMenu.tsx
@@ -4034,17 +4133,6 @@ function SpeechSettingsControls({ speech }) {
4034
4133
  ] });
4035
4134
  }
4036
4135
 
4037
- // src/local-date.ts
4038
- function localDateStamp(date) {
4039
- const pad = (value) => String(value).padStart(2, "0");
4040
- return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
4041
- }
4042
- function displayBuildDate(value) {
4043
- if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
4044
- const instant = new Date(value);
4045
- return Number.isNaN(instant.getTime()) ? value : localDateStamp(instant);
4046
- }
4047
-
4048
4136
  // src/AppMenu/AppMenu.tsx
4049
4137
  import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
4050
4138
  function formatBytes(bytes) {
@@ -4081,6 +4169,7 @@ function AppMenu({
4081
4169
  onVersioningPreferenceChange,
4082
4170
  onOpenWorkspaceSettings,
4083
4171
  onDownloadAllWorkspaces,
4172
+ onRestoreBackup,
4084
4173
  onShowKeyboardShortcuts,
4085
4174
  onKeepBrowserData,
4086
4175
  onInstallApp,
@@ -4099,7 +4188,8 @@ function AppMenu({
4099
4188
  if (settingsRequest > 0) setShowSettings(true);
4100
4189
  }, [settingsRequest]);
4101
4190
  const [requestingPersistentStorage, setRequestingPersistentStorage] = useState7(false);
4102
- const moreInformationUrl = MORE_INFORMATION_URLS[getHostEnvironment()?.surface ?? "web"] ?? MORE_INFORMATION_URLS.web;
4191
+ const surface = getHostEnvironment()?.surface ?? "web";
4192
+ const moreInformationUrl = MORE_INFORMATION_URLS[surface] ?? MORE_INFORMATION_URLS.web;
4103
4193
  const containerRef = useRef8(null);
4104
4194
  const { menuRef, triggerRef, handleMenuKeyDown, handleTriggerKeyDown, closeMenu } = useMenuKeyboard(isOpen, setIsOpen);
4105
4195
  const [storageEstimate, setStorageEstimate] = useState7(null);
@@ -4214,7 +4304,7 @@ function AppMenu({
4214
4304
  children: "Install DocBlocks\u2026"
4215
4305
  }
4216
4306
  ),
4217
- (onDownloadAllWorkspaces || onKeepBrowserData) && /* @__PURE__ */ jsx8("div", { className: "db-app-menu-divider" }),
4307
+ (onDownloadAllWorkspaces || onRestoreBackup || onKeepBrowserData) && /* @__PURE__ */ jsx8("div", { className: "db-app-menu-divider" }),
4218
4308
  onDownloadAllWorkspaces && /* @__PURE__ */ jsx8(
4219
4309
  "button",
4220
4310
  {
@@ -4225,6 +4315,16 @@ function AppMenu({
4225
4315
  children: "Back up all workspaces"
4226
4316
  }
4227
4317
  ),
4318
+ onRestoreBackup && /* @__PURE__ */ jsx8(
4319
+ "button",
4320
+ {
4321
+ className: "db-app-menu-item",
4322
+ role: "menuitem",
4323
+ tabIndex: -1,
4324
+ onClick: () => handleAction(onRestoreBackup),
4325
+ children: "Restore backup\u2026"
4326
+ }
4327
+ ),
4228
4328
  onKeepBrowserData && /* @__PURE__ */ jsx8(
4229
4329
  "button",
4230
4330
  {
@@ -4400,22 +4500,6 @@ function AppMenu({
4400
4500
  /* @__PURE__ */ jsx8("strong", { children: "DocBlocks" }),
4401
4501
  " is a local-first Markdown document editor. Your files stay under your control."
4402
4502
  ] }),
4403
- /* @__PURE__ */ jsxs7("aside", { className: "db-about-beta", children: [
4404
- /* @__PURE__ */ jsx8("strong", { children: "Beta Software." }),
4405
- " We're still working through initial hiccups and issues. Please bear with us, make sure you keep backups, and",
4406
- " ",
4407
- /* @__PURE__ */ jsx8(
4408
- "a",
4409
- {
4410
- href: "https://github.com/bendyline/docblocks/issues/new",
4411
- target: "_blank",
4412
- rel: "noopener noreferrer",
4413
- children: "open issues"
4414
- }
4415
- ),
4416
- " ",
4417
- "where you find them. Thanks!"
4418
- ] }),
4419
4503
  appVersion && /* @__PURE__ */ jsxs7("p", { className: "db-about-version", children: [
4420
4504
  /* @__PURE__ */ jsx8("span", { children: "Version" }),
4421
4505
  /* @__PURE__ */ jsx8("code", { "aria-label": `DocBlocks version ${appVersion}`, children: appVersion })
@@ -4434,6 +4518,12 @@ function AppMenu({
4434
4518
  /* @__PURE__ */ jsx8("a", { href: "https://bendyline.com", target: "_blank", rel: "noopener noreferrer", children: "Bendyline" }),
4435
4519
  "."
4436
4520
  ] }),
4521
+ surface === "web" && /* @__PURE__ */ jsxs7("p", { children: [
4522
+ "See our free desktop apps at",
4523
+ " ",
4524
+ /* @__PURE__ */ jsx8("a", { href: MORE_INFORMATION_URLS.electron, target: "_blank", rel: "noopener noreferrer", children: "docblocks.com/desktop" }),
4525
+ "."
4526
+ ] }),
4437
4527
  /* @__PURE__ */ jsxs7("p", { className: "db-dialog-links", children: [
4438
4528
  /* @__PURE__ */ jsx8("a", { href: moreInformationUrl, target: "_blank", rel: "noopener noreferrer", children: "More information..." }),
4439
4529
  /* @__PURE__ */ jsx8(
@@ -4486,6 +4576,59 @@ function AppMenu({
4486
4576
  ] });
4487
4577
  }
4488
4578
 
4579
+ // src/DocBlocksShell/restore-backup.ts
4580
+ import {
4581
+ readWorkspaceBackup,
4582
+ restoreWorkspaceBackup,
4583
+ WORKSPACE_BACKUP_LIMITS,
4584
+ saveWorkspace as saveWorkspace2,
4585
+ removeWorkspace
4586
+ } from "@bendyline/docblocks/workspace";
4587
+ async function restoreBrowserBackup(file) {
4588
+ if (file.size > 256 * 1024 * 1024) throw new Error("Backup ZIPs must be smaller than 256 MiB.");
4589
+ const { zipToContainer } = await import("@bendyline/squisq-formats/container");
4590
+ const container = await zipToContainer(await file.arrayBuffer(), WORKSPACE_BACKUP_LIMITS);
4591
+ const workspaces = await readWorkspaceBackup(
4592
+ container,
4593
+ file.name.replace(/\.zip$/i, "") || "Workspace"
4594
+ );
4595
+ const { IndexedDBFileSystemProvider, deleteIndexedDBWorkspaceData } = await import("@bendyline/docblocks/filesystem/indexeddb");
4596
+ const staged = [];
4597
+ try {
4598
+ for (const workspace of workspaces) {
4599
+ const descriptor = {
4600
+ id: `restored-${crypto.randomUUID()}`,
4601
+ name: `${workspace.name.slice(0, 1013)} (restored)`,
4602
+ type: "indexeddb",
4603
+ lastOpened: (/* @__PURE__ */ new Date()).toISOString()
4604
+ };
4605
+ const provider = new IndexedDBFileSystemProvider(descriptor.id, descriptor.name).v2;
4606
+ staged.push({ descriptor, provider });
4607
+ await restoreWorkspaceBackup(workspace, provider);
4608
+ }
4609
+ for (const { descriptor } of staged) await saveWorkspace2(descriptor);
4610
+ return staged.map(({ descriptor }) => descriptor);
4611
+ } catch (error) {
4612
+ const failures = [error];
4613
+ for (const { descriptor, provider } of staged) {
4614
+ try {
4615
+ await provider.dispose();
4616
+ await removeWorkspace(descriptor.id);
4617
+ await deleteIndexedDBWorkspaceData(descriptor.id);
4618
+ } catch (cleanupError) {
4619
+ failures.push(cleanupError);
4620
+ }
4621
+ }
4622
+ if (failures.length > 1)
4623
+ throw new Error(
4624
+ `Restore failed; temporary workspace cleanup also failed. ${failures.map((failure) => failure instanceof Error ? failure.message : String(failure)).join("; ")}`
4625
+ );
4626
+ throw error;
4627
+ } finally {
4628
+ for (const { provider } of staged) await provider.dispose();
4629
+ }
4630
+ }
4631
+
4489
4632
  // src/WorkspacePicker/WorkspaceSettingsButton.tsx
4490
4633
  import { useState as useState8, useCallback as useCallback9, useRef as useRef9, useEffect as useEffect9 } from "react";
4491
4634
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
@@ -5008,7 +5151,7 @@ function useDocumentSession(autoSaveDelayMs = 500) {
5008
5151
  import { lazy, Suspense } from "react";
5009
5152
  import { jsx as jsx12 } from "react/jsx-runtime";
5010
5153
  var ExportToolbarControlsImplementation = lazy(
5011
- () => import("./ExportToolbarControls-2FEIHNMW.js").then((module) => ({
5154
+ () => import("./ExportToolbarControls-HOV424TO.js").then((module) => ({
5012
5155
  default: module.ExportToolbarControls
5013
5156
  }))
5014
5157
  );
@@ -5578,7 +5721,7 @@ function resolveShellEditorLinkTarget(href, documentPath) {
5578
5721
  targetSegments.push(segment);
5579
5722
  }
5580
5723
  const path = targetSegments.join("/");
5581
- if (!path || path.length > HOST_WIRE_LIMITS2.pathCharacters || !isMarkdownFileName(path)) {
5724
+ if (!path || path.length > HOST_WIRE_LIMITS2.pathCharacters || !isMarkdownFileName(path) && !/\.html?$/i.test(path)) {
5582
5725
  return null;
5583
5726
  }
5584
5727
  return { kind: "workspace", path };
@@ -5601,10 +5744,36 @@ function hasControlCharacter(value) {
5601
5744
  return false;
5602
5745
  }
5603
5746
 
5604
- // src/DocBlocksShell/import-files.ts
5747
+ // src/DocBlocksShell/wiki-articles.ts
5605
5748
  import {
5749
+ ensureWikiArticle,
5750
+ wikiArticleExists,
5606
5751
  FsError as FsError4,
5607
- getFileSystemProviderV2 as getFileSystemProviderV27,
5752
+ getFileSystemProviderV2 as getFileSystemProviderV27
5753
+ } from "@bendyline/docblocks/filesystem";
5754
+ function wikiArticleExtension(format) {
5755
+ return newFileHtmlOutput(format) ? ".html" : ".md";
5756
+ }
5757
+ async function ensureShellWikiArticle(provider, path, format, render) {
5758
+ if (!/\.html?$/i.test(path)) return ensureWikiArticle(provider, path);
5759
+ if (await wikiArticleExists(provider, path)) return false;
5760
+ if (!getFileSystemProviderV27(provider)) {
5761
+ throw new FsError4("not-supported", "This workspace cannot create articles safely.");
5762
+ }
5763
+ try {
5764
+ await createNewOutsideInDocument(provider, path, newFileHtmlOutput(format), render);
5765
+ return true;
5766
+ } catch (error) {
5767
+ if (error instanceof FsError4 && (error.code === "already-exists" || error.code === "conflict") && await wikiArticleExists(provider, path))
5768
+ return false;
5769
+ throw error;
5770
+ }
5771
+ }
5772
+
5773
+ // src/DocBlocksShell/import-files.ts
5774
+ import {
5775
+ FsError as FsError5,
5776
+ getFileSystemProviderV2 as getFileSystemProviderV28,
5608
5777
  parseWorkspacePath as parseWorkspacePath6
5609
5778
  } from "@bendyline/docblocks/filesystem";
5610
5779
  var MAX_NAME_ATTEMPTS = 100;
@@ -5615,7 +5784,7 @@ function withCopySuffix(path, n) {
5615
5784
  return `${path.slice(0, dot)} (${n})${path.slice(dot)}`;
5616
5785
  }
5617
5786
  function isAlreadyExists(error) {
5618
- return error instanceof FsError4 && error.code === "already-exists";
5787
+ return error instanceof FsError5 && error.code === "already-exists";
5619
5788
  }
5620
5789
  async function claimAvailablePath(provider, desiredPath) {
5621
5790
  for (let attempt = 1; attempt <= MAX_NAME_ATTEMPTS; attempt++) {
@@ -5630,7 +5799,7 @@ async function claimAvailablePath(provider, desiredPath) {
5630
5799
  throw new Error(`Too many documents are already named like \u201C${desiredPath}\u201D.`);
5631
5800
  }
5632
5801
  async function writeProviderBytes(provider, path, data, mode) {
5633
- const providerV2 = getFileSystemProviderV27(provider);
5802
+ const providerV2 = getFileSystemProviderV28(provider);
5634
5803
  if (providerV2) {
5635
5804
  await providerV2.writeFile(parseWorkspacePath6(path), data, {
5636
5805
  mode,
@@ -5640,7 +5809,7 @@ async function writeProviderBytes(provider, path, data, mode) {
5640
5809
  return;
5641
5810
  }
5642
5811
  if (mode === "create" && await providerEntryExists(provider, path)) {
5643
- throw new FsError4("already-exists", "File already exists.", { operation: "write", path });
5812
+ throw new FsError5("already-exists", "File already exists.", { operation: "write", path });
5644
5813
  }
5645
5814
  await provider.writeBinary(path, data);
5646
5815
  }
@@ -5673,7 +5842,7 @@ function directoriesUnder(files, root) {
5673
5842
  return [...directories].sort((a, b) => b.split("/").length - a.split("/").length);
5674
5843
  }
5675
5844
  async function removeIfEmptyDirectory(provider, path) {
5676
- const providerV2 = getFileSystemProviderV27(provider);
5845
+ const providerV2 = getFileSystemProviderV28(provider);
5677
5846
  if (!providerV2) return;
5678
5847
  await providerV2.remove(parseWorkspacePath6(path), { recursive: false, missing: "ignore" }).catch(() => void 0);
5679
5848
  }
@@ -6050,37 +6219,8 @@ function resolveShellTheme({
6050
6219
  return osTheme;
6051
6220
  }
6052
6221
 
6053
- // src/DocBlocksShell/issue-report.ts
6054
- var NEW_ISSUE_URL = "https://github.com/bendyline/docblocks/issues/new";
6055
- var VERSION_VALUE_LIMIT = 128;
6056
- var USER_AGENT_VALUE_LIMIT = 512;
6057
- function normalizeEnvironmentValue(value, fallback, limit) {
6058
- const normalized = value.replace(/[\r\n]+/g, " ").trim().slice(0, limit);
6059
- return normalized || fallback;
6060
- }
6061
- function buildIssueReportUrl(environment) {
6062
- const body = [
6063
- "## Description",
6064
- "",
6065
- "<!-- What happened, and what did you expect? -->",
6066
- "",
6067
- "## Environment",
6068
- "",
6069
- `- Date: ${localDateStamp(environment.reportedAt)}`,
6070
- `- DocBlocks: ${normalizeEnvironmentValue(environment.version, "unknown", VERSION_VALUE_LIMIT)}`,
6071
- `- User agent: ${normalizeEnvironmentValue(
6072
- environment.userAgent,
6073
- "unavailable",
6074
- USER_AGENT_VALUE_LIMIT
6075
- )}`
6076
- ].join("\n");
6077
- const url = new URL(NEW_ISSUE_URL);
6078
- url.searchParams.set("body", body);
6079
- return url.href;
6080
- }
6081
-
6082
6222
  // src/DocBlocksShell/last-state.ts
6083
- import { tryParseWorkspacePath as tryParseWorkspacePath2 } from "@bendyline/docblocks/filesystem";
6223
+ import { tryParseWorkspacePath as tryParseWorkspacePath3 } from "@bendyline/docblocks/filesystem";
6084
6224
  var LAST_STATE_KEY = "docblocks:lastState";
6085
6225
  var MAX_WORKSPACE_ID_CHARACTERS = 256;
6086
6226
  var MAX_WORKSPACE_PATH_CHARACTERS = 4096;
@@ -6101,7 +6241,7 @@ function parseLastState(value) {
6101
6241
  if (!hasExactKeys(record, ["workspaceId", "filePath", "view"])) return null;
6102
6242
  if (!isBoundedText(record.workspaceId, MAX_WORKSPACE_ID_CHARACTERS)) return null;
6103
6243
  if (!isBoundedText(record.filePath, MAX_WORKSPACE_PATH_CHARACTERS)) return null;
6104
- const parsedPath = tryParseWorkspacePath2(record.filePath);
6244
+ const parsedPath = tryParseWorkspacePath3(record.filePath);
6105
6245
  if (!parsedPath) return null;
6106
6246
  if (typeof record.view !== "string" || !EDITOR_VIEWS.has(record.view)) return null;
6107
6247
  return {
@@ -6733,6 +6873,104 @@ function UpdateAvailableNotice({
6733
6873
  ] });
6734
6874
  }
6735
6875
 
6876
+ // src/DocBlocksShell/WelcomeScreen.tsx
6877
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
6878
+ var WELCOME_HEADLINE = "The Markdown editor that turns one file into anything.";
6879
+ var WELCOME_SUMMARY = "Write the way you normally would, then share the same file as a PDF, a Word document, a slide deck, or a video.";
6880
+ var WELCOME_PRIVACY_NOTE = "No account, and your documents stay on this device.";
6881
+ var DOCBLOCKS_APPS_URL = "https://docblocks.com/desktop/";
6882
+ function WelcomeScreen({
6883
+ onCreateDocument,
6884
+ onTour,
6885
+ onDismiss,
6886
+ offerApps = false
6887
+ }) {
6888
+ return /* @__PURE__ */ jsxs15("section", { className: "db-welcome-gateway", "aria-labelledby": "db-welcome-gateway-title", children: [
6889
+ /* @__PURE__ */ jsx17(
6890
+ "button",
6891
+ {
6892
+ type: "button",
6893
+ className: "db-welcome-gateway-dismiss",
6894
+ onClick: onDismiss,
6895
+ "aria-label": "Dismiss welcome",
6896
+ title: "Dismiss",
6897
+ children: "\xD7"
6898
+ }
6899
+ ),
6900
+ /* @__PURE__ */ jsxs15("div", { className: "db-welcome-gateway-body", children: [
6901
+ /* @__PURE__ */ jsxs15("div", { className: "db-welcome-gateway-copy", children: [
6902
+ /* @__PURE__ */ jsx17("p", { className: "db-welcome-gateway-eyebrow", children: "Welcome to DocBlocks" }),
6903
+ /* @__PURE__ */ jsx17("h1", { id: "db-welcome-gateway-title", children: WELCOME_HEADLINE }),
6904
+ /* @__PURE__ */ jsx17("p", { children: WELCOME_SUMMARY }),
6905
+ /* @__PURE__ */ jsx17("p", { className: "db-welcome-gateway-note", children: WELCOME_PRIVACY_NOTE }),
6906
+ /* @__PURE__ */ jsxs15("div", { className: "db-welcome-gateway-actions", children: [
6907
+ /* @__PURE__ */ jsx17("button", { type: "button", className: "db-welcome-gateway-primary", onClick: onCreateDocument, children: "Create a document" }),
6908
+ /* @__PURE__ */ jsx17("button", { type: "button", className: "db-welcome-gateway-cta", onClick: onTour, children: "Tour the welcome document" }),
6909
+ offerApps && /* @__PURE__ */ jsx17(
6910
+ "a",
6911
+ {
6912
+ className: "db-welcome-gateway-apps",
6913
+ href: DOCBLOCKS_APPS_URL,
6914
+ target: "_blank",
6915
+ rel: "noopener noreferrer",
6916
+ children: "Check out the DocBlocks apps"
6917
+ }
6918
+ )
6919
+ ] })
6920
+ ] }),
6921
+ /* @__PURE__ */ jsx17(WelcomeLoop, {})
6922
+ ] })
6923
+ ] });
6924
+ }
6925
+ function WelcomeLoop() {
6926
+ return /* @__PURE__ */ jsxs15("div", { className: "db-welcome-loop", "aria-hidden": "true", children: [
6927
+ /* @__PURE__ */ jsxs15("div", { className: "db-welcome-loop-source", children: [
6928
+ /* @__PURE__ */ jsx17("span", { className: "db-welcome-loop-file", children: "Lisbon.md" }),
6929
+ /* @__PURE__ */ jsxs15("code", { children: [
6930
+ /* @__PURE__ */ jsx17("span", { className: "db-welcome-loop-mark", children: "# " }),
6931
+ "Lisbon in three days",
6932
+ "\n",
6933
+ "Trams, tiles, and pastries.",
6934
+ "\n\n",
6935
+ /* @__PURE__ */ jsx17("span", { className: "db-welcome-loop-mark", children: "## " }),
6936
+ "Day one",
6937
+ "\n",
6938
+ /* @__PURE__ */ jsx17("span", { className: "db-welcome-loop-mark", children: "- " }),
6939
+ "Ride Tram 28",
6940
+ "\n",
6941
+ /* @__PURE__ */ jsx17("span", { className: "db-welcome-loop-mark", children: "- " }),
6942
+ "Sunset at Gra\xE7a"
6943
+ ] })
6944
+ ] }),
6945
+ /* @__PURE__ */ jsx17("div", { className: "db-welcome-loop-arrow", children: "\u2192" }),
6946
+ /* @__PURE__ */ jsxs15("div", { className: "db-welcome-loop-stage", children: [
6947
+ /* @__PURE__ */ jsxs15("div", { className: "db-welcome-loop-frame db-welcome-loop-frame--page", children: [
6948
+ /* @__PURE__ */ jsx17("strong", { children: "Lisbon in three days" }),
6949
+ /* @__PURE__ */ jsx17("span", { children: "Trams, tiles, and pastries." }),
6950
+ /* @__PURE__ */ jsx17("em", { children: "Day one" }),
6951
+ /* @__PURE__ */ jsx17("span", { children: "\u2022 Ride Tram 28" }),
6952
+ /* @__PURE__ */ jsx17("span", { children: "\u2022 Sunset at Gra\xE7a" })
6953
+ ] }),
6954
+ /* @__PURE__ */ jsxs15("div", { className: "db-welcome-loop-frame db-welcome-loop-frame--slide", children: [
6955
+ /* @__PURE__ */ jsx17("strong", { children: "Lisbon in three days" }),
6956
+ /* @__PURE__ */ jsx17("span", { children: "Trams, tiles, and pastries." })
6957
+ ] }),
6958
+ /* @__PURE__ */ jsxs15("div", { className: "db-welcome-loop-frame db-welcome-loop-frame--video", children: [
6959
+ /* @__PURE__ */ jsx17("em", { children: "Day one" }),
6960
+ /* @__PURE__ */ jsx17("span", { children: "\u2022 Ride Tram 28" }),
6961
+ /* @__PURE__ */ jsx17("span", { children: "\u2022 Sunset at Gra\xE7a" }),
6962
+ /* @__PURE__ */ jsx17("i", { className: "db-welcome-loop-play", children: "\u25B6" }),
6963
+ /* @__PURE__ */ jsx17("i", { className: "db-welcome-loop-progress" })
6964
+ ] })
6965
+ ] }),
6966
+ /* @__PURE__ */ jsxs15("ol", { className: "db-welcome-loop-labels", children: [
6967
+ /* @__PURE__ */ jsx17("li", { children: "PDF" }),
6968
+ /* @__PURE__ */ jsx17("li", { children: "Slides" }),
6969
+ /* @__PURE__ */ jsx17("li", { children: "Video" })
6970
+ ] })
6971
+ ] });
6972
+ }
6973
+
6736
6974
  // src/DocBlocksShell/useDocumentLinkProvider.ts
6737
6975
  import { useCallback as useCallback16 } from "react";
6738
6976
 
@@ -6797,7 +7035,7 @@ function useDocumentLinkProvider(index, selectedFile, ensureIndex) {
6797
7035
 
6798
7036
  // src/WorkspaceIndex/useWorkspaceIndex.ts
6799
7037
  import { useCallback as useCallback17, useEffect as useEffect28, useMemo as useMemo5, useRef as useRef18, useState as useState22, useSyncExternalStore as useSyncExternalStore4 } from "react";
6800
- import { getFileSystemProviderV2 as getFileSystemProviderV28 } from "@bendyline/docblocks/filesystem";
7038
+ import { getFileSystemProviderV2 as getFileSystemProviderV29 } from "@bendyline/docblocks/filesystem";
6801
7039
  import { maybeGetDocBlocksHost as maybeGetDocBlocksHost3 } from "@bendyline/docblocks/host";
6802
7040
  var IDLE_WORKSPACE_INDEX_STATUS = Object.freeze({
6803
7041
  phase: "idle",
@@ -6827,7 +7065,7 @@ function whenIdle(callback) {
6827
7065
  return () => clearTimeout(handle);
6828
7066
  }
6829
7067
  function useWorkspaceIndex(provider, workspaceId, mode = "eager") {
6830
- const providerV2 = provider && mode !== "off" ? getFileSystemProviderV28(provider) : null;
7068
+ const providerV2 = provider && mode !== "off" ? getFileSystemProviderV29(provider) : null;
6831
7069
  const [index, setIndex] = useState22(null);
6832
7070
  const startRequestedRef = useRef18(false);
6833
7071
  useEffect28(() => {
@@ -6890,7 +7128,7 @@ function useWorkspaceIndex(provider, workspaceId, mode = "eager") {
6890
7128
  }
6891
7129
 
6892
7130
  // src/WorkspaceIndex/recent-documents.ts
6893
- import { tryParseWorkspacePath as tryParseWorkspacePath3 } from "@bendyline/docblocks/filesystem";
7131
+ import { tryParseWorkspacePath as tryParseWorkspacePath4 } from "@bendyline/docblocks/filesystem";
6894
7132
  var RECENT_DOCUMENTS_STORAGE_KEY = "docblocks:recentDocuments:v1";
6895
7133
  var MAX_PATHS_PER_WORKSPACE = 50;
6896
7134
  var MAX_WORKSPACES = 50;
@@ -6909,7 +7147,7 @@ function defaultStorage() {
6909
7147
  }
6910
7148
  function canonicalPath(path) {
6911
7149
  if (path.length === 0 || path.length > MAX_PATH_LENGTH) return null;
6912
- return tryParseWorkspacePath3(path) || null;
7150
+ return tryParseWorkspacePath4(path) || null;
6913
7151
  }
6914
7152
  function parseRecentDocuments(value) {
6915
7153
  const parsed = /* @__PURE__ */ new Map();
@@ -7015,14 +7253,14 @@ var WorkspaceAuthorityBarrier = class {
7015
7253
 
7016
7254
  // src/DocBlocksShell/transient-workspace-move.ts
7017
7255
  import {
7018
- getFileSystemProviderV2 as getFileSystemProviderV29
7256
+ getFileSystemProviderV2 as getFileSystemProviderV210
7019
7257
  } from "@bendyline/docblocks/filesystem";
7020
7258
  function pathDepth(path) {
7021
7259
  return path ? path.split("/").length : 0;
7022
7260
  }
7023
7261
  async function copyTransientWorkspaceContents(source, destination) {
7024
- const sourceV2 = getFileSystemProviderV29(source);
7025
- const destinationV2 = getFileSystemProviderV29(destination);
7262
+ const sourceV2 = getFileSystemProviderV210(source);
7263
+ const destinationV2 = getFileSystemProviderV210(destination);
7026
7264
  if (!sourceV2 || !destinationV2) {
7027
7265
  throw new Error("Moving a temporary document requires a current workspace provider.");
7028
7266
  }
@@ -7107,7 +7345,15 @@ function createNativeFileActions(entry, workspaceId, host) {
7107
7345
 
7108
7346
  // src/DocBlocksShell/welcome-document.ts
7109
7347
  import { DEFAULT_WORKSPACE_ID } from "@bendyline/docblocks/workspace";
7110
- var WELCOME_DOCUMENT_PATH = "/aboutDocBlocks.md";
7348
+ var WELCOME_DOCUMENT_PATH = "/Welcome.md";
7349
+ var WELCOME_DOCUMENT_NAMES = /* @__PURE__ */ new Set([
7350
+ "welcome.md",
7351
+ // Seeded as `aboutDocBlocks.md` through 2.7 (`aboutDocblocks.md` before that).
7352
+ "aboutdocblocks.md"
7353
+ ]);
7354
+ function isWelcomeDocumentPath(path) {
7355
+ return WELCOME_DOCUMENT_NAMES.has(path.replace(/^\//, "").toLowerCase());
7356
+ }
7111
7357
  function isAppDefaultWorkspace(workspaceId, hostDefaultWorkspaceIds) {
7112
7358
  return hostDefaultWorkspaceIds ? hostDefaultWorkspaceIds.has(workspaceId) : workspaceId === DEFAULT_WORKSPACE_ID;
7113
7359
  }
@@ -7117,126 +7363,57 @@ var WELCOME_DOCUMENT_CONTENT = [
7117
7363
  "squisq-cover-slide: false",
7118
7364
  "---",
7119
7365
  "",
7120
- '# DocBlocks {[title subtitle="Local-first writing for pages, documents, slideshows, and video"]}',
7121
- "",
7122
- "Write in plain Markdown. Shape it visually. Keep it portable.",
7123
- "",
7124
- "## A document editor that never asks you to stop owning the document {[factCard]}",
7125
- "",
7126
- "DocBlocks pairs a friendly visual writing surface with the durable source format you already know. There is no account to create, no proprietary document to rescue later, and no requirement to send your work to a hosted editor.",
7127
- "",
7128
- "## Start with three small moves {[list]}",
7129
- "",
7130
- "1. Choose **New file** in the file pane and give your document a name, such as `my-first-document.md`. On a small screen, open the file pane first.",
7131
- "2. Add a heading and a few sentences in **Write**, then switch to **Source** to see the Markdown behind them. Both views edit the same document.",
7132
- "3. Try a Document, Page, Slideshow, or Video preview, then open **Export and share** to make a PDF, Word document, or another supported file.",
7133
- "",
7134
- '## Choose your DocBlocks app {[twoColumn header="The same Markdown, wherever you work" left="Web|Start in a browser, with no account. Install the web app when your browser offers it." right="Desktop|Download for macOS (Apple silicon), Windows, or Linux. Work directly in your folders."]}',
7366
+ '# Welcome to DocBlocks {[title subtitle="Write once. Share it as a document, a deck, or a video."]}',
7135
7367
  "",
7136
- "Choose the **Web app** to start writing in your browser, or **Desktop** for everyday work in local folders on macOS (Apple silicon), Windows, and Linux. Use **VS Code** for documents beside your code, and the **CLI and MCP server** for automation. The links at the end of this guide lead to downloads and setup instructions.",
7368
+ "This is an ordinary document, so go ahead and change it. Click anywhere and type; DocBlocks saves as you go.",
7137
7369
  "",
7138
- "## Get started on desktop {[list]}",
7370
+ "## Write the way you already do {[list]}",
7139
7371
  "",
7140
- "- Download the installer for your computer from the **Desktop app** link at the end of this guide. macOS builds support Apple silicon.",
7141
- "- Launch DocBlocks and choose **Open a folder** to work in a folder you own. Your Markdown and media stay in that folder.",
7142
- "- Open an existing document or create a new file. Edits save automatically; check any save or conflict message before closing.",
7143
- "- Use the same Write, Source, preview, and export workflow as the web app, with native menus and file opening.",
7372
+ "- Type to add text, and use the toolbar for headings, lists, and bold or italic text.",
7373
+ "- Choose **Insert** in the toolbar to add a table, a checklist, a chart, a diagram, or a picture.",
7374
+ "- Choose **Source** to see the Markdown text behind this page. Both views edit the same document.",
7144
7375
  "",
7145
- "## Use the web app on this device {[list]}",
7146
- "",
7147
- "- Open DocBlocks in your browser and start in a browser workspace. In supported browsers, you can also open a local folder.",
7148
- "- Choose **Install DocBlocks\u2026** in the app menu when offered. The installed web app keeps using the same browser storage.",
7149
- "- Once the app has finished downloading for offline use, you can keep editing without a connection.",
7150
- "- Use **Back up all workspaces** in the app menu to keep a backup before clearing browser data or moving to a new device.",
7151
- "",
7152
- "## Work inside VS Code {[content]}",
7153
- "",
7154
- "Install the DocBlocks extension, then open a `.md` file in your project. If it opens as plain text, right-click it and choose **Open in DocBlocks**. VS Code supplies the file explorer, tabs, saving, and source control; DocBlocks supplies the visual editor, Markdown source, previews, and export. Follow the extension guide below to get started.",
7155
- "",
7156
- "## On a phone or tablet {[content]}",
7157
- "",
7158
- "Use the web app on your phone or tablet today. Native apps for iPhone, iPad, and Android are in preview, with store distribution pending. Preview builds offer an **On this device** workspace, picked folders, and system sharing. Keep an exported copy of important work: uninstalling a native app removes its private device workspace.",
7159
- "",
7160
- '## Automate with the CLI and MCP {[twoColumn header="For repeatable document work" left="CLI|Install @bendyline/docblocks-cli with npm, then run docblocks --help. Build, preview, and convert files from your terminal." right="MCP|Connect the local server to an agent. Convert and preview documents, then deliberately save the resulting artifacts."]}',
7161
- "",
7162
- "With a supported version of `Node.js` installed, run `npm install -g @bendyline/docblocks-cli`, then `docblocks --help`. The CLI builds, previews, and converts files from your terminal. Its local MCP server gives agents document tools with explicitly allowed file access. Follow the CLI and MCP guide below for requirements and configuration.",
7163
- "",
7164
- "## One source, four ways to publish {[diagram]}",
7376
+ "## One document, many ways to share it {[diagram]}",
7165
7377
  "",
7166
7378
  "```diagram",
7167
- "+------------+",
7168
- "| Markdown |",
7169
- "+-----+------+",
7170
- " |",
7171
- " +---+------+------------+-----------+",
7172
- " | | | |",
7173
- " v v v v",
7174
- "+-------+ +---------+ +------------+ +-------+",
7175
- "| Pages | |Documents| | Slideshows | | Video |",
7176
- "+-------+ +---------+ +------------+ +-------+",
7379
+ " +-----------------+",
7380
+ " | Your document |",
7381
+ " +--------+--------+",
7382
+ " |",
7383
+ " +-----------+------+-----+------------+",
7384
+ " | | | |",
7385
+ " v v v v",
7386
+ "+-------+ +--------+ +----------+ +---------+",
7387
+ "| PDF | | Word | | Slides | | Video |",
7388
+ "+-------+ +--------+ +----------+ +---------+",
7177
7389
  "```",
7178
7390
  "",
7179
- "## Write visually, then look under the hood whenever you like {[content]}",
7180
- "",
7181
- "Use the rich editor for headings, lists, tables, links, images, media, and everyday formatting. Switch to raw Markdown for exact control. Add themes, transform styles, and squiggly-square section annotations when a passage deserves its own visual treatment. The readable `.md` source stays at the center of the workflow.",
7182
- "",
7183
- "## Preview the finished idea before you export it {[list]}",
7184
- "",
7185
- "- **Document** \u2014 Read the work as a polished, continuous document.",
7186
- "- **Page** \u2014 See how it behaves as a web page.",
7187
- "- **Slideshow** \u2014 Step through headings as designed presentation blocks.",
7188
- "- **Video** \u2014 Rehearse timing, motion, narration, and media in a player-ready view.",
7189
- "",
7190
- "## Try the optional desktop tools {[list]}",
7191
- "",
7192
- "- **AI assistance** can help rewrite, review, and illustrate a document. In **Settings \u2192 AI assistance**, turn on **Use AI features** and choose an available model. AI is off by default; model downloads start only when you request them.",
7193
- "- **Dictation and read aloud** run on your computer. Where available, open **Settings \u2192 Speech** to download a dictation model or narration voices, then use the microphone or read-aloud controls.",
7194
- "- These tools depend on the app, device, and installed models. Writing, previewing, and exporting work without setting them up.",
7195
- "",
7196
- "## The quiet conveniences are there, too {[list]}",
7197
- "",
7198
- "- **Workspaces and file navigation** keep related documents organized without hiding their source.",
7199
- "- **Automatic saving** protects the active revision while still surfacing conflicts or save errors.",
7200
- "- **Optional version history** keeps plain sibling revisions when you want a trail of changes.",
7201
- "- **Copy-by-link sharing** puts a Markdown copy in a link that opens straight into a Document, Page, Slideshow, or Video view.",
7202
- "- **Offline-ready, local-first storage** keeps browser work on this device and native work inside folders you choose.",
7203
- "",
7204
- "## Export without rebuilding the document {[list]}",
7205
- "",
7206
- "- Create **Word documents and PDFs** for review, printing, and sharing with a fixed page layout.",
7207
- "- Create **PowerPoint decks** from the same annotated sections you preview as a slideshow.",
7208
- "- Publish **HTML pages, packaged HTML, and EPUB e-books**.",
7209
- "- Keep portable **Markdown or DBK bundles** with their media assets.",
7210
- "- Use the linked CLI registry for **spreadsheets, MP4 video, animated GIF, batch conversion, and more**.",
7391
+ "## Present it or send it {[content]}",
7211
7392
  "",
7212
- "## Local-first is concrete, not decorative {[quote]}",
7393
+ "Each heading starts a new slide, so this document is already a slideshow. Choose **Slideshow** at the top to see it, or **Export** to save it as a PDF, a Word document, a PowerPoint deck, an e-book, a web page, or a video.",
7213
7394
  "",
7214
- "> Your browser workspace stays on this device. Native folders are opened only when you choose them. Your Markdown remains useful in DocBlocks, Git, a terminal, another editor, or an AI-assisted workflow.",
7395
+ "## Your documents stay yours {[factCard]}",
7215
7396
  "",
7216
- "## Agents get document tools, not a blank check {[factCard]}",
7397
+ "There is no account to create. Your documents stay on this device as plain text files that other apps can open too. Choose **Back up** at the bottom of the file list to keep a copy somewhere safe.",
7217
7398
  "",
7218
- "The local MCP server converts plain text or Markdown directly and can optionally inspect, preview, or compare documents. Results remain temporary session artifacts until they are deliberately saved, and filesystem access begins with explicit roots rather than assumed authority.",
7399
+ "## Make your own {[list]}",
7219
7400
  "",
7220
- "## Take your documents to another app {[content]}",
7401
+ "1. Choose **New File** above the file list and give it a name.",
7402
+ "2. Write a heading and a few sentences.",
7403
+ "3. Choose **Slideshow** to present it, or **Export** to share it.",
7221
7404
  "",
7222
- "DocBlocks does not automatically sync workspaces between apps or devices. Open the same folder in Desktop or VS Code, or export Markdown and its media to move your work. A DBK bundle keeps documents and media together for another DocBlocks app. A share link contains a snapshot; later edits do not update that copy.",
7405
+ "## Go further {[list]}",
7223
7406
  "",
7224
- "## Explore DocBlocks {[list]}",
7225
- "",
7226
- "- [Desktop app](https://docblocks.com/desktop/) \u2014 downloads and first steps for macOS (Apple silicon), Windows, and Linux.",
7227
- "- [Web app](https://docblocks.com/web/) \u2014 browser storage, installation, and offline use.",
7228
- "- [Mobile preview](https://docblocks.com/docs/#mobile-preview) \u2014 iPhone, iPad, and Android status and build guide.",
7229
- "- [VS Code extension](https://docblocks.com/vscode/) \u2014 rich Markdown editing inside a workspace.",
7230
- "- [CLI and MCP server](https://docblocks.com/cli/) \u2014 conversion, rendering, and agent workflows.",
7231
- "- [Supported formats](https://docblocks.com/formats/) \u2014 current import and export directions.",
7232
- "- [Documentation](https://docblocks.com/docs/) \u2014 compare the apps and follow the getting-started guides.",
7407
+ "- [Get the desktop app](https://docblocks.com/desktop/) to work in folders on your computer.",
7408
+ "- [Add DocBlocks to VS Code](https://docblocks.com/vscode/) to edit documents beside your code.",
7409
+ "- [Read the guides](https://docblocks.com/docs/) for everything else.",
7233
7410
  // End with a newline, as the Write view saves every document. Without it,
7234
7411
  // merely opening the welcome document in Write rewrote the file.
7235
7412
  ""
7236
7413
  ].join("\n");
7237
7414
 
7238
7415
  // src/DocBlocksShell/DocBlocksShell.tsx
7239
- import { Fragment as Fragment11, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
7416
+ import { Fragment as Fragment11, jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
7240
7417
  var editorShellModulePromise = null;
7241
7418
  function loadEditorShell() {
7242
7419
  editorShellModulePromise ?? (editorShellModulePromise = (async () => {
@@ -7248,13 +7425,13 @@ function loadEditorShell() {
7248
7425
  return editorShellModulePromise;
7249
7426
  }
7250
7427
  var EditorShell = lazy2(loadEditorShell);
7251
- var DocumentConflictDialog = lazy2(() => import("./DocumentConflictDialog-5LRVIGXS.js"));
7252
- var GitUI = lazy2(() => import("./GitUI-RCWUZVS6.js").then((m) => ({ default: m.GitUI })));
7428
+ var DocumentConflictDialog = lazy2(() => import("./DocumentConflictDialog-DZ2BV2IH.js"));
7429
+ var GitUI = lazy2(() => import("./GitUI-F3CNBURE.js").then((m) => ({ default: m.GitUI })));
7253
7430
  var QuickOpenDialog = lazy2(
7254
- () => import("./QuickOpenDialog-2KK4SEBU.js").then((m) => ({ default: m.QuickOpenDialog }))
7431
+ () => import("./QuickOpenDialog-ENVFGIHS.js").then((m) => ({ default: m.QuickOpenDialog }))
7255
7432
  );
7256
7433
  var KeyboardShortcutsDialog = lazy2(
7257
- () => import("./KeyboardShortcutsDialog-NOD4P25G.js").then((m) => ({ default: m.KeyboardShortcutsDialog }))
7434
+ () => import("./KeyboardShortcutsDialog-HVKDFP5Z.js").then((m) => ({ default: m.KeyboardShortcutsDialog }))
7258
7435
  );
7259
7436
  var WorkspaceSearchPanel = lazy2(
7260
7437
  () => import("./WorkspaceSearchPanel-J6BKEZQJ.js").then((m) => ({
@@ -7268,37 +7445,37 @@ var GitToolbarControl = lazy2(
7268
7445
  () => import("./GitToolbarControl-B7KG4MFP.js").then((m) => ({ default: m.GitToolbarControl }))
7269
7446
  );
7270
7447
  var AiToolbarControl = lazy2(
7271
- () => import("./AiEditorAssistant-GN2KXKZD.js").then((module) => ({
7448
+ () => import("./AiEditorAssistant-4HFFGVU4.js").then((module) => ({
7272
7449
  default: module.AiToolbarControl
7273
7450
  }))
7274
7451
  );
7275
7452
  var PresentationMaker = lazy2(
7276
- () => import("./PresentationMaker-ITNP5SDO.js").then((module) => ({
7453
+ () => import("./PresentationMaker-MUMFAXEI.js").then((module) => ({
7277
7454
  default: module.PresentationMaker
7278
7455
  }))
7279
7456
  );
7280
7457
  var AiReviewPanel = lazy2(
7281
- () => import("./AiEditorAssistant-GN2KXKZD.js").then((module) => ({
7458
+ () => import("./AiEditorAssistant-4HFFGVU4.js").then((module) => ({
7282
7459
  default: module.AiReviewPanel
7283
7460
  }))
7284
7461
  );
7285
7462
  var AiIllustratePanel = lazy2(
7286
- () => import("./AiDiagrams-LFV4YU6W.js").then((module) => ({
7463
+ () => import("./AiDiagrams-QZMJNGDT.js").then((module) => ({
7287
7464
  default: module.AiIllustratePanel
7288
7465
  }))
7289
7466
  );
7290
7467
  var TextFromNarration = lazy2(
7291
- () => import("./TextFromNarration-NITJQ4IB.js").then((module) => ({
7468
+ () => import("./TextFromNarration-EYOMW2IW.js").then((module) => ({
7292
7469
  default: module.TextFromNarration
7293
7470
  }))
7294
7471
  );
7295
7472
  var SpeechToolbarControl = lazy2(
7296
- () => import("./SpeechToolbarControl-CLU7RB2G.js").then((module) => ({
7473
+ () => import("./SpeechToolbarControl-KNI2C4JJ.js").then((module) => ({
7297
7474
  default: module.SpeechToolbarControl
7298
7475
  }))
7299
7476
  );
7300
7477
  var SpeechMenuBridge = lazy2(
7301
- () => import("./SpeechToolbarControl-CLU7RB2G.js").then((module) => ({
7478
+ () => import("./SpeechToolbarControl-KNI2C4JJ.js").then((module) => ({
7302
7479
  default: module.SpeechMenuBridge
7303
7480
  }))
7304
7481
  );
@@ -7439,19 +7616,19 @@ function pathContains(parentPath2, candidatePath) {
7439
7616
  return workspacePathContains(parseWorkspacePath8(parentPath2), parseWorkspacePath8(candidatePath));
7440
7617
  }
7441
7618
  async function readProviderDirectory2(provider, path) {
7442
- const providerV2 = getFileSystemProviderV210(provider);
7619
+ const providerV2 = getFileSystemProviderV211(provider);
7443
7620
  if (!providerV2) return provider.readDirectory(path);
7444
7621
  const entries = await providerV2.readDirectory(parseWorkspacePath8(path));
7445
7622
  return entries.map((entry) => ({ kind: entry.kind, name: entry.name, path: entry.path }));
7446
7623
  }
7447
7624
  async function readProviderText(provider, path) {
7448
- const providerV2 = getFileSystemProviderV210(provider);
7625
+ const providerV2 = getFileSystemProviderV211(provider);
7449
7626
  if (!providerV2) return provider.readFile(path);
7450
7627
  const file = await providerV2.readFile(parseWorkspacePath8(path));
7451
7628
  return file ? decodeUtf8Text(file.data, { label: "The document", path: parseWorkspacePath8(path) }) : null;
7452
7629
  }
7453
7630
  async function pinnedProviderFileExists(provider, path) {
7454
- const providerV2 = getFileSystemProviderV210(provider);
7631
+ const providerV2 = getFileSystemProviderV211(provider);
7455
7632
  if (!providerV2) return provider.exists(path);
7456
7633
  const entry = await providerV2.stat(parseWorkspacePath8(path));
7457
7634
  if (!entry) return false;
@@ -7459,7 +7636,7 @@ async function pinnedProviderFileExists(provider, path) {
7459
7636
  return true;
7460
7637
  }
7461
7638
  async function removePinnedProviderFile(provider, path) {
7462
- const providerV2 = getFileSystemProviderV210(provider);
7639
+ const providerV2 = getFileSystemProviderV211(provider);
7463
7640
  if (providerV2) {
7464
7641
  const canonical = parseWorkspacePath8(path);
7465
7642
  const entry = await providerV2.stat(canonical);
@@ -7477,7 +7654,7 @@ async function removePinnedProviderFile(provider, path) {
7477
7654
  return true;
7478
7655
  }
7479
7656
  async function readStableFileSnapshot(provider, path) {
7480
- const providerV2 = getFileSystemProviderV210(provider);
7657
+ const providerV2 = getFileSystemProviderV211(provider);
7481
7658
  if (providerV2) {
7482
7659
  const read2 = await providerV2.readFile(parseWorkspacePath8(path));
7483
7660
  return {
@@ -7521,7 +7698,7 @@ async function ensureHandleWritePermission(handle) {
7521
7698
  }
7522
7699
  }
7523
7700
  async function copyProviderToContainer(src, container, pathPrefix) {
7524
- const providerV2 = getFileSystemProviderV210(src);
7701
+ const providerV2 = getFileSystemProviderV211(src);
7525
7702
  if (providerV2) {
7526
7703
  const snapshot = await providerV2.snapshot();
7527
7704
  for (const entry of snapshot.entries) {
@@ -7565,7 +7742,7 @@ async function createHostProviderFromWorkspace(ws) {
7565
7742
  var DESKTOP_TOOLBAR_WRAP_WIDTH = 900;
7566
7743
  var WEB_TOOLBAR_WRAP_WIDTH = 700;
7567
7744
  function FolderGlyph() {
7568
- return /* @__PURE__ */ jsx17(
7745
+ return /* @__PURE__ */ jsx18(
7569
7746
  "svg",
7570
7747
  {
7571
7748
  viewBox: "0 0 24 24",
@@ -7574,12 +7751,12 @@ function FolderGlyph() {
7574
7751
  strokeWidth: "1.5",
7575
7752
  strokeLinejoin: "round",
7576
7753
  "aria-hidden": "true",
7577
- children: /* @__PURE__ */ jsx17("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" })
7754
+ children: /* @__PURE__ */ jsx18("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" })
7578
7755
  }
7579
7756
  );
7580
7757
  }
7581
7758
  function FileGlyph() {
7582
- return /* @__PURE__ */ jsxs15(
7759
+ return /* @__PURE__ */ jsxs16(
7583
7760
  "svg",
7584
7761
  {
7585
7762
  viewBox: "0 0 24 24",
@@ -7589,14 +7766,14 @@ function FileGlyph() {
7589
7766
  strokeLinejoin: "round",
7590
7767
  "aria-hidden": "true",
7591
7768
  children: [
7592
- /* @__PURE__ */ jsx17("path", { d: "M6 3h8l5 5v12a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z" }),
7593
- /* @__PURE__ */ jsx17("path", { d: "M14 3v5h5" })
7769
+ /* @__PURE__ */ jsx18("path", { d: "M6 3h8l5 5v12a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z" }),
7770
+ /* @__PURE__ */ jsx18("path", { d: "M14 3v5h5" })
7594
7771
  ]
7595
7772
  }
7596
7773
  );
7597
7774
  }
7598
7775
  function CollapseSidebarGlyph() {
7599
- return /* @__PURE__ */ jsxs15(
7776
+ return /* @__PURE__ */ jsxs16(
7600
7777
  "svg",
7601
7778
  {
7602
7779
  viewBox: "0 0 64 64",
@@ -7607,9 +7784,9 @@ function CollapseSidebarGlyph() {
7607
7784
  strokeLinejoin: "round",
7608
7785
  "aria-hidden": "true",
7609
7786
  children: [
7610
- /* @__PURE__ */ jsx17("rect", { x: "7", y: "10", width: "50", height: "44", rx: "6" }),
7611
- /* @__PURE__ */ jsx17("path", { d: "M24 10v44" }),
7612
- /* @__PURE__ */ jsx17("path", { d: "m17 25-7 7 7 7" })
7787
+ /* @__PURE__ */ jsx18("rect", { x: "7", y: "10", width: "50", height: "44", rx: "6" }),
7788
+ /* @__PURE__ */ jsx18("path", { d: "M24 10v44" }),
7789
+ /* @__PURE__ */ jsx18("path", { d: "m17 25-7 7 7 7" })
7613
7790
  ]
7614
7791
  }
7615
7792
  );
@@ -7841,6 +8018,7 @@ function DocBlocksShell({
7841
8018
  );
7842
8019
  const [provider, setProvider] = useState23(null);
7843
8020
  const [workspaceStartupError, setWorkspaceStartupError] = useState23(null);
8021
+ const workspaceStarting = provider === null && workspaceStartupError === null;
7844
8022
  const [activeWorkspaceId, setActiveWorkspaceId] = useState23(null);
7845
8023
  const [defaultNewFileFormat, setDefaultNewFileFormat] = useState23(DEFAULT_NEW_FILE_FORMAT);
7846
8024
  useEffect29(() => {
@@ -7863,7 +8041,7 @@ function DocBlocksShell({
7863
8041
  const effectiveProofingIgnoreStore = proofingIgnoreStore === void 0 ? defaultProofingIgnoreStore : proofingIgnoreStore;
7864
8042
  useEffect29(() => {
7865
8043
  if (!provider || getTransientWorkspace(provider.id)) return;
7866
- const providerV2 = getFileSystemProviderV210(provider);
8044
+ const providerV2 = getFileSystemProviderV211(provider);
7867
8045
  return providerV2 ? retainFileSystemProvider(providerV2) : void 0;
7868
8046
  }, [provider]);
7869
8047
  const [descriptorRefreshKey, setDescriptorRefreshKey] = useState23(0);
@@ -7951,7 +8129,7 @@ function DocBlocksShell({
7951
8129
  void scan();
7952
8130
  const scanOnFocus = () => void scan();
7953
8131
  window.addEventListener("focus", scanOnFocus);
7954
- const providerV2 = getFileSystemProviderV210(provider);
8132
+ const providerV2 = getFileSystemProviderV211(provider);
7955
8133
  const subscription = providerV2?.capabilities.watch ? providerV2.watch(
7956
8134
  (event) => {
7957
8135
  if (event.type !== "modified") void scan();
@@ -8060,7 +8238,7 @@ function DocBlocksShell({
8060
8238
  if (descriptor && descriptor.versioningOverride !== void 0) {
8061
8239
  const moved = { ...descriptor };
8062
8240
  delete moved.versioningOverride;
8063
- await saveWorkspace2(moved);
8241
+ await saveWorkspace3(moved);
8064
8242
  setDescriptorRefreshKey((k) => k + 1);
8065
8243
  }
8066
8244
  },
@@ -8157,7 +8335,13 @@ function DocBlocksShell({
8157
8335
  return {
8158
8336
  resolveTarget: (filename) => resolveTarget(documentId, filename),
8159
8337
  pickTarget: (filename, currentTarget) => pickTarget(documentId, filename, currentTarget?.grantId ?? null),
8160
- saveBlob: (blob, filename, target) => saveBlobToHost(exports, documentId, blob, filename, target?.grantId ?? null)
8338
+ saveBlob: (blob, filename, target) => saveBlobToHost(exports, documentId, blob, filename, target?.grantId ?? null),
8339
+ ...hostSupports2("openSavedExports") && exports.openSaved ? {
8340
+ openSaved: async (target) => {
8341
+ if (!target.grantId) throw new Error("The saved export has no native target.");
8342
+ await exports.openSaved(documentId, target.grantId);
8343
+ }
8344
+ } : {}
8161
8345
  };
8162
8346
  }
8163
8347
  }
@@ -8194,7 +8378,6 @@ function DocBlocksShell({
8194
8378
  } : null,
8195
8379
  [documentSnapshot.frozen, documentSnapshot.generation, documentSnapshot.targetKey]
8196
8380
  );
8197
- const [editorPresentationEpoch, setEditorPresentationEpoch] = useState23(0);
8198
8381
  const [editorGeneration, setEditorGeneration] = useState23(documentSnapshot.generation);
8199
8382
  useEffect29(() => {
8200
8383
  const sync = () => setEditorGeneration(documentSession.getSnapshot().generation);
@@ -8208,7 +8391,7 @@ function DocBlocksShell({
8208
8391
  useEffect29(() => {
8209
8392
  setOutsideInPdfWorker(pdfWorkerSrc2);
8210
8393
  }, [pdfWorkerSrc2]);
8211
- const editorKey = `${editorGeneration}:${editorPresentationEpoch}`;
8394
+ const editorKey = String(editorGeneration);
8212
8395
  const editorPlaceholder = useMemo6(() => {
8213
8396
  void editorKey;
8214
8397
  return pickEmptyDocumentPrompt();
@@ -8217,7 +8400,7 @@ function DocBlocksShell({
8217
8400
  const workspaceOutputsResult = workspaceOutputs.status.result;
8218
8401
  useEffect29(() => {
8219
8402
  if (!workspaceOutputsResult?.outputs.some((output) => output.created)) return;
8220
- if (provider && getFileSystemProviderV210(provider)?.capabilities.watch) return;
8403
+ if (provider && getFileSystemProviderV211(provider)?.capabilities.watch) return;
8221
8404
  setExplorerKey((key) => key + 1);
8222
8405
  }, [provider, workspaceOutputsResult]);
8223
8406
  const [fileExplorerSortMode, setFileExplorerSortMode] = useState23(loadFileExplorerSortMode);
@@ -8228,6 +8411,15 @@ function DocBlocksShell({
8228
8411
  const [initialView, setInitialView] = useState23("wysiwyg");
8229
8412
  const [initialSharedMode, setInitialSharedMode] = useState23(null);
8230
8413
  const [showWelcomeGateway, setShowWelcomeGateway] = useState23(false);
8414
+ const welcomeScreenShown = showWelcomeGateway && !singlePane;
8415
+ const welcomeCoveredRef = useRef19(null);
8416
+ useInert(welcomeCoveredRef, welcomeScreenShown);
8417
+ const closeWelcomeGateway = useCallback18(() => {
8418
+ setShowWelcomeGateway((showing) => {
8419
+ if (showing) markWelcomeGatewayDismissed();
8420
+ return false;
8421
+ });
8422
+ }, []);
8231
8423
  const [aiPanel, setAiPanel] = useState23(null);
8232
8424
  const navigationRequestRef = useRef19(0);
8233
8425
  const workspaceAuthorityBarrier = useMemo6(() => new WorkspaceAuthorityBarrier(), []);
@@ -8511,7 +8703,7 @@ function DocBlocksShell({
8511
8703
  }
8512
8704
  return fsProvider;
8513
8705
  } finally {
8514
- if (!transient && !adopted) await getFileSystemProviderV210(fsProvider)?.dispose();
8706
+ if (!transient && !adopted) await getFileSystemProviderV211(fsProvider)?.dispose();
8515
8707
  }
8516
8708
  },
8517
8709
  [adoptSelectedDocument, createDocumentTarget, documentSession, pushHash]
@@ -8536,6 +8728,7 @@ function DocBlocksShell({
8536
8728
  options.navigationRequestId,
8537
8729
  options.sharedMode ?? null
8538
8730
  ) !== null;
8731
+ if (opened) closeWelcomeGateway();
8539
8732
  return opened;
8540
8733
  } finally {
8541
8734
  if (!opened && getTransientWorkspace(options.id)?.provider === options.provider) {
@@ -8543,7 +8736,7 @@ function DocBlocksShell({
8543
8736
  }
8544
8737
  }
8545
8738
  },
8546
- [openFromIds]
8739
+ [openFromIds, closeWelcomeGateway]
8547
8740
  );
8548
8741
  const openSharedDocument = useCallback18(
8549
8742
  async (payload, navigationRequestId) => {
@@ -8562,7 +8755,7 @@ function DocBlocksShell({
8562
8755
  )
8563
8756
  });
8564
8757
  } catch (error) {
8565
- await getFileSystemProviderV210(mem)?.dispose();
8758
+ await getFileSystemProviderV211(mem)?.dispose();
8566
8759
  throw error;
8567
8760
  }
8568
8761
  return adoptTransientWorkspace({
@@ -8585,18 +8778,18 @@ function DocBlocksShell({
8585
8778
  const entries = await readProviderDirectory2(fs, "/");
8586
8779
  if (!isCurrent()) return;
8587
8780
  const visibleEntries = filterVisibleFileEntries(entries);
8588
- if (visibleEntries.length === 1 && visibleEntries[0].kind === "file" && visibleEntries[0].path.replace(/^\//, "").toLowerCase() === "aboutdocblocks.md") {
8781
+ if (visibleEntries.length === 1 && visibleEntries[0].kind === "file" && isWelcomeDocumentPath(visibleEntries[0].path)) {
8589
8782
  const aboutPath = visibleEntries[0].path;
8590
8783
  const content = await readProviderText(fs, aboutPath);
8591
8784
  if (content !== null && isCurrent()) {
8592
8785
  await documentSession.transitionTo(createDocumentTarget(fs, fs.id, aboutPath), content);
8593
8786
  if (!isCurrent()) return;
8594
8787
  adoptRegularDocument(aboutPath);
8595
- setInitialView("preview");
8788
+ setInitialView("wysiwyg");
8596
8789
  setInitialSharedMode(null);
8597
8790
  setExplorerKey((k) => k + 1);
8598
8791
  pushHash(fs.id, aboutPath);
8599
- saveLastState({ workspaceId: fs.id, filePath: aboutPath, view: "preview" });
8792
+ saveLastState({ workspaceId: fs.id, filePath: aboutPath, view: "wysiwyg" });
8600
8793
  if (!isWelcomeGatewayDismissed()) setShowWelcomeGateway(true);
8601
8794
  }
8602
8795
  return;
@@ -8608,7 +8801,7 @@ function DocBlocksShell({
8608
8801
  try {
8609
8802
  await writeProviderText(fs, welcomePath, welcomeContent, "create");
8610
8803
  } catch (error) {
8611
- if (!(error instanceof FsError5 && error.code === "already-exists")) throw error;
8804
+ if (!(error instanceof FsError6 && error.code === "already-exists")) throw error;
8612
8805
  const existing = await readProviderText(fs, welcomePath);
8613
8806
  if (existing === null) throw error;
8614
8807
  seededContent = existing;
@@ -8620,11 +8813,11 @@ function DocBlocksShell({
8620
8813
  );
8621
8814
  if (!isCurrent()) return;
8622
8815
  adoptRegularDocument(welcomePath);
8623
- setInitialView("preview");
8816
+ setInitialView("wysiwyg");
8624
8817
  setInitialSharedMode(null);
8625
8818
  setExplorerKey((k) => k + 1);
8626
8819
  pushHash(fs.id, welcomePath);
8627
- saveLastState({ workspaceId: fs.id, filePath: welcomePath, view: "preview" });
8820
+ saveLastState({ workspaceId: fs.id, filePath: welcomePath, view: "wysiwyg" });
8628
8821
  if (!isWelcomeGatewayDismissed()) setShowWelcomeGateway(true);
8629
8822
  },
8630
8823
  [adoptRegularDocument, createDocumentTarget, documentSession, pushHash]
@@ -8635,21 +8828,16 @@ function DocBlocksShell({
8635
8828
  startupOpenSharedDocumentRef.current = openSharedDocument;
8636
8829
  const startupSeedWelcomeFileRef = useRef19(seedWelcomeFile);
8637
8830
  startupSeedWelcomeFileRef.current = seedWelcomeFile;
8638
- const closeWelcomeGateway = useCallback18(() => {
8639
- setShowWelcomeGateway((showing) => {
8640
- if (showing) markWelcomeGatewayDismissed();
8641
- return false;
8642
- });
8643
- }, []);
8644
- const handleStartWriting = useCallback18(() => {
8831
+ const [focusAfterWelcome, setFocusAfterWelcome] = useState23(false);
8832
+ const handleTourWelcome = useCallback18(() => {
8645
8833
  closeWelcomeGateway();
8646
- setInitialView("wysiwyg");
8647
- setInitialSharedMode(null);
8648
- setEditorPresentationEpoch((epoch) => epoch + 1);
8649
- if (activeWorkspaceId && selectedFile) {
8650
- saveLastState({ workspaceId: activeWorkspaceId, filePath: selectedFile, view: "wysiwyg" });
8651
- }
8652
- }, [closeWelcomeGateway, activeWorkspaceId, selectedFile]);
8834
+ setFocusAfterWelcome(true);
8835
+ }, [closeWelcomeGateway]);
8836
+ useEffect29(() => {
8837
+ if (!focusAfterWelcome || welcomeScreenShown) return;
8838
+ setFocusAfterWelcome(false);
8839
+ editorAreaRef.current?.querySelector('[contenteditable="true"]')?.focus({ preventScroll: true });
8840
+ }, [focusAfterWelcome, welcomeScreenShown]);
8653
8841
  useEffect29(() => {
8654
8842
  const requestId = ++navigationRequestRef.current;
8655
8843
  let cancelled = false;
@@ -8669,8 +8857,8 @@ function DocBlocksShell({
8669
8857
  getDocBlocksHost().env.platform,
8670
8858
  (/* @__PURE__ */ new Date()).toISOString()
8671
8859
  );
8672
- for (const id of reconciliation.removeIds) await removeWorkspace(id);
8673
- for (const workspace of reconciliation.upsert) await saveWorkspace2(workspace);
8860
+ for (const id of reconciliation.removeIds) await removeWorkspace2(id);
8861
+ for (const workspace of reconciliation.upsert) await saveWorkspace3(workspace);
8674
8862
  workspaceIdRemap = reconciliation.idRemap;
8675
8863
  for (const workspace of hostWorkspaces) {
8676
8864
  if (workspace.isDefault === true) defaultHostWorkspaceIds.add(workspace.id);
@@ -8763,12 +8951,12 @@ function DocBlocksShell({
8763
8951
  const p = await createHostProviderFromWorkspace(ws);
8764
8952
  if (!p) continue;
8765
8953
  if (!isCurrent()) {
8766
- await getFileSystemProviderV210(p)?.dispose();
8954
+ await getFileSystemProviderV211(p)?.dispose();
8767
8955
  return;
8768
8956
  }
8769
8957
  await touchWorkspace2(ws.id);
8770
8958
  if (!isCurrent()) {
8771
- await getFileSystemProviderV210(p)?.dispose();
8959
+ await getFileSystemProviderV211(p)?.dispose();
8772
8960
  return;
8773
8961
  }
8774
8962
  fsProvider = p;
@@ -8777,12 +8965,12 @@ function DocBlocksShell({
8777
8965
  const restored = await (await loadNativeFileSystem()).restoreNativeFolder(ws.id);
8778
8966
  if (restored) {
8779
8967
  if (!isCurrent()) {
8780
- await getFileSystemProviderV210(restored)?.dispose();
8968
+ await getFileSystemProviderV211(restored)?.dispose();
8781
8969
  return;
8782
8970
  }
8783
8971
  await touchWorkspace2(ws.id);
8784
8972
  if (!isCurrent()) {
8785
- await getFileSystemProviderV210(restored)?.dispose();
8973
+ await getFileSystemProviderV211(restored)?.dispose();
8786
8974
  return;
8787
8975
  }
8788
8976
  fsProvider = restored;
@@ -8791,12 +8979,12 @@ function DocBlocksShell({
8791
8979
  } else {
8792
8980
  const p = await createIndexedDbFileSystemProvider(ws.id, ws.name);
8793
8981
  if (!isCurrent()) {
8794
- await getFileSystemProviderV210(p)?.dispose();
8982
+ await getFileSystemProviderV211(p)?.dispose();
8795
8983
  return;
8796
8984
  }
8797
8985
  await touchWorkspace2(ws.id);
8798
8986
  if (!isCurrent()) {
8799
- await getFileSystemProviderV210(p)?.dispose();
8987
+ await getFileSystemProviderV211(p)?.dispose();
8800
8988
  return;
8801
8989
  }
8802
8990
  fsProvider = p;
@@ -8814,11 +9002,11 @@ function DocBlocksShell({
8814
9002
  rootPath: info.rootPath,
8815
9003
  lastOpened: (/* @__PURE__ */ new Date()).toISOString()
8816
9004
  };
8817
- await saveWorkspace2(descriptor);
9005
+ await saveWorkspace3(descriptor);
8818
9006
  if (!isCurrent()) return;
8819
9007
  const p = await createHostFileSystemProvider(info.id, info.name, info.rootPath);
8820
9008
  if (!isCurrent()) {
8821
- await getFileSystemProviderV210(p)?.dispose();
9009
+ await getFileSystemProviderV211(p)?.dispose();
8822
9010
  return;
8823
9011
  }
8824
9012
  fsProvider = p;
@@ -8827,7 +9015,7 @@ function DocBlocksShell({
8827
9015
  if (!isCurrent()) return;
8828
9016
  const p = await createIndexedDbFileSystemProvider(defaultWs.id, defaultWs.name);
8829
9017
  if (!isCurrent()) {
8830
- await getFileSystemProviderV210(p)?.dispose();
9018
+ await getFileSystemProviderV211(p)?.dispose();
8831
9019
  return;
8832
9020
  }
8833
9021
  fsProvider = p;
@@ -8848,7 +9036,7 @@ function DocBlocksShell({
8848
9036
  setActiveWorkspaceId(fsProvider.id);
8849
9037
  adopted = true;
8850
9038
  } finally {
8851
- if (!adopted) await getFileSystemProviderV210(fsProvider)?.dispose();
9039
+ if (!adopted) await getFileSystemProviderV211(fsProvider)?.dispose();
8852
9040
  }
8853
9041
  })().catch(() => {
8854
9042
  if (isCurrent()) {
@@ -9101,7 +9289,7 @@ function DocBlocksShell({
9101
9289
  }, [selectedFile, selectedManagedOutput, workspaceOutputsResult]);
9102
9290
  useEffect29(() => {
9103
9291
  if (!provider) return;
9104
- const providerV2 = getFileSystemProviderV210(provider);
9292
+ const providerV2 = getFileSystemProviderV211(provider);
9105
9293
  const lifecycle = maybeGetDocBlocksHost4()?.lifecycle;
9106
9294
  if (!providerV2 || !providerV2.capabilities.watch && !lifecycle?.onResume && !managedOutputRefreshToken)
9107
9295
  return;
@@ -9499,7 +9687,7 @@ function DocBlocksShell({
9499
9687
  } finally {
9500
9688
  if (nextProvider && ownsNextProvider && !adoptedNextProvider) {
9501
9689
  try {
9502
- await getFileSystemProviderV210(nextProvider)?.dispose();
9690
+ await getFileSystemProviderV211(nextProvider)?.dispose();
9503
9691
  } catch {
9504
9692
  }
9505
9693
  }
@@ -9605,14 +9793,14 @@ function DocBlocksShell({
9605
9793
  }
9606
9794
  }
9607
9795
  try {
9608
- await removeWorkspace(activeWorkspaceId);
9796
+ await removeWorkspace2(activeWorkspaceId);
9609
9797
  } catch {
9610
9798
  }
9611
9799
  setDescriptorRefreshKey((key) => key + 1);
9612
9800
  } catch (error) {
9613
9801
  if (destinationProvider && !adoptedDestination) {
9614
9802
  try {
9615
- await getFileSystemProviderV210(destinationProvider)?.dispose();
9803
+ await getFileSystemProviderV211(destinationProvider)?.dispose();
9616
9804
  } catch {
9617
9805
  }
9618
9806
  }
@@ -9647,7 +9835,7 @@ function DocBlocksShell({
9647
9835
  rootPath: info.rootPath,
9648
9836
  lastOpened: (/* @__PURE__ */ new Date()).toISOString()
9649
9837
  };
9650
- await saveWorkspace2(descriptor2);
9838
+ await saveWorkspace3(descriptor2);
9651
9839
  if (requestId !== navigationRequestRef.current) return;
9652
9840
  const provider2 = await createHostFileSystemProvider(info.id, info.name, info.rootPath);
9653
9841
  if (!await transitionAwayFromDocument(requestId)) return;
@@ -9667,7 +9855,7 @@ function DocBlocksShell({
9667
9855
  type: "native",
9668
9856
  lastOpened: (/* @__PURE__ */ new Date()).toISOString()
9669
9857
  };
9670
- await saveWorkspace2(descriptor);
9858
+ await saveWorkspace3(descriptor);
9671
9859
  if (requestId !== navigationRequestRef.current) return;
9672
9860
  if (!await transitionAwayFromDocument(requestId)) return;
9673
9861
  setProvider(nativeProvider);
@@ -9690,7 +9878,7 @@ function DocBlocksShell({
9690
9878
  rootPath: info.rootPath,
9691
9879
  lastOpened: (/* @__PURE__ */ new Date()).toISOString()
9692
9880
  };
9693
- await saveWorkspace2(descriptor);
9881
+ await saveWorkspace3(descriptor);
9694
9882
  if (requestId !== navigationRequestRef.current) return;
9695
9883
  const cloneProvider = await createHostFileSystemProvider(info.id, info.name, info.rootPath);
9696
9884
  if (!await transitionAwayFromDocument(requestId)) return;
@@ -9718,12 +9906,12 @@ function DocBlocksShell({
9718
9906
  if (requestId !== navigationRequestRef.current) return null;
9719
9907
  if (format === "markdown") {
9720
9908
  const content = "# " + filename.replace(/\.[^.]+$/, "") + "\n";
9721
- const providerV2 = getFileSystemProviderV210(provider);
9909
+ const providerV2 = getFileSystemProviderV211(provider);
9722
9910
  if (providerV2) {
9723
9911
  try {
9724
9912
  await writeProviderText(provider, path, content, "create");
9725
9913
  } catch (error) {
9726
- if (error instanceof FsError5 && error.code === "already-exists") {
9914
+ if (error instanceof FsError6 && error.code === "already-exists") {
9727
9915
  throw new Error("A document with that name already exists.");
9728
9916
  }
9729
9917
  throw error;
@@ -9822,7 +10010,7 @@ function DocBlocksShell({
9822
10010
  }
9823
10011
  const path = `/${folderName}`;
9824
10012
  try {
9825
- const providerV2 = getFileSystemProviderV210(provider);
10013
+ const providerV2 = getFileSystemProviderV211(provider);
9826
10014
  if (providerV2) {
9827
10015
  await providerV2.createDirectory(parseWorkspacePath8(path), { mode: "create" });
9828
10016
  } else {
@@ -9832,7 +10020,7 @@ function DocBlocksShell({
9832
10020
  await provider.createDirectory(path);
9833
10021
  }
9834
10022
  } catch (error) {
9835
- const message = error instanceof FsError5 && error.code === "already-exists" ? "A file or folder with that name already exists." : error instanceof Error ? error.message : "Could not create the folder.";
10023
+ const message = error instanceof FsError6 && error.code === "already-exists" ? "A file or folder with that name already exists." : error instanceof Error ? error.message : "Could not create the folder.";
9836
10024
  showToast("error", message);
9837
10025
  return;
9838
10026
  }
@@ -9882,14 +10070,20 @@ function DocBlocksShell({
9882
10070
  "new-folder": () => void handleNewFolder(),
9883
10071
  shortcuts: openKeyboardShortcuts,
9884
10072
  "toggle-sidebar": () => {
10073
+ if (canRestoreSplitView) {
10074
+ updateSidebarPreference("pinned");
10075
+ return;
10076
+ }
9885
10077
  if (singlePane) {
9886
10078
  setMobileShowEditor((showingEditor) => !showingEditor);
9887
10079
  return;
9888
10080
  }
9889
- updateSidebarPreference(sidebarPreference === "collapsed" ? "pinned" : "collapsed");
10081
+ updateSidebarPreference("collapsed");
10082
+ setMobileShowEditor(true);
9890
10083
  },
9891
10084
  "focus-files": () => {
9892
10085
  if (singlePane) setMobileShowEditor(false);
10086
+ setSidebarView("files");
9893
10087
  window.requestAnimationFrame(() => {
9894
10088
  const tree = document.querySelector(".db-tree");
9895
10089
  const target = tree?.querySelector('.db-tree-row[tabindex="0"]') ?? tree?.querySelector(".db-tree-row");
@@ -9919,7 +10113,7 @@ function DocBlocksShell({
9919
10113
  openKeyboardShortcuts,
9920
10114
  singlePane,
9921
10115
  mobileShowEditor,
9922
- sidebarPreference,
10116
+ canRestoreSplitView,
9923
10117
  updateSidebarPreference
9924
10118
  ]
9925
10119
  );
@@ -10052,7 +10246,7 @@ function DocBlocksShell({
10052
10246
  });
10053
10247
  }, [handleNewFile, handleOpenFolder, handleRevealWorkspace, acknowledge, openFind]);
10054
10248
  const handleSelect = useCallback18(
10055
- async (path, kind) => {
10249
+ async (path, kind, createWikiArticle = false) => {
10056
10250
  if (!provider || !activeWorkspaceId) return;
10057
10251
  const requestId = ++navigationRequestRef.current;
10058
10252
  if (kind === "directory") {
@@ -10069,6 +10263,17 @@ function DocBlocksShell({
10069
10263
  try {
10070
10264
  const transitioned = await documentSession.transitionWithLoad(async () => {
10071
10265
  if (requestId !== navigationRequestRef.current) return null;
10266
+ if (createWikiArticle) {
10267
+ if (await ensureShellWikiArticle(
10268
+ provider,
10269
+ parseWorkspacePath8(path),
10270
+ defaultNewFileFormat,
10271
+ outsideInRenderRef.current
10272
+ )) {
10273
+ notifyWorkspaceChanged(activeWorkspaceId, path);
10274
+ }
10275
+ if (requestId !== navigationRequestRef.current) return null;
10276
+ }
10072
10277
  openedDocument = await loadEditableShellDocument(
10073
10278
  provider,
10074
10279
  path,
@@ -10120,9 +10325,72 @@ function DocBlocksShell({
10120
10325
  documentSession,
10121
10326
  notifyWorkspaceChanged,
10122
10327
  transitionAwayFromDocument,
10123
- showToast
10328
+ showToast,
10329
+ defaultNewFileFormat
10124
10330
  ]
10125
10331
  );
10332
+ const wikiNavigationRef = useRef19(handleSelect);
10333
+ wikiNavigationRef.current = handleSelect;
10334
+ const wikiLinks = useMemo6(() => {
10335
+ if (!provider || !selectedFile || !activeWorkspaceId) return null;
10336
+ const source = selectedFile;
10337
+ const extension = wikiArticleExtension(defaultNewFileFormat);
10338
+ const lookups = /* @__PURE__ */ new Map();
10339
+ let queue = Promise.resolve();
10340
+ const lookup = (target) => {
10341
+ let result = lookups.get(target);
10342
+ if (!result) {
10343
+ result = queue.then(() => findWikiArticle2(provider, source, target, extension));
10344
+ queue = result.then(
10345
+ () => void 0,
10346
+ () => void 0
10347
+ );
10348
+ lookups.set(target, result);
10349
+ }
10350
+ return result;
10351
+ };
10352
+ return {
10353
+ async suggest(query) {
10354
+ const index = workspaceIndex.index;
10355
+ if (!index) return [];
10356
+ workspaceIndex.ensure();
10357
+ await index.whenListed({ timeoutMs: 5e3 });
10358
+ return suggestWikiArticles(
10359
+ index.getFiles(),
10360
+ index.getDirectories(),
10361
+ source,
10362
+ query,
10363
+ extension
10364
+ );
10365
+ },
10366
+ async exists(target) {
10367
+ return (await lookup(target)).exists;
10368
+ },
10369
+ async resolve(target) {
10370
+ const article = await lookup(target);
10371
+ if (!article.exists) return null;
10372
+ const href = selectedSourceFile !== selectedFile ? `/${article.path}` : article.relativePath;
10373
+ return href.split("/").map(encodeURIComponent).join("/");
10374
+ },
10375
+ async open(target, { create }) {
10376
+ const requestId = navigationRequestRef.current;
10377
+ const navigate = wikiNavigationRef.current;
10378
+ const { path } = await lookup(target);
10379
+ if (requestId !== navigationRequestRef.current || navigate !== wikiNavigationRef.current)
10380
+ return;
10381
+ await navigate(path, "file", create);
10382
+ }
10383
+ };
10384
+ }, [
10385
+ provider,
10386
+ selectedFile,
10387
+ selectedSourceFile,
10388
+ activeWorkspaceId,
10389
+ defaultNewFileFormat,
10390
+ workspaceIndex.index,
10391
+ workspaceIndex.ensure,
10392
+ workspaceIndex.status.generation
10393
+ ]);
10126
10394
  const handleEnableOutsideInEditing = useCallback18(
10127
10395
  async (path) => {
10128
10396
  if (!provider || !activeWorkspaceId) {
@@ -10250,44 +10518,32 @@ function DocBlocksShell({
10250
10518
  if (!target) {
10251
10519
  showToast(
10252
10520
  "error",
10253
- "DocBlocks only opens HTTP(S) links or Markdown files inside this workspace."
10521
+ "DocBlocks only opens HTTP(S) links or Markdown and HTML files inside this workspace."
10254
10522
  );
10255
10523
  return true;
10256
10524
  }
10257
10525
  if (target.kind === "fragment") return false;
10258
10526
  if (target.kind === "external") {
10259
- const shell = maybeGetDocBlocksHost4()?.shell;
10260
- if (hostSupports2("externalNavigation") && shell?.openExternal) {
10261
- void shell.openExternal(target.url).catch((error) => {
10262
- showToast(
10263
- "error",
10264
- error instanceof Error ? error.message : "Could not open the external link."
10265
- );
10266
- });
10267
- } else if (typeof window !== "undefined") {
10268
- try {
10269
- window.open(target.url, "_blank", "noopener,noreferrer");
10270
- } catch (error) {
10271
- showToast(
10272
- "error",
10273
- error instanceof Error ? error.message : "Could not open the external link."
10274
- );
10275
- }
10276
- }
10527
+ void openExternalUrl(target.url).catch((error) => {
10528
+ showToast(
10529
+ "error",
10530
+ error instanceof Error ? error.message : "Could not open the external link."
10531
+ );
10532
+ });
10277
10533
  return true;
10278
10534
  }
10279
10535
  void (async () => {
10280
10536
  if (!provider) return;
10281
10537
  try {
10282
10538
  if (!await providerEntryExists(provider, target.path)) {
10283
- showToast("error", "The linked Markdown file was not found in this workspace.");
10539
+ showToast("error", "The linked document was not found in this workspace.");
10284
10540
  return;
10285
10541
  }
10286
10542
  await handleSelect(target.path, "file");
10287
10543
  } catch (error) {
10288
10544
  showToast(
10289
10545
  "error",
10290
- error instanceof Error ? error.message : "Could not open the linked Markdown file."
10546
+ error instanceof Error ? error.message : "Could not open the linked document."
10291
10547
  );
10292
10548
  }
10293
10549
  })();
@@ -10561,7 +10817,7 @@ function DocBlocksShell({
10561
10817
  } finally {
10562
10818
  if (access.owned) {
10563
10819
  try {
10564
- await getFileSystemProviderV210(access.provider)?.dispose();
10820
+ await getFileSystemProviderV211(access.provider)?.dispose();
10565
10821
  } catch {
10566
10822
  }
10567
10823
  }
@@ -10630,7 +10886,7 @@ function DocBlocksShell({
10630
10886
  } finally {
10631
10887
  if (access.owned) {
10632
10888
  try {
10633
- await getFileSystemProviderV210(access.provider)?.dispose();
10889
+ await getFileSystemProviderV211(access.provider)?.dispose();
10634
10890
  } catch {
10635
10891
  }
10636
10892
  }
@@ -10648,32 +10904,73 @@ function DocBlocksShell({
10648
10904
  showToast
10649
10905
  ]
10650
10906
  );
10907
+ const restoreInputRef = useRef19(null);
10908
+ const restoreInProgress = useRef19(false);
10909
+ const handleRestoreBackup = useCallback18(
10910
+ async (file) => {
10911
+ if (restoreInProgress.current) {
10912
+ showToast("error", "A backup is already being restored. Please wait.");
10913
+ return;
10914
+ }
10915
+ restoreInProgress.current = true;
10916
+ try {
10917
+ await documentSession.flush("backup");
10918
+ const restored = await restoreBrowserBackup(file);
10919
+ setDescriptorRefreshKey((key) => key + 1);
10920
+ if (restored[0]) await handleWorkspaceSelect(restored[0]);
10921
+ showToast(
10922
+ "success",
10923
+ `Restored ${restored.length} workspace(s) as new copies. Your existing workspaces are unchanged.`
10924
+ );
10925
+ } catch (error) {
10926
+ showToast(
10927
+ "error",
10928
+ `Could not restore ${file.name}: ${error instanceof Error ? error.message : "Unknown error"}`
10929
+ );
10930
+ } finally {
10931
+ restoreInProgress.current = false;
10932
+ }
10933
+ },
10934
+ [documentSession, handleWorkspaceSelect, showToast]
10935
+ );
10936
+ const importOrRestore = useCallback18(
10937
+ async (files) => {
10938
+ const backups = hostSupports2("nativeWorkspaces") ? [] : files.filter((file) => /\.zip$/i.test(file.name));
10939
+ const documents = files.filter((file) => !backups.includes(file));
10940
+ const result = provider ? await importDroppedFiles(documents, provider) : null;
10941
+ for (const file of backups) await handleRestoreBackup(file);
10942
+ return result;
10943
+ },
10944
+ [handleRestoreBackup, provider]
10945
+ );
10651
10946
  const handleImportFiles = useCallback18(
10652
10947
  async (files) => {
10653
10948
  if (!provider) return;
10654
- const result = await importDroppedFiles(files, provider);
10949
+ const result = await importOrRestore(files);
10950
+ if (!result) return;
10655
10951
  notifyWorkspaceChanged(activeWorkspaceId);
10656
10952
  setExplorerKey((k) => k + 1);
10657
10953
  const summary = summariseImport(result);
10658
10954
  if (summary) showToast(summary.kind, summary.message);
10659
10955
  },
10660
- [activeWorkspaceId, notifyWorkspaceChanged, provider, showToast]
10956
+ [activeWorkspaceId, notifyWorkspaceChanged, provider, showToast, importOrRestore]
10661
10957
  );
10662
10958
  const handleImportFilesToEditor = useCallback18(
10663
10959
  async (files) => {
10664
10960
  if (!provider) return;
10665
- const result = await importDroppedFiles(files, provider);
10961
+ const result = await importOrRestore(files);
10962
+ if (!result) return;
10666
10963
  notifyWorkspaceChanged(activeWorkspaceId);
10667
10964
  setExplorerKey((k) => k + 1);
10668
10965
  const summary = summariseImport(result);
10669
10966
  if (summary) showToast(summary.kind, summary.message);
10670
10967
  const imported = result.imported;
10671
- if (imported.length === 1 && result.failed.length === 0) {
10968
+ if (imported.length === 1 && result.failed.length === 0 && !files.some((file) => /\.zip$/i.test(file.name))) {
10672
10969
  const only = imported[0];
10673
10970
  if (only) void handleSelect(only.path, "file");
10674
10971
  }
10675
10972
  },
10676
- [activeWorkspaceId, notifyWorkspaceChanged, provider, showToast, handleSelect]
10973
+ [activeWorkspaceId, notifyWorkspaceChanged, provider, showToast, handleSelect, importOrRestore]
10677
10974
  );
10678
10975
  const editorDropActive = useEditorFileDrop(
10679
10976
  editorAreaRef,
@@ -10701,7 +10998,7 @@ function DocBlocksShell({
10701
10998
  confirmLabel: "Rename"
10702
10999
  });
10703
11000
  if (!newName || newName === ws.name) return;
10704
- await saveWorkspace2({ ...ws, name: newName });
11001
+ await saveWorkspace3({ ...ws, name: newName });
10705
11002
  setPinnedDocuments(
10706
11003
  (current) => renamePinnedDocumentWorkspace(current, activeWorkspaceId, newName)
10707
11004
  );
@@ -10727,7 +11024,7 @@ function DocBlocksShell({
10727
11024
  throw error;
10728
11025
  }
10729
11026
  if (!isCurrent()) {
10730
- await getFileSystemProviderV210(mem)?.dispose();
11027
+ await getFileSystemProviderV211(mem)?.dispose();
10731
11028
  await revokeAbandonedResource();
10732
11029
  return;
10733
11030
  }
@@ -10775,7 +11072,7 @@ function DocBlocksShell({
10775
11072
  } finally {
10776
11073
  try {
10777
11074
  if (!adoptionStarted) {
10778
- await getFileSystemProviderV210(mem)?.dispose();
11075
+ await getFileSystemProviderV211(mem)?.dispose();
10779
11076
  }
10780
11077
  } finally {
10781
11078
  if (!opened) await revokeAbandonedResource();
@@ -10796,6 +11093,7 @@ function DocBlocksShell({
10796
11093
  if (!opened && requestId === navigationRequestRef.current) {
10797
11094
  throw new Error("The requested workspace file is no longer available.");
10798
11095
  }
11096
+ if (opened) closeWelcomeGateway();
10799
11097
  return opened;
10800
11098
  }
10801
11099
  );
@@ -10807,7 +11105,7 @@ function DocBlocksShell({
10807
11105
  );
10808
11106
  });
10809
11107
  });
10810
- }, [openFromIds, openTransient, workspaceAuthorityBarrier, showToast]);
11108
+ }, [openFromIds, openTransient, workspaceAuthorityBarrier, showToast, closeWelcomeGateway]);
10811
11109
  const openTransientFromHandle = useCallback18(
10812
11110
  async (handle) => {
10813
11111
  const name = handle.name;
@@ -10845,7 +11143,7 @@ function DocBlocksShell({
10845
11143
  push: true
10846
11144
  });
10847
11145
  } finally {
10848
- if (!adoptionStarted) await getFileSystemProviderV210(mem)?.dispose();
11146
+ if (!adoptionStarted) await getFileSystemProviderV211(mem)?.dispose();
10849
11147
  }
10850
11148
  },
10851
11149
  [adoptTransientWorkspace]
@@ -10875,8 +11173,11 @@ function DocBlocksShell({
10875
11173
  import("@bendyline/squisq-formats/container")
10876
11174
  ]);
10877
11175
  const container = new MemoryContentContainer();
10878
- await copyProviderToContainer(provider, container, "");
10879
- const blob = await containerToZip(container);
11176
+ const source = getFileSystemProviderV211(provider);
11177
+ if (!source) throw new Error("This workspace does not support complete backups.");
11178
+ const workspaces = await appendWorkspaceBackup(container, source, []);
11179
+ await finishWorkspaceBackup(container, workspaces);
11180
+ const blob = await containerToZip(container, { compression: "STORE" });
10880
11181
  const url = URL.createObjectURL(blob);
10881
11182
  const a = document.createElement("a");
10882
11183
  a.href = url;
@@ -10885,8 +11186,7 @@ function DocBlocksShell({
10885
11186
  a.click();
10886
11187
  URL.revokeObjectURL(url);
10887
11188
  } catch (err) {
10888
- console.error("Failed to download workspace", err);
10889
- showToast("error", "Failed to download workspace. See console for details.");
11189
+ showToast("error", err instanceof Error ? err.message : "Failed to download workspace.");
10890
11190
  }
10891
11191
  }, [provider, documentSession, showToast]);
10892
11192
  const handleDownloadAllWorkspaces = useCallback18(async () => {
@@ -10906,7 +11206,7 @@ function DocBlocksShell({
10906
11206
  return;
10907
11207
  }
10908
11208
  const container = new MemoryContentContainer();
10909
- const usedFolders = /* @__PURE__ */ new Set();
11209
+ let backupWorkspaces = [];
10910
11210
  const skipped = [];
10911
11211
  for (const ws of candidates) {
10912
11212
  let p = null;
@@ -10932,24 +11232,20 @@ function DocBlocksShell({
10932
11232
  skipped.push(ws.name);
10933
11233
  continue;
10934
11234
  }
10935
- const base = (ws.name || "workspace").replace(/[^a-z0-9_\- ]/gi, "_").trim() || "workspace";
10936
- let folder = base;
10937
- let suffix = 2;
10938
- while (usedFolders.has(folder)) {
10939
- folder = `${base} (${suffix++})`;
10940
- }
10941
- usedFolders.add(folder);
10942
11235
  try {
10943
- await copyProviderToContainer(p, container, folder);
11236
+ const source = getFileSystemProviderV211(p);
11237
+ if (!source) throw new Error("This workspace does not support complete backups.");
11238
+ backupWorkspaces = await appendWorkspaceBackup(container, source, backupWorkspaces);
10944
11239
  } finally {
10945
- if (ownsProvider) await getFileSystemProviderV210(p)?.dispose();
11240
+ if (ownsProvider) await getFileSystemProviderV211(p)?.dispose();
10946
11241
  }
10947
11242
  }
10948
- if (usedFolders.size === 0) {
11243
+ if (backupWorkspaces.length === 0) {
10949
11244
  showToast("error", "No workspaces could be opened for download.");
10950
11245
  return;
10951
11246
  }
10952
- const blob = await containerToZip(container);
11247
+ await finishWorkspaceBackup(container, backupWorkspaces);
11248
+ const blob = await containerToZip(container, { compression: "STORE" });
10953
11249
  const url = URL.createObjectURL(blob);
10954
11250
  const a = document.createElement("a");
10955
11251
  a.href = url;
@@ -10959,12 +11255,11 @@ function DocBlocksShell({
10959
11255
  if (skipped.length > 0) {
10960
11256
  await acknowledge({
10961
11257
  title: "Some workspaces were skipped",
10962
- message: `Downloaded ${usedFolders.size} workspace(s). Skipped ${skipped.length} that require re-granting access: ${skipped.join(", ")}.`
11258
+ message: `Downloaded ${backupWorkspaces.length} workspace(s). Skipped ${skipped.length} that require re-granting access: ${skipped.join(", ")}.`
10963
11259
  });
10964
11260
  }
10965
11261
  } catch (err) {
10966
- console.error("Failed to download all workspaces", err);
10967
- showToast("error", "Failed to download all workspaces. See console for details.");
11262
+ showToast("error", err instanceof Error ? err.message : "Failed to download all workspaces.");
10968
11263
  }
10969
11264
  }, [documentSession, acknowledge, showToast]);
10970
11265
  const handleKeepBrowserData = useCallback18(async () => {
@@ -11055,7 +11350,7 @@ function DocBlocksShell({
11055
11350
  } else if (ws?.type === "indexeddb") {
11056
11351
  await (await loadIndexedDbFileSystem()).deleteIndexedDBWorkspaceData(workspaceId);
11057
11352
  }
11058
- await removeWorkspace(workspaceId);
11353
+ await removeWorkspace2(workspaceId);
11059
11354
  forgetRecentDocuments(workspaceId);
11060
11355
  for (const document2 of pinnedDocuments) {
11061
11356
  if (document2.workspaceId === workspaceId) {
@@ -11083,7 +11378,7 @@ function DocBlocksShell({
11083
11378
  rootPath: info.rootPath,
11084
11379
  lastOpened: (/* @__PURE__ */ new Date()).toISOString()
11085
11380
  };
11086
- await saveWorkspace2(descriptor);
11381
+ await saveWorkspace3(descriptor);
11087
11382
  const p = await createHostFileSystemProvider(info.id, info.name, info.rootPath);
11088
11383
  setProvider(p);
11089
11384
  setActiveWorkspaceId(info.id);
@@ -11110,7 +11405,7 @@ function DocBlocksShell({
11110
11405
  transitionAwayFromDocument
11111
11406
  ]
11112
11407
  );
11113
- return /* @__PURE__ */ jsx17(
11408
+ return /* @__PURE__ */ jsx18(
11114
11409
  "div",
11115
11410
  {
11116
11411
  ref: shellRef,
@@ -11119,10 +11414,10 @@ function DocBlocksShell({
11119
11414
  "data-accent": accentColor,
11120
11415
  "data-interface-font": interfaceFont === "fixed" ? "fixed" : void 0,
11121
11416
  "data-document-status": documentSnapshot.status,
11122
- children: /* @__PURE__ */ jsxs15(GitContext.Provider, { value: git, children: [
11417
+ children: /* @__PURE__ */ jsxs16(GitContext.Provider, { value: git, children: [
11123
11418
  promptDialog,
11124
11419
  confirmDialog,
11125
- quickOpenOpen && activeWorkspaceId && /* @__PURE__ */ jsx17(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx17(
11420
+ quickOpenOpen && activeWorkspaceId && /* @__PURE__ */ jsx18(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx18(
11126
11421
  QuickOpenDialog,
11127
11422
  {
11128
11423
  index: workspaceIndex.index,
@@ -11136,8 +11431,8 @@ function DocBlocksShell({
11136
11431
  onClose: () => setQuickOpenOpen(false)
11137
11432
  }
11138
11433
  ) }),
11139
- shortcutsOpen && /* @__PURE__ */ jsx17(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx17(KeyboardShortcutsDialog, { onClose: () => setShortcutsOpen(false) }) }),
11140
- newDocumentDialogOpen && /* @__PURE__ */ jsx17(
11434
+ shortcutsOpen && /* @__PURE__ */ jsx18(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx18(KeyboardShortcutsDialog, { onClose: () => setShortcutsOpen(false) }) }),
11435
+ newDocumentDialogOpen && /* @__PURE__ */ jsx18(
11141
11436
  NewDocumentDialog,
11142
11437
  {
11143
11438
  initialFormat: defaultNewFileFormat,
@@ -11147,8 +11442,8 @@ function DocBlocksShell({
11147
11442
  }
11148
11443
  }
11149
11444
  ),
11150
- workspaceStartupError && /* @__PURE__ */ jsx17("div", { className: "db-save-toast db-save-toast--error", role: "alert", "aria-live": "assertive", children: workspaceStartupError }),
11151
- saveToast && /* @__PURE__ */ jsx17(
11445
+ workspaceStartupError && /* @__PURE__ */ jsx18("div", { className: "db-save-toast db-save-toast--error", role: "alert", "aria-live": "assertive", children: workspaceStartupError }),
11446
+ saveToast && /* @__PURE__ */ jsx18(
11152
11447
  "div",
11153
11448
  {
11154
11449
  className: "db-save-toast db-save-toast--" + saveToast.kind,
@@ -11157,12 +11452,12 @@ function DocBlocksShell({
11157
11452
  children: saveToast.message
11158
11453
  }
11159
11454
  ),
11160
- offlineReadyToast && !saveToast && /* @__PURE__ */ jsx17("div", { className: "db-save-toast db-save-toast--success", role: "status", "aria-live": "polite", children: "DocBlocks is ready to work offline." }),
11161
- documentSnapshot.conflict && /* @__PURE__ */ jsxs15("div", { className: "db-document-conflict", role: "alert", children: [
11162
- /* @__PURE__ */ jsx17("span", { children: documentSnapshot.conflict.recoveredDraft ? "A recovery draft from a previous session differs from the saved file. The saved file may be newer. Choose which version to use." : "This document changed outside DocBlocks. Your unsaved version is still intact." }),
11163
- /* @__PURE__ */ jsx17("div", { className: "db-document-conflict-actions", children: /* @__PURE__ */ jsx17("button", { type: "button", onClick: () => setShowConflictComparison(true), children: "Compare versions" }) })
11455
+ offlineReadyToast && !saveToast && /* @__PURE__ */ jsx18("div", { className: "db-save-toast db-save-toast--success", role: "status", "aria-live": "polite", children: "DocBlocks is ready to work offline." }),
11456
+ documentSnapshot.conflict && /* @__PURE__ */ jsxs16("div", { className: "db-document-conflict", role: "alert", children: [
11457
+ /* @__PURE__ */ jsx18("span", { children: documentSnapshot.conflict.recoveredDraft ? "A recovery draft from a previous session differs from the saved file. The saved file may be newer. Choose which version to use." : "This document changed outside DocBlocks. Your unsaved version is still intact." }),
11458
+ /* @__PURE__ */ jsx18("div", { className: "db-document-conflict-actions", children: /* @__PURE__ */ jsx18("button", { type: "button", onClick: () => setShowConflictComparison(true), children: "Compare versions" }) })
11164
11459
  ] }),
11165
- documentSnapshot.conflict && showConflictComparison && /* @__PURE__ */ jsx17(Suspense2, { fallback: /* @__PURE__ */ jsx17("div", { role: "status", children: "Loading version comparison\u2026" }), children: /* @__PURE__ */ jsx17(
11460
+ documentSnapshot.conflict && showConflictComparison && /* @__PURE__ */ jsx18(Suspense2, { fallback: /* @__PURE__ */ jsx18("div", { role: "status", children: "Loading version comparison\u2026" }), children: /* @__PURE__ */ jsx18(
11166
11461
  DocumentConflictDialog,
11167
11462
  {
11168
11463
  conflict: documentSnapshot.conflict,
@@ -11172,14 +11467,14 @@ function DocBlocksShell({
11172
11467
  onClose: () => setShowConflictComparison(false)
11173
11468
  }
11174
11469
  ) }),
11175
- storageFull && !documentSnapshot.conflict && /* @__PURE__ */ jsxs15("div", { className: "db-storage-full-banner", role: "alert", children: [
11176
- /* @__PURE__ */ jsx17("span", { children: "Browser storage is full -- changes can\u2019t be saved. Free up space or back up your work now." }),
11177
- /* @__PURE__ */ jsxs15("div", { className: "db-storage-full-banner-actions", children: [
11178
- /* @__PURE__ */ jsx17("button", { type: "button", onClick: () => void handleDownloadAllWorkspaces(), children: "Back up all workspaces" }),
11179
- /* @__PURE__ */ jsx17("button", { type: "button", onClick: () => setStorageFull(false), children: "Dismiss" })
11470
+ storageFull && !documentSnapshot.conflict && /* @__PURE__ */ jsxs16("div", { className: "db-storage-full-banner", role: "alert", children: [
11471
+ /* @__PURE__ */ jsx18("span", { children: "Browser storage is full -- changes can\u2019t be saved. Free up space or back up your work now." }),
11472
+ /* @__PURE__ */ jsxs16("div", { className: "db-storage-full-banner-actions", children: [
11473
+ /* @__PURE__ */ jsx18("button", { type: "button", onClick: () => void handleDownloadAllWorkspaces(), children: "Back up all workspaces" }),
11474
+ /* @__PURE__ */ jsx18("button", { type: "button", onClick: () => setStorageFull(false), children: "Dismiss" })
11180
11475
  ] })
11181
11476
  ] }),
11182
- workspaceSettingsOpen && activeWorkspaceDescriptor && /* @__PURE__ */ jsx17(
11477
+ workspaceSettingsOpen && activeWorkspaceDescriptor && /* @__PURE__ */ jsx18(
11183
11478
  WorkspaceSettingsDialog,
11184
11479
  {
11185
11480
  workspace: activeWorkspaceDescriptor,
@@ -11192,8 +11487,8 @@ function DocBlocksShell({
11192
11487
  onClose: () => setWorkspaceSettingsOpen(false)
11193
11488
  }
11194
11489
  ),
11195
- /* @__PURE__ */ jsxs15("div", { className: "db-shell-panes", ref: panesRef, children: [
11196
- /* @__PURE__ */ jsxs15(
11490
+ /* @__PURE__ */ jsxs16("div", { className: "db-shell-panes", ref: panesRef, children: [
11491
+ /* @__PURE__ */ jsxs16(
11197
11492
  "aside",
11198
11493
  {
11199
11494
  ref: sidebarRef,
@@ -11201,8 +11496,8 @@ function DocBlocksShell({
11201
11496
  "aria-label": "Workspace and files",
11202
11497
  style: singlePane ? void 0 : { width: `${sidebarWidth}px` },
11203
11498
  children: [
11204
- /* @__PURE__ */ jsxs15("div", { className: "db-shell-sidebar-header", children: [
11205
- /* @__PURE__ */ jsx17(
11499
+ /* @__PURE__ */ jsxs16("div", { className: "db-shell-sidebar-header", children: [
11500
+ /* @__PURE__ */ jsx18(
11206
11501
  AppMenu,
11207
11502
  {
11208
11503
  logoUrl,
@@ -11224,6 +11519,7 @@ function DocBlocksShell({
11224
11519
  onVersioningPreferenceChange: handleVersioningPreferenceChange,
11225
11520
  onOpenWorkspaceSettings: activeWorkspaceDescriptor ? handleOpenWorkspaceSettings : void 0,
11226
11521
  onDownloadAllWorkspaces: handleDownloadAllWorkspaces,
11522
+ onRestoreBackup: hostSupports2("nativeWorkspaces") ? void 0 : () => restoreInputRef.current?.click(),
11227
11523
  onShowKeyboardShortcuts: singlePane ? void 0 : openKeyboardShortcuts,
11228
11524
  onKeepBrowserData: showBrowserStorageWarning && !browserStoragePersistent ? handleKeepBrowserData : void 0,
11229
11525
  onInstallApp: installPromptEvent ? handleInstallApp : void 0,
@@ -11236,10 +11532,26 @@ function DocBlocksShell({
11236
11532
  settingsRequest
11237
11533
  }
11238
11534
  ),
11239
- /* @__PURE__ */ jsx17(
11535
+ /* @__PURE__ */ jsx18(
11536
+ "input",
11537
+ {
11538
+ ref: restoreInputRef,
11539
+ type: "file",
11540
+ accept: ".zip,application/zip",
11541
+ "aria-label": "Restore workspace backup",
11542
+ hidden: true,
11543
+ onChange: (event) => {
11544
+ const file = event.currentTarget.files?.[0];
11545
+ event.currentTarget.value = "";
11546
+ if (file) void handleRestoreBackup(file);
11547
+ }
11548
+ }
11549
+ ),
11550
+ /* @__PURE__ */ jsx18(
11240
11551
  WorkspacePicker,
11241
11552
  {
11242
11553
  activeWorkspaceId,
11554
+ loading: workspaceStarting,
11243
11555
  refreshKey: descriptorRefreshKey,
11244
11556
  onSelect: handleWorkspaceSelect,
11245
11557
  onOpenFolder: handleOpenFolder,
@@ -11247,7 +11559,7 @@ function DocBlocksShell({
11247
11559
  onRemoveWorkspace: (workspace) => void handleRemoveWorkspace(workspace.id)
11248
11560
  }
11249
11561
  ),
11250
- /* @__PURE__ */ jsx17(
11562
+ /* @__PURE__ */ jsx18(
11251
11563
  WorkspaceSettingsButton,
11252
11564
  {
11253
11565
  onSettings: handleOpenWorkspaceSettings,
@@ -11258,28 +11570,29 @@ function DocBlocksShell({
11258
11570
  }
11259
11571
  }
11260
11572
  ),
11261
- canRestoreSplitView && /* @__PURE__ */ jsx17(
11573
+ canRestoreSplitView && /* @__PURE__ */ jsx18(
11262
11574
  "button",
11263
11575
  {
11264
11576
  className: "db-restore-split",
11265
11577
  onClick: () => updateSidebarPreference("pinned"),
11266
11578
  "aria-label": "Restore split view",
11267
11579
  title: "Restore split view",
11268
- children: /* @__PURE__ */ jsx17(SplitViewIcon, {})
11580
+ children: /* @__PURE__ */ jsx18(SplitViewIcon, {})
11269
11581
  }
11270
11582
  ),
11271
- /* @__PURE__ */ jsx17("span", { className: "db-window-drag-grip", "aria-hidden": true })
11583
+ /* @__PURE__ */ jsx18("span", { className: "db-window-drag-grip", "aria-hidden": true })
11272
11584
  ] }),
11273
- git.available && /* @__PURE__ */ jsx17(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx17(
11585
+ git.available && /* @__PURE__ */ jsx18(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx18(
11274
11586
  GitUI,
11275
11587
  {
11276
11588
  onOpenFile: (path) => void handleSelect(path, "file"),
11277
11589
  onWorkspaceCloned: handleWorkspaceCloned
11278
11590
  }
11279
11591
  ) }),
11280
- /* @__PURE__ */ jsx17(
11592
+ /* @__PURE__ */ jsx18(
11281
11593
  FileExplorer,
11282
11594
  {
11595
+ loading: workspaceStarting,
11283
11596
  className: sidebarView === "search" ? "db-sidebar-view--hidden" : void 0,
11284
11597
  onShowSearch: canBrowseWorkspace ? openWorkspaceSearch : void 0,
11285
11598
  searchShortcutLabel: searchShortcut,
@@ -11311,7 +11624,7 @@ function DocBlocksShell({
11311
11624
  },
11312
11625
  explorerKey
11313
11626
  ),
11314
- searchMounted && /* @__PURE__ */ jsx17(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx17(
11627
+ searchMounted && /* @__PURE__ */ jsx18(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx18(
11315
11628
  WorkspaceSearchPanel,
11316
11629
  {
11317
11630
  index: workspaceIndex.index,
@@ -11322,41 +11635,38 @@ function DocBlocksShell({
11322
11635
  onShowFiles: () => setSidebarView("files")
11323
11636
  }
11324
11637
  ) }),
11325
- singlePane && showWelcomeGateway && /* @__PURE__ */ jsxs15("section", { className: "db-mobile-first-run", "aria-labelledby": "db-mobile-first-run-title", children: [
11326
- /* @__PURE__ */ jsx17("p", { className: "db-mobile-first-run-eyebrow", children: "Local-first Markdown editor" }),
11327
- /* @__PURE__ */ jsx17("h1", { id: "db-mobile-first-run-title", children: "Welcome to DocBlocks" }),
11328
- /* @__PURE__ */ jsx17("p", { children: "Write visually, keep plain Markdown underneath, and export the same document in useful formats. Keep your documents in the workspace you choose." }),
11329
- /* @__PURE__ */ jsxs15("div", { className: "db-mobile-first-run-actions", children: [
11330
- /* @__PURE__ */ jsx17(
11638
+ singlePane && showWelcomeGateway && /* @__PURE__ */ jsxs16("section", { className: "db-mobile-first-run", "aria-labelledby": "db-mobile-first-run-title", children: [
11639
+ /* @__PURE__ */ jsx18("p", { className: "db-mobile-first-run-eyebrow", children: "Local-first Markdown editor" }),
11640
+ /* @__PURE__ */ jsx18("h1", { id: "db-mobile-first-run-title", children: "Welcome to DocBlocks" }),
11641
+ /* @__PURE__ */ jsxs16("p", { children: [
11642
+ WELCOME_SUMMARY,
11643
+ " ",
11644
+ WELCOME_PRIVACY_NOTE
11645
+ ] }),
11646
+ /* @__PURE__ */ jsxs16("div", { className: "db-mobile-first-run-actions", children: [
11647
+ /* @__PURE__ */ jsx18("button", { type: "button", onClick: () => void handleNewFile(), children: "Create a document" }),
11648
+ /* @__PURE__ */ jsx18(
11331
11649
  "button",
11332
11650
  {
11333
11651
  type: "button",
11652
+ className: "db-mobile-first-run-secondary",
11334
11653
  onClick: () => {
11335
11654
  closeWelcomeGateway();
11336
11655
  setMobileShowEditor(true);
11337
11656
  },
11338
11657
  children: "Tour the welcome document"
11339
11658
  }
11340
- ),
11341
- /* @__PURE__ */ jsx17(
11342
- "button",
11343
- {
11344
- type: "button",
11345
- className: "db-mobile-first-run-secondary",
11346
- onClick: () => void handleNewFile(),
11347
- children: "Create a document"
11348
- }
11349
11659
  )
11350
11660
  ] })
11351
11661
  ] }),
11352
- git.available && git.pendingGrant && /* @__PURE__ */ jsx17(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx17(GitGrantNotice, {}) }),
11353
- /* @__PURE__ */ jsxs15("div", { className: "db-shell-sidebar-footer", children: [
11354
- /* @__PURE__ */ jsx17("a", { href: "https://docblocks.com/docs/", target: "_blank", rel: "noopener noreferrer", children: "Docs" }),
11355
- /* @__PURE__ */ jsx17("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
11356
- /* @__PURE__ */ jsx17("a", { href: "https://docblocks.com/terms/", target: "_blank", rel: "noopener noreferrer", children: "Terms" }),
11357
- /* @__PURE__ */ jsx17("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
11358
- /* @__PURE__ */ jsx17("a", { href: issueReportUrl, target: "_blank", rel: "noopener noreferrer", children: "Report issue" }),
11359
- showBrowserStorageWarning && /* @__PURE__ */ jsx17(Fragment11, { children: /* @__PURE__ */ jsxs15(
11662
+ git.available && git.pendingGrant && /* @__PURE__ */ jsx18(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx18(GitGrantNotice, {}) }),
11663
+ /* @__PURE__ */ jsxs16("div", { className: "db-shell-sidebar-footer", children: [
11664
+ /* @__PURE__ */ jsx18("a", { href: "https://docblocks.com/docs/", target: "_blank", rel: "noopener noreferrer", children: "Docs" }),
11665
+ /* @__PURE__ */ jsx18("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
11666
+ /* @__PURE__ */ jsx18("a", { href: "https://docblocks.com/terms/", target: "_blank", rel: "noopener noreferrer", children: "Terms" }),
11667
+ /* @__PURE__ */ jsx18("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
11668
+ /* @__PURE__ */ jsx18("a", { href: issueReportUrl, target: "_blank", rel: "noopener noreferrer", children: "Report issue" }),
11669
+ showBrowserStorageWarning && /* @__PURE__ */ jsx18(Fragment11, { children: /* @__PURE__ */ jsxs16(
11360
11670
  "button",
11361
11671
  {
11362
11672
  type: "button",
@@ -11365,20 +11675,20 @@ function DocBlocksShell({
11365
11675
  "aria-label": "Back up all workspaces",
11366
11676
  title: "Browsers can clear site data. Download a backup of every workspace.",
11367
11677
  children: [
11368
- /* @__PURE__ */ jsx17(BackupIcon, {}),
11678
+ /* @__PURE__ */ jsx18(BackupIcon, {}),
11369
11679
  "Back up"
11370
11680
  ]
11371
11681
  }
11372
11682
  ) })
11373
11683
  ] }),
11374
- /* @__PURE__ */ jsxs15("div", { className: "db-sidebar-collapse-preview", "aria-hidden": "true", children: [
11375
- /* @__PURE__ */ jsx17("span", { className: "db-sidebar-collapse-preview-icon", children: /* @__PURE__ */ jsx17(CollapseSidebarGlyph, {}) }),
11376
- /* @__PURE__ */ jsx17("span", { className: "db-sidebar-collapse-preview-label", children: "Release to hide files" })
11684
+ /* @__PURE__ */ jsxs16("div", { className: "db-sidebar-collapse-preview", "aria-hidden": "true", children: [
11685
+ /* @__PURE__ */ jsx18("span", { className: "db-sidebar-collapse-preview-icon", children: /* @__PURE__ */ jsx18(CollapseSidebarGlyph, {}) }),
11686
+ /* @__PURE__ */ jsx18("span", { className: "db-sidebar-collapse-preview-label", children: "Release to hide files" })
11377
11687
  ] })
11378
11688
  ]
11379
11689
  }
11380
11690
  ),
11381
- !singlePane && /* @__PURE__ */ jsx17(
11691
+ !singlePane && /* @__PURE__ */ jsx18(
11382
11692
  "div",
11383
11693
  {
11384
11694
  className: "db-shell-sidebar-resizer",
@@ -11388,7 +11698,7 @@ function DocBlocksShell({
11388
11698
  onPointerDown: handleResizerPointerDown
11389
11699
  }
11390
11700
  ),
11391
- singlePane && /* @__PURE__ */ jsx17(
11701
+ singlePane && /* @__PURE__ */ jsx18(
11392
11702
  "div",
11393
11703
  {
11394
11704
  className: "db-shell-scrim",
@@ -11396,7 +11706,7 @@ function DocBlocksShell({
11396
11706
  onPointerDown: () => setMobileShowEditor(true)
11397
11707
  }
11398
11708
  ),
11399
- /* @__PURE__ */ jsxs15(
11709
+ /* @__PURE__ */ jsxs16(
11400
11710
  "main",
11401
11711
  {
11402
11712
  ref: editorAreaRef,
@@ -11407,8 +11717,8 @@ function DocBlocksShell({
11407
11717
  editorDropActive ? "db-shell-editor-area--drop-active" : ""
11408
11718
  ].filter(Boolean).join(" "),
11409
11719
  children: [
11410
- selectedFile && mediaProvider ? /* @__PURE__ */ jsxs15(Fragment11, { children: [
11411
- selectedManagedOutput === "catalog-html" ? /* @__PURE__ */ jsx17(
11720
+ /* @__PURE__ */ jsxs16("div", { ref: welcomeCoveredRef, className: "db-shell-editor-content", children: [
11721
+ selectedFile && mediaProvider ? /* @__PURE__ */ jsx18(Fragment11, { children: selectedManagedOutput === "catalog-html" ? /* @__PURE__ */ jsx18(
11412
11722
  GeneratedCatalogPage,
11413
11723
  {
11414
11724
  html: editorContent,
@@ -11416,17 +11726,17 @@ function DocBlocksShell({
11416
11726
  onOpenSettings: activeWorkspaceDescriptor ? handleOpenWorkspaceSettings : void 0,
11417
11727
  onBack: singlePane ? openDrawer : void 0
11418
11728
  }
11419
- ) : /* @__PURE__ */ jsx17(
11729
+ ) : /* @__PURE__ */ jsx18(
11420
11730
  GeneratedFileFrame,
11421
11731
  {
11422
11732
  kind: selectedManagedOutput,
11423
11733
  onOpenSettings: activeWorkspaceDescriptor ? handleOpenWorkspaceSettings : void 0,
11424
11734
  onBack: singlePane ? openDrawer : void 0,
11425
- children: /* @__PURE__ */ jsx17(
11735
+ children: /* @__PURE__ */ jsx18(
11426
11736
  Suspense2,
11427
11737
  {
11428
- fallback: /* @__PURE__ */ jsx17("div", { className: "db-shell-empty", role: "status", children: "Loading your document\u2026" }),
11429
- children: editorGenerationSettled && mediaProviderCurrent && /* @__PURE__ */ jsx17(EditorErrorBoundary, { resetKey: `${selectedFile}-${editorKey}`, children: /* @__PURE__ */ jsx17(
11738
+ fallback: /* @__PURE__ */ jsx18("div", { className: "db-shell-empty", role: "status", children: "Loading your document\u2026" }),
11739
+ children: editorGenerationSettled && mediaProviderCurrent && /* @__PURE__ */ jsx18(EditorErrorBoundary, { resetKey: `${selectedFile}-${editorKey}`, children: /* @__PURE__ */ jsx18(
11430
11740
  EditorShell,
11431
11741
  {
11432
11742
  initialMarkdown: editorContent,
@@ -11443,6 +11753,7 @@ function DocBlocksShell({
11443
11753
  saveDashboardImageOutput: saveRenderedImageOutput,
11444
11754
  onChange: handleEditorChange,
11445
11755
  onLinkClick: handleEditorLinkClick,
11756
+ wikiLinks,
11446
11757
  colorScheme: resolvedTheme,
11447
11758
  defaultThemeId: workspaceDefaultThemeId,
11448
11759
  defaultThemeLabel: "Workspace default",
@@ -11476,19 +11787,19 @@ function DocBlocksShell({
11476
11787
  versioningAutoSaveIdleMs,
11477
11788
  onSaveVersion,
11478
11789
  statusBarSlotRight,
11479
- sidePanelSlot: ai && aiPanel && selectedImage === void 0 && (selectedOutsideIn === null || selectedOutsideInEditingEnabled) ? /* @__PURE__ */ jsx17(Suspense2, { fallback: null, children: aiPanel === "review" ? /* @__PURE__ */ jsx17(AiReviewPanel, { ai, onClose: () => setAiPanel(null) }) : /* @__PURE__ */ jsx17(AiIllustratePanel, { ai, onClose: () => setAiPanel(null) }) }) : void 0,
11480
- toolbarSlotLeft: singlePane ? /* @__PURE__ */ jsx17(
11790
+ sidePanelSlot: ai && aiPanel && selectedImage === void 0 && (selectedOutsideIn === null || selectedOutsideInEditingEnabled) ? /* @__PURE__ */ jsx18(Suspense2, { fallback: null, children: aiPanel === "review" ? /* @__PURE__ */ jsx18(AiReviewPanel, { ai, onClose: () => setAiPanel(null) }) : /* @__PURE__ */ jsx18(AiIllustratePanel, { ai, onClose: () => setAiPanel(null) }) }) : void 0,
11791
+ toolbarSlotLeft: singlePane ? /* @__PURE__ */ jsx18(
11481
11792
  "button",
11482
11793
  {
11483
11794
  className: "db-mobile-back",
11484
11795
  onClick: () => setMobileShowEditor(false),
11485
11796
  "aria-label": "Show file list",
11486
- children: /* @__PURE__ */ jsx17("span", { className: "db-mobile-files-icon", children: /* @__PURE__ */ jsx17(FolderGlyph, {}) })
11797
+ children: /* @__PURE__ */ jsx18("span", { className: "db-mobile-files-icon", children: /* @__PURE__ */ jsx18(FolderGlyph, {}) })
11487
11798
  }
11488
11799
  ) : void 0,
11489
- summarizationDesigner: selectedImage === void 0 && selectedManagedOutput === void 0 && (selectedOutsideIn === null || selectedOutsideInEditingEnabled) ? (onClose) => /* @__PURE__ */ jsx17(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx17(PresentationMaker, { ai: ai ?? void 0, onClose }) }) : void 0,
11490
- toolbarSlotRight: /* @__PURE__ */ jsxs15(Fragment11, { children: [
11491
- canFind && /* @__PURE__ */ jsx17(
11800
+ summarizationDesigner: selectedImage === void 0 && selectedManagedOutput === void 0 && (selectedOutsideIn === null || selectedOutsideInEditingEnabled) ? (onClose) => /* @__PURE__ */ jsx18(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx18(PresentationMaker, { ai: ai ?? void 0, onClose }) }) : void 0,
11801
+ toolbarSlotRight: /* @__PURE__ */ jsxs16(Fragment11, { children: [
11802
+ canFind && /* @__PURE__ */ jsx18(
11492
11803
  FindButton,
11493
11804
  {
11494
11805
  active: findMode,
@@ -11496,7 +11807,7 @@ function DocBlocksShell({
11496
11807
  shortcutLabel: findShortcut
11497
11808
  }
11498
11809
  ),
11499
- ai && /* @__PURE__ */ jsx17(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx17(
11810
+ ai && /* @__PURE__ */ jsx18(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx18(
11500
11811
  AiToolbarControl,
11501
11812
  {
11502
11813
  ai,
@@ -11505,8 +11816,8 @@ function DocBlocksShell({
11505
11816
  onOpenPanel: setAiPanel
11506
11817
  }
11507
11818
  ) }),
11508
- speech && /* @__PURE__ */ jsxs15(Suspense2, { fallback: null, children: [
11509
- /* @__PURE__ */ jsx17(
11819
+ speech && /* @__PURE__ */ jsxs16(Suspense2, { fallback: null, children: [
11820
+ /* @__PURE__ */ jsx18(
11510
11821
  TextFromNarration,
11511
11822
  {
11512
11823
  speech,
@@ -11516,27 +11827,27 @@ function DocBlocksShell({
11516
11827
  onOpenSettings: openSettings
11517
11828
  }
11518
11829
  ),
11519
- hostSupports2("speechOutput") ? /* @__PURE__ */ jsx17(
11830
+ hostSupports2("speechOutput") ? /* @__PURE__ */ jsx18(
11520
11831
  SpeechToolbarControl,
11521
11832
  {
11522
11833
  speech,
11523
11834
  onOpenSettings: openSettings,
11524
11835
  readOnly: selectedImage !== void 0 || selectedOutsideIn !== null && !selectedOutsideInEditingEnabled
11525
11836
  }
11526
- ) : /* @__PURE__ */ jsx17(SpeechMenuBridge, {})
11837
+ ) : /* @__PURE__ */ jsx18(SpeechMenuBridge, {})
11527
11838
  ] }),
11528
- canRestoreSplitView && /* @__PURE__ */ jsx17(
11839
+ canRestoreSplitView && /* @__PURE__ */ jsx18(
11529
11840
  "button",
11530
11841
  {
11531
11842
  className: "db-restore-split",
11532
11843
  onClick: () => updateSidebarPreference("pinned"),
11533
11844
  "aria-label": "Restore split view",
11534
11845
  title: "Restore split view",
11535
- children: /* @__PURE__ */ jsx17(SplitViewIcon, {})
11846
+ children: /* @__PURE__ */ jsx18(SplitViewIcon, {})
11536
11847
  }
11537
11848
  ),
11538
- git.repo && /* @__PURE__ */ jsx17(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx17(GitToolbarControl, { selectedFile }) }),
11539
- /* @__PURE__ */ jsx17(
11849
+ git.repo && /* @__PURE__ */ jsx18(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx18(GitToolbarControl, { selectedFile }) }),
11850
+ /* @__PURE__ */ jsx18(
11540
11851
  ExportToolbarControls,
11541
11852
  {
11542
11853
  selectedFile,
@@ -11560,87 +11871,77 @@ function DocBlocksShell({
11560
11871
  }
11561
11872
  )
11562
11873
  }
11563
- ),
11564
- showWelcomeGateway && !singlePane && /* @__PURE__ */ jsxs15("div", { className: "db-welcome-gateway", role: "note", "aria-label": "Welcome tip", children: [
11565
- /* @__PURE__ */ jsxs15("span", { className: "db-welcome-gateway-text", children: [
11566
- "You\u2019re watching this welcome doc in ",
11567
- /* @__PURE__ */ jsx17("strong", { children: "Slideshow" }),
11568
- " ",
11569
- "view\u2014it\u2019s a regular Markdown file, and so is everything you\u2019ll write."
11874
+ ) }) : selectedFolder ? /* @__PURE__ */ jsxs16("div", { className: "db-folder-view", children: [
11875
+ singlePane && /* @__PURE__ */ jsxs16("button", { className: "db-mobile-back", onClick: () => setMobileShowEditor(false), children: [
11876
+ /* @__PURE__ */ jsx18("span", { className: "db-mobile-files-icon", children: /* @__PURE__ */ jsx18(FolderGlyph, {}) }),
11877
+ "Back to files"
11570
11878
  ] }),
11571
- /* @__PURE__ */ jsx17("button", { className: "db-welcome-gateway-cta", onClick: handleStartWriting, children: "Start writing" }),
11572
- /* @__PURE__ */ jsx17(
11573
- "button",
11879
+ /* @__PURE__ */ jsxs16("div", { className: "db-folder-view-header", children: [
11880
+ /* @__PURE__ */ jsx18("span", { className: "db-folder-view-icon", children: /* @__PURE__ */ jsx18(FolderGlyph, {}) }),
11881
+ /* @__PURE__ */ jsx18("span", { className: "db-folder-view-path", children: selectedFolder })
11882
+ ] }),
11883
+ visibleFolderEntries.length === 0 ? /* @__PURE__ */ jsx18("p", { className: "db-folder-view-empty", children: "This folder is empty." }) : /* @__PURE__ */ jsx18("ul", { className: "db-folder-view-list", children: visibleFolderEntries.map((entry) => /* @__PURE__ */ jsxs16(
11884
+ "li",
11574
11885
  {
11575
- className: "db-welcome-gateway-dismiss",
11576
- onClick: closeWelcomeGateway,
11577
- "aria-label": "Dismiss welcome tip",
11578
- title: "Dismiss",
11579
- children: "\xD7"
11580
- }
11581
- )
11582
- ] })
11583
- ] }) : selectedFolder ? /* @__PURE__ */ jsxs15("div", { className: "db-folder-view", children: [
11584
- singlePane && /* @__PURE__ */ jsxs15("button", { className: "db-mobile-back", onClick: () => setMobileShowEditor(false), children: [
11585
- /* @__PURE__ */ jsx17("span", { className: "db-mobile-files-icon", children: /* @__PURE__ */ jsx17(FolderGlyph, {}) }),
11586
- "Back to files"
11587
- ] }),
11588
- /* @__PURE__ */ jsxs15("div", { className: "db-folder-view-header", children: [
11589
- /* @__PURE__ */ jsx17("span", { className: "db-folder-view-icon", children: /* @__PURE__ */ jsx17(FolderGlyph, {}) }),
11590
- /* @__PURE__ */ jsx17("span", { className: "db-folder-view-path", children: selectedFolder })
11886
+ className: "db-folder-view-item",
11887
+ onClick: () => handleSelect(entry.path, entry.kind),
11888
+ children: [
11889
+ /* @__PURE__ */ jsx18("span", { className: "db-folder-view-item-icon", children: entry.kind === "directory" ? /* @__PURE__ */ jsx18(FolderGlyph, {}) : /* @__PURE__ */ jsx18(FileGlyph, {}) }),
11890
+ entry.name
11891
+ ]
11892
+ },
11893
+ entry.path
11894
+ )) })
11895
+ ] }) : /* @__PURE__ */ jsxs16("div", { className: "db-shell-empty db-shell-empty--workspace", children: [
11896
+ singlePane && /* @__PURE__ */ jsxs16("button", { className: "db-mobile-back", onClick: () => setMobileShowEditor(false), children: [
11897
+ /* @__PURE__ */ jsx18("span", { className: "db-mobile-files-icon", children: /* @__PURE__ */ jsx18(FolderGlyph, {}) }),
11898
+ "Back to files"
11899
+ ] }),
11900
+ /* @__PURE__ */ jsxs16("div", { className: "db-workspace-empty-content", children: [
11901
+ /* @__PURE__ */ jsx18("span", { className: "db-workspace-empty-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx18(FileGlyph, {}) }),
11902
+ /* @__PURE__ */ jsx18("h1", { children: activeWorkspaceDescriptor?.name ?? "Workspace" }),
11903
+ /* @__PURE__ */ jsx18("p", { children: "Choose a Markdown document from the sidebar, or create a new one." }),
11904
+ /* @__PURE__ */ jsxs16("div", { className: "db-workspace-empty-actions", children: [
11905
+ /* @__PURE__ */ jsx18("button", { type: "button", onClick: () => void handleNewFile(), children: "New document" }),
11906
+ /* @__PURE__ */ jsx18(
11907
+ "button",
11908
+ {
11909
+ type: "button",
11910
+ className: "db-workspace-empty-secondary",
11911
+ onClick: () => void handleNewFolder(),
11912
+ children: "New folder"
11913
+ }
11914
+ ),
11915
+ (hostSupports2("workspaceFolderPicker") || typeof globalThis.showDirectoryPicker === "function") && /* @__PURE__ */ jsx18(
11916
+ "button",
11917
+ {
11918
+ type: "button",
11919
+ className: "db-workspace-empty-secondary",
11920
+ onClick: () => void handleOpenFolder(),
11921
+ children: "Open a folder"
11922
+ }
11923
+ )
11924
+ ] }),
11925
+ /* @__PURE__ */ jsx18("p", { className: "db-workspace-empty-hint", children: "Use the sidebar backup action to keep a portable copy of this workspace." })
11926
+ ] })
11591
11927
  ] }),
11592
- visibleFolderEntries.length === 0 ? /* @__PURE__ */ jsx17("p", { className: "db-folder-view-empty", children: "This folder is empty." }) : /* @__PURE__ */ jsx17("ul", { className: "db-folder-view-list", children: visibleFolderEntries.map((entry) => /* @__PURE__ */ jsxs15(
11593
- "li",
11928
+ /* @__PURE__ */ jsx18(
11929
+ UpdateAvailableNotice,
11594
11930
  {
11595
- className: "db-folder-view-item",
11596
- onClick: () => handleSelect(entry.path, entry.kind),
11597
- children: [
11598
- /* @__PURE__ */ jsx17("span", { className: "db-folder-view-item-icon", children: entry.kind === "directory" ? /* @__PURE__ */ jsx17(FolderGlyph, {}) : /* @__PURE__ */ jsx17(FileGlyph, {}) }),
11599
- entry.name
11600
- ]
11601
- },
11602
- entry.path
11603
- )) })
11604
- ] }) : /* @__PURE__ */ jsxs15("div", { className: "db-shell-empty db-shell-empty--workspace", children: [
11605
- singlePane && /* @__PURE__ */ jsxs15("button", { className: "db-mobile-back", onClick: () => setMobileShowEditor(false), children: [
11606
- /* @__PURE__ */ jsx17("span", { className: "db-mobile-files-icon", children: /* @__PURE__ */ jsx17(FolderGlyph, {}) }),
11607
- "Back to files"
11608
- ] }),
11609
- /* @__PURE__ */ jsxs15("div", { className: "db-workspace-empty-content", children: [
11610
- /* @__PURE__ */ jsx17("span", { className: "db-workspace-empty-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx17(FileGlyph, {}) }),
11611
- /* @__PURE__ */ jsx17("h1", { children: activeWorkspaceDescriptor?.name ?? "Workspace" }),
11612
- /* @__PURE__ */ jsx17("p", { children: "Choose a Markdown document from the sidebar, or create a new one." }),
11613
- /* @__PURE__ */ jsxs15("div", { className: "db-workspace-empty-actions", children: [
11614
- /* @__PURE__ */ jsx17("button", { type: "button", onClick: () => void handleNewFile(), children: "New document" }),
11615
- /* @__PURE__ */ jsx17(
11616
- "button",
11617
- {
11618
- type: "button",
11619
- className: "db-workspace-empty-secondary",
11620
- onClick: () => void handleNewFolder(),
11621
- children: "New folder"
11622
- }
11623
- ),
11624
- (hostSupports2("workspaceFolderPicker") || typeof globalThis.showDirectoryPicker === "function") && /* @__PURE__ */ jsx17(
11625
- "button",
11626
- {
11627
- type: "button",
11628
- className: "db-workspace-empty-secondary",
11629
- onClick: () => void handleOpenFolder(),
11630
- children: "Open a folder"
11631
- }
11632
- )
11633
- ] }),
11634
- /* @__PURE__ */ jsx17("p", { className: "db-workspace-empty-hint", children: "Use the sidebar backup action to keep a portable copy of this workspace." })
11635
- ] })
11931
+ available: updateAvailable,
11932
+ onApplyUpdate,
11933
+ blocked: documentSnapshot.conflict !== null || storageFull,
11934
+ statusBarVisible: updateStatusBarVisible
11935
+ }
11936
+ )
11636
11937
  ] }),
11637
- /* @__PURE__ */ jsx17(
11638
- UpdateAvailableNotice,
11938
+ welcomeScreenShown && /* @__PURE__ */ jsx18(
11939
+ WelcomeScreen,
11639
11940
  {
11640
- available: updateAvailable,
11641
- onApplyUpdate,
11642
- blocked: documentSnapshot.conflict !== null || storageFull,
11643
- statusBarVisible: updateStatusBarVisible
11941
+ onCreateDocument: handleNewFile,
11942
+ onTour: handleTourWelcome,
11943
+ onDismiss: handleTourWelcome,
11944
+ offerApps: hostEnvironment === null
11644
11945
  }
11645
11946
  )
11646
11947
  ]