@rightkit/document-workspace 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE-APACHE +201 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +74 -2
  5. package/dist/app/App.d.ts +1 -0
  6. package/dist/app/App.js +6 -0
  7. package/dist/app/AppDialogs.d.ts +52 -0
  8. package/dist/app/AppDialogs.js +23 -0
  9. package/dist/app/AppDialogsHost.d.ts +31 -0
  10. package/dist/app/AppDialogsHost.js +30 -0
  11. package/dist/app/AppLifecycle.d.ts +19 -0
  12. package/dist/app/AppLifecycle.js +87 -0
  13. package/dist/app/AppProvider.d.ts +317 -0
  14. package/dist/app/AppProvider.js +217 -0
  15. package/dist/app/AppRoot.d.ts +8 -0
  16. package/dist/app/AppRoot.js +159 -0
  17. package/dist/app/AppShell.d.ts +2 -0
  18. package/dist/app/AppShell.js +81 -0
  19. package/dist/app/CommandPalette.d.ts +13 -0
  20. package/dist/app/CommandPalette.js +47 -0
  21. package/dist/app/DocActionBar.d.ts +36 -0
  22. package/dist/app/DocActionBar.js +300 -0
  23. package/dist/app/DocumentContextMenu.d.ts +29 -0
  24. package/dist/app/DocumentContextMenu.js +122 -0
  25. package/dist/app/DocumentPane.d.ts +52 -0
  26. package/dist/app/DocumentPane.js +30 -0
  27. package/dist/app/DocumentRouterTable.d.ts +58 -0
  28. package/dist/app/DocumentRouterTable.js +143 -0
  29. package/dist/app/ErrorBoundary.d.ts +12 -0
  30. package/dist/app/ErrorBoundary.js +16 -0
  31. package/dist/app/FindBar.d.ts +12 -0
  32. package/dist/app/FindBar.js +199 -0
  33. package/dist/app/LiveTitle.d.ts +14 -0
  34. package/dist/app/LiveTitle.js +14 -0
  35. package/dist/app/OpeningProgress.d.ts +13 -0
  36. package/dist/app/OpeningProgress.js +21 -0
  37. package/dist/app/PrintPreviewWindow.d.ts +2 -0
  38. package/dist/app/PrintPreviewWindow.js +57 -0
  39. package/dist/app/ShortcutHelp.d.ts +3 -0
  40. package/dist/app/ShortcutHelp.js +99 -0
  41. package/dist/app/Sidebar.d.ts +36 -0
  42. package/dist/app/Sidebar.js +121 -0
  43. package/dist/app/ToastViewport.d.ts +1 -0
  44. package/dist/app/ToastViewport.js +31 -0
  45. package/dist/app/Toolbar.d.ts +26 -0
  46. package/dist/app/Toolbar.js +63 -0
  47. package/dist/app/activeFormatSavers.d.ts +7 -0
  48. package/dist/app/activeFormatSavers.js +4 -0
  49. package/dist/app/clipboardPaths.d.ts +1 -0
  50. package/dist/app/clipboardPaths.js +31 -0
  51. package/dist/app/documentDrafts.d.ts +17 -0
  52. package/dist/app/documentDrafts.js +104 -0
  53. package/dist/app/draftResolution.d.ts +5 -0
  54. package/dist/app/draftResolution.js +25 -0
  55. package/dist/app/markdownPaletteActions.d.ts +8 -0
  56. package/dist/app/markdownPaletteActions.js +107 -0
  57. package/dist/app/resourceCensusDev.d.ts +41 -0
  58. package/dist/app/resourceCensusDev.js +518 -0
  59. package/dist/app/setupRecovery.d.ts +34 -0
  60. package/dist/app/setupRecovery.js +70 -0
  61. package/dist/app/shell/DocStatsPill.d.ts +18 -0
  62. package/dist/app/shell/DocStatsPill.js +32 -0
  63. package/dist/app/shell/DocumentTools.d.ts +9 -0
  64. package/dist/app/shell/DocumentTools.js +73 -0
  65. package/dist/app/shell/InfoInspector.d.ts +9 -0
  66. package/dist/app/shell/InfoInspector.js +67 -0
  67. package/dist/app/shell/StartPage.d.ts +9 -0
  68. package/dist/app/shell/StartPage.js +49 -0
  69. package/dist/app/shell/readEdit.d.ts +6 -0
  70. package/dist/app/shell/readEdit.js +6 -0
  71. package/dist/app/sidebar/SidebarDocumentMenu.d.ts +20 -0
  72. package/dist/app/sidebar/SidebarDocumentMenu.js +56 -0
  73. package/dist/app/sidebar/SidebarExpandedPanel.d.ts +9 -0
  74. package/dist/app/sidebar/SidebarExpandedPanel.js +78 -0
  75. package/dist/app/sidebar/SidebarIcons.d.ts +3 -0
  76. package/dist/app/sidebar/SidebarIcons.js +18 -0
  77. package/dist/app/sidebar/SidebarMenus.d.ts +19 -0
  78. package/dist/app/sidebar/SidebarMenus.js +64 -0
  79. package/dist/app/sidebar/SidebarPathUtils.d.ts +10 -0
  80. package/dist/app/sidebar/SidebarPathUtils.js +55 -0
  81. package/dist/app/sidebar/SidebarPrimitives.d.ts +12 -0
  82. package/dist/app/sidebar/SidebarPrimitives.js +45 -0
  83. package/dist/app/sidebar/SidebarRail.d.ts +5 -0
  84. package/dist/app/sidebar/SidebarRail.js +37 -0
  85. package/dist/app/sidebar/explorer/ExplorerRowMenu.d.ts +21 -0
  86. package/dist/app/sidebar/explorer/ExplorerRowMenu.js +37 -0
  87. package/dist/app/sidebar/explorer/ExplorerSections.d.ts +16 -0
  88. package/dist/app/sidebar/explorer/ExplorerSections.js +259 -0
  89. package/dist/app/sidebar/explorer/ExplorerTree.d.ts +46 -0
  90. package/dist/app/sidebar/explorer/ExplorerTree.js +387 -0
  91. package/dist/app/sidebar/explorer/explorerState.d.ts +37 -0
  92. package/dist/app/sidebar/explorer/explorerState.js +135 -0
  93. package/dist/app/tabMountPolicy.d.ts +45 -0
  94. package/dist/app/tabMountPolicy.js +104 -0
  95. package/dist/app/tabWarmOrder.d.ts +7 -0
  96. package/dist/app/tabWarmOrder.js +48 -0
  97. package/dist/app/tabs/TabFuseCurves.d.ts +3 -0
  98. package/dist/app/tabs/TabFuseCurves.js +15 -0
  99. package/dist/app/tabs/TabOverview.d.ts +24 -0
  100. package/dist/app/tabs/TabOverview.js +121 -0
  101. package/dist/app/tabs/TabStrip.d.ts +42 -0
  102. package/dist/app/tabs/TabStrip.js +380 -0
  103. package/dist/app/tabs/tabIds.d.ts +5 -0
  104. package/dist/app/tabs/tabIds.js +7 -0
  105. package/dist/app/tabs/useTabDirty.d.ts +3 -0
  106. package/dist/app/tabs/useTabDirty.js +11 -0
  107. package/dist/app/useAppChromeState.d.ts +21 -0
  108. package/dist/app/useAppChromeState.js +48 -0
  109. package/dist/app/useAppCommands.d.ts +66 -0
  110. package/dist/app/useAppCommands.js +71 -0
  111. package/dist/app/useAppDocumentCore.d.ts +210 -0
  112. package/dist/app/useAppDocumentCore.js +40 -0
  113. package/dist/app/useAppEffects.d.ts +18 -0
  114. package/dist/app/useAppEffects.js +52 -0
  115. package/dist/app/useAppPaneSearch.d.ts +37 -0
  116. package/dist/app/useAppPaneSearch.js +74 -0
  117. package/dist/app/useAppShellSetup.d.ts +40 -0
  118. package/dist/app/useAppShellSetup.js +52 -0
  119. package/dist/app/useAppSlots.d.ts +28 -0
  120. package/dist/app/useAppSlots.js +40 -0
  121. package/dist/app/useAppTabChrome.d.ts +51 -0
  122. package/dist/app/useAppTabChrome.js +66 -0
  123. package/dist/app/useAutoSave.d.ts +34 -0
  124. package/dist/app/useAutoSave.js +116 -0
  125. package/dist/app/useCloseAll.d.ts +52 -0
  126. package/dist/app/useCloseAll.js +295 -0
  127. package/dist/app/useClosedTabEviction.d.ts +10 -0
  128. package/dist/app/useClosedTabEviction.js +38 -0
  129. package/dist/app/useCommandPalette.d.ts +44 -0
  130. package/dist/app/useCommandPalette.js +254 -0
  131. package/dist/app/useCrossDocSearch.d.ts +7 -0
  132. package/dist/app/useCrossDocSearch.js +94 -0
  133. package/dist/app/useDistractionFree.d.ts +21 -0
  134. package/dist/app/useDistractionFree.js +73 -0
  135. package/dist/app/useDocumentCreation.d.ts +18 -0
  136. package/dist/app/useDocumentCreation.js +63 -0
  137. package/dist/app/useDocumentDirty.d.ts +31 -0
  138. package/dist/app/useDocumentDirty.js +46 -0
  139. package/dist/app/useDocumentLoader.d.ts +55 -0
  140. package/dist/app/useDocumentLoader.js +267 -0
  141. package/dist/app/useDocumentOutputActions.d.ts +6 -0
  142. package/dist/app/useDocumentOutputActions.js +45 -0
  143. package/dist/app/useDocumentRuntimeEffects.d.ts +36 -0
  144. package/dist/app/useDocumentRuntimeEffects.js +458 -0
  145. package/dist/app/useDocumentSaver.d.ts +58 -0
  146. package/dist/app/useDocumentSaver.js +511 -0
  147. package/dist/app/useDocumentTabs.d.ts +41 -0
  148. package/dist/app/useDocumentTabs.js +207 -0
  149. package/dist/app/useDocumentWatcher.d.ts +83 -0
  150. package/dist/app/useDocumentWatcher.js +393 -0
  151. package/dist/app/useDocumentZoom.d.ts +24 -0
  152. package/dist/app/useDocumentZoom.js +104 -0
  153. package/dist/app/useDocuments.d.ts +169 -0
  154. package/dist/app/useDocuments.js +455 -0
  155. package/dist/app/useDraftConfirmGate.d.ts +27 -0
  156. package/dist/app/useDraftConfirmGate.js +47 -0
  157. package/dist/app/useFileDragOverlay.d.ts +12 -0
  158. package/dist/app/useFileDragOverlay.js +49 -0
  159. package/dist/app/useFormatApis.d.ts +13 -0
  160. package/dist/app/useFormatApis.js +62 -0
  161. package/dist/app/useFormatDirtyState.d.ts +29 -0
  162. package/dist/app/useFormatDirtyState.js +81 -0
  163. package/dist/app/useGlobalShortcuts.d.ts +47 -0
  164. package/dist/app/useGlobalShortcuts.js +359 -0
  165. package/dist/app/useHtmlSession.d.ts +68 -0
  166. package/dist/app/useHtmlSession.js +463 -0
  167. package/dist/app/useHtmlSessionProps.d.ts +33 -0
  168. package/dist/app/useHtmlSessionProps.js +57 -0
  169. package/dist/app/useLibrary.d.ts +21 -0
  170. package/dist/app/useLibrary.js +81 -0
  171. package/dist/app/useOpenFromClipboard.d.ts +7 -0
  172. package/dist/app/useOpenFromClipboard.js +44 -0
  173. package/dist/app/usePaneLiveness.d.ts +29 -0
  174. package/dist/app/usePaneLiveness.js +199 -0
  175. package/dist/app/usePendingSavePaths.d.ts +19 -0
  176. package/dist/app/usePendingSavePaths.js +67 -0
  177. package/dist/app/useReadyShow.d.ts +12 -0
  178. package/dist/app/useReadyShow.js +142 -0
  179. package/dist/app/useSearchController.d.ts +22 -0
  180. package/dist/app/useSearchController.js +114 -0
  181. package/dist/app/useSetupRecovery.d.ts +41 -0
  182. package/dist/app/useSetupRecovery.js +153 -0
  183. package/dist/app/useTabChrome.d.ts +40 -0
  184. package/dist/app/useTabChrome.js +115 -0
  185. package/dist/app/useViewState.d.ts +27 -0
  186. package/dist/app/useViewState.js +160 -0
  187. package/dist/appName.d.ts +2 -0
  188. package/dist/appName.js +8 -0
  189. package/dist/documents/DocumentRouter.d.ts +102 -0
  190. package/dist/documents/DocumentRouter.js +153 -0
  191. package/dist/documents/FileTypeIcon.d.ts +6 -0
  192. package/dist/documents/FileTypeIcon.js +93 -0
  193. package/dist/documents/ImageDocument.d.ts +14 -0
  194. package/dist/documents/ImageDocument.js +61 -0
  195. package/dist/documents/capabilities.d.ts +17 -0
  196. package/dist/documents/capabilities.js +18 -0
  197. package/dist/documents/html/HtmlDocument.d.ts +15 -0
  198. package/dist/documents/html/HtmlDocument.js +584 -0
  199. package/dist/documents/html/HtmlDocumentLayout.d.ts +87 -0
  200. package/dist/documents/html/HtmlDocumentLayout.js +25 -0
  201. package/dist/documents/html/HtmlDocumentProps.d.ts +60 -0
  202. package/dist/documents/html/HtmlDocumentProps.js +1 -0
  203. package/dist/documents/html/HtmlDocumentSource.d.ts +34 -0
  204. package/dist/documents/html/HtmlDocumentSource.js +28 -0
  205. package/dist/documents/markdown/MarkdownSplitPane.d.ts +115 -0
  206. package/dist/documents/markdown/MarkdownSplitPane.js +352 -0
  207. package/dist/documents/mdx/MdxSplitPane.d.ts +32 -0
  208. package/dist/documents/mdx/MdxSplitPane.js +60 -0
  209. package/dist/documents/txt/LinkDialog.d.ts +8 -0
  210. package/dist/documents/txt/LinkDialog.js +34 -0
  211. package/dist/documents/txt/MarkdownSourceToolbar.d.ts +8 -0
  212. package/dist/documents/txt/MarkdownSourceToolbar.js +25 -0
  213. package/dist/documents/txt/SourceEditorToolbar.d.ts +12 -0
  214. package/dist/documents/txt/SourceEditorToolbar.js +4 -0
  215. package/dist/documents/txt/TxtEditor.d.ts +39 -0
  216. package/dist/documents/txt/TxtEditor.js +186 -0
  217. package/dist/documents/txt/sourceEditorTypes.d.ts +49 -0
  218. package/dist/documents/txt/sourceEditorTypes.js +1 -0
  219. package/dist/documents/txt/sourceExtensions.d.ts +38 -0
  220. package/dist/documents/txt/sourceExtensions.js +444 -0
  221. package/dist/documents/txt/sourceFormatting.d.ts +18 -0
  222. package/dist/documents/txt/sourceFormatting.js +183 -0
  223. package/dist/documents/txt/sourceTheme.d.ts +5 -0
  224. package/dist/documents/txt/sourceTheme.js +220 -0
  225. package/dist/documents/txt/useCodeMirrorSourceEditor.d.ts +69 -0
  226. package/dist/documents/txt/useCodeMirrorSourceEditor.js +546 -0
  227. package/dist/documents/txt/useSourceSearch.d.ts +39 -0
  228. package/dist/documents/txt/useSourceSearch.js +122 -0
  229. package/dist/documents/txt/useTextEditorCommands.d.ts +26 -0
  230. package/dist/documents/txt/useTextEditorCommands.js +151 -0
  231. package/dist/documents/web/LivePreviewPane.d.ts +11 -0
  232. package/dist/documents/web/LivePreviewPane.js +58 -0
  233. package/dist/env.d.ts +66 -0
  234. package/dist/index.d.ts +121 -0
  235. package/dist/index.js +123 -0
  236. package/dist/settings/AppearanceMenu.d.ts +9 -0
  237. package/dist/settings/AppearanceMenu.js +54 -0
  238. package/dist/settings/SettingsPanel.d.ts +7 -0
  239. package/dist/settings/SettingsPanel.js +104 -0
  240. package/dist/settings/publishedSections.d.ts +22 -0
  241. package/dist/settings/publishedSections.js +79 -0
  242. package/dist/settings/sections/AboutSection.d.ts +1 -0
  243. package/dist/settings/sections/AboutSection.js +13 -0
  244. package/dist/settings/sections/DiagnosticsSection.d.ts +1 -0
  245. package/dist/settings/sections/DiagnosticsSection.js +42 -0
  246. package/dist/settings/sections/PrivacySection.d.ts +3 -0
  247. package/dist/settings/sections/PrivacySection.js +51 -0
  248. package/dist/settings/sections/UpdateLicenseBlock.d.ts +10 -0
  249. package/dist/settings/sections/UpdateLicenseBlock.js +158 -0
  250. package/dist/state/explorer.d.ts +32 -0
  251. package/dist/state/explorer.js +111 -0
  252. package/dist/state/legal.d.ts +51 -0
  253. package/dist/state/legal.js +130 -0
  254. package/dist/state/library.d.ts +33 -0
  255. package/dist/state/library.js +110 -0
  256. package/dist/state/updateLicense.d.ts +31 -0
  257. package/dist/state/updateLicense.js +340 -0
  258. package/dist/styles/explorer.css +219 -0
  259. package/dist/styles/index.css +1477 -0
  260. package/dist/styles/tabs.css +467 -0
  261. package/dist/styles.css +6 -0
  262. package/package.json +88 -3
@@ -0,0 +1,267 @@
1
+ import { useCallback } from "react";
2
+ import { isMarkdownFormat } from "../documents/capabilities.js";
3
+ import { isUnsupportedBinary, } from "@rightkit/document-core/documents/types";
4
+ import { isHostAppOrigin, normalizeLivePreviewUrl, webDisplayTitle, webTabPath, } from "@rightkit/document-core/documents/web/webUrl";
5
+ import { openFileDialog, readDocument, grantKnownFileToken } from "@rightkit/document-core/state/tauriBridge";
6
+ import { recordRecent } from "../state/library.js";
7
+ import { readFileDraft, clearFileDraft } from "./documentDrafts.js";
8
+ import { errorMessage } from "@rightkit/document-core/state/errors";
9
+ import { logError, logEvent, logPerf } from "@rightkit/document-core/state/diagnostics";
10
+ import { hostAppName } from "../appName.js";
11
+ /**
12
+ * Open/read paths into tabs: file-picker open, live-URL "tabs", reopening a known-recent entry, the
13
+ * open-file dialog, and paging to a sibling image in place. Extracted verbatim from useDocuments —
14
+ * bodies, error handling, restore-draft logic, and view-default selection are unchanged. `activate`
15
+ * stays with the orchestrator (it's just an activePath setter, and requestClose/doClose/reload all
16
+ * need it too), so loader functions take it as a dependency instead of owning it.
17
+ */
18
+ export function useDocumentLoader({ openDocs, setOpenDocs, setViews, viewsRef, viewMetaRef, writeEdits, setError, setOpeningPath, setActivePath, fingerprints, refreshLibrary, syncPinned, isDirty, clearImageDirty, withTabKey, }) {
19
+ const activate = useCallback(async (path) => {
20
+ // Pure state change. Every open file is watched (App's watch effect re-arms on the open set), so an
21
+ // external change to ANY tab — active or background — arrives via the `file-changed` event and
22
+ // reloads that tab when it happens. So activation does NOT re-stat or reload-on-switch; switching
23
+ // tabs is instant, and a background tab is already fresh by the time you click it.
24
+ setActivePath(path);
25
+ }, [setActivePath]);
26
+ const open = useCallback(async (path, opts = {}) => {
27
+ const startedAt = performance.now();
28
+ if (!opts.keepError)
29
+ setError(null);
30
+ logEvent("open:start");
31
+ if (isUnsupportedBinary(path)) {
32
+ setError(`${hostAppName()} can't open ${path.split(".").pop()?.toUpperCase()} files yet. Supported: Markdown, TXT, HTML, DOCX, PDF, and images (PNG, JPG, WEBP, GIF, BMP, SVG, ICO, AVIF).`);
33
+ logEvent("open:unsupported");
34
+ return;
35
+ }
36
+ setOpeningPath(path);
37
+ try {
38
+ const loaded = await readDocument(path);
39
+ logPerf("open:read", startedAt);
40
+ if (loaded.fingerprint)
41
+ fingerprints.current.set(path, loaded.fingerprint);
42
+ setOpenDocs((prev) => {
43
+ const existing = prev.find((d) => d.path === path);
44
+ const next = withTabKey(loaded, existing?.tabKey);
45
+ const docs = existing ? prev.map((d) => (d.path === path ? next : d)) : [...prev, next];
46
+ return opts.arrange ? opts.arrange(docs) : docs;
47
+ });
48
+ // Notepad-style restore for file-backed txt: if the user left unsaved edits last session and
49
+ // the file hasn't changed on disk since, silently restore the in-progress text. If the file
50
+ // changed on disk (base mismatch), the draft is stale — discard it so the user sees fresh disk.
51
+ if (loaded.format === "txt") {
52
+ const draft = readFileDraft(path);
53
+ if (draft) {
54
+ if (draft.base === loaded.content && draft.text !== loaded.content) {
55
+ writeEdits((e) => ({ ...e, [path]: draft.text }));
56
+ }
57
+ else {
58
+ clearFileDraft(path); // stale — file changed on disk; discard silently
59
+ }
60
+ }
61
+ }
62
+ // HTML always opens in Interactive; Markdown always opens in Preview. Source is a within-session
63
+ // drawer toggle, NOT a sticky per-file default — so neither format restores a persisted source/
64
+ // split view (you always land on the rendered view; hit the switch for source).
65
+ const defaultView = loaded.format === "html" ? "interactive" : "preview";
66
+ const restoredView = loaded.format === "html"
67
+ ? "interactive"
68
+ : isMarkdownFormat(loaded.format)
69
+ ? "preview"
70
+ : loaded.lastView;
71
+ setViews((v) => ({ ...v, [path]: v[path] ?? restoredView ?? defaultView }));
72
+ viewsRef.current[path] = viewsRef.current[path] ?? restoredView ?? defaultView;
73
+ viewMetaRef.current[path] = loaded.viewMeta ?? {};
74
+ // Prefetch the renderer's lazy chunk so the FIRST tab activation (after this background
75
+ // open) doesn't pay a 200–1000ms Suspense fallback. Each chunk is small (HTML pulls in
76
+ // CodeMirror via TxtEditor for its source drawer; MD pulls in remark/unified), so warming
77
+ // them in parallel with the open's other awaits costs nothing extra. The DocumentRouter
78
+ // useEffect at lines 131–145 still re-prefetches as a safety net on tab activation.
79
+ // Errors here are non-fatal — a chunk that can't load in the current environment (e.g.
80
+ // jsdom missing DOMMatrix for PDF.js) just makes the first activation pay its lazy cost;
81
+ // production code is unaffected because every browser/embedded WebView has DOMMatrix.
82
+ switch (loaded.format) {
83
+ case "html":
84
+ void import("../documents/html/HtmlDocument.js").catch(() => { });
85
+ void import("../documents/txt/TxtEditor.js").catch(() => { }); // HTML source drawer uses CodeMirror
86
+ break;
87
+ case "md":
88
+ void import("../documents/markdown/MarkdownSplitPane.js").catch(() => { });
89
+ void import("@rightkit/markdown-editor/documents/markdown/MarkdownCoreProvider").catch(() => { });
90
+ void import("../documents/txt/TxtEditor.js").catch(() => { });
91
+ break;
92
+ case "mdx":
93
+ void import("../documents/mdx/MdxSplitPane.js").catch(() => { });
94
+ void import("@rightkit/markdown-editor/documents/mdx/mdxCompiler").catch(() => { });
95
+ void import("../documents/markdown/MarkdownSplitPane.js").catch(() => { }); // shared TxtEditor + outline code
96
+ void import("../documents/txt/TxtEditor.js").catch(() => { });
97
+ break;
98
+ case "docx":
99
+ void import("@rightkit/docx-editor/documents/docx/DocxPane").catch(() => { });
100
+ break;
101
+ case "pdf":
102
+ void import("@rightkit/document-core/documents/pdf/PdfViewer").catch(() => { });
103
+ break;
104
+ case "image":
105
+ void import("../documents/ImageDocument.js").catch(() => { });
106
+ break;
107
+ // "txt" reuses TxtEditor's chunk too; the prefetch is idempotent.
108
+ case "txt":
109
+ case "web":
110
+ void import("../documents/txt/TxtEditor.js").catch(() => { });
111
+ break;
112
+ default:
113
+ break;
114
+ }
115
+ // Background open (Ctrl/middle-click "open in new tab"): add the tab but DON'T switch to it —
116
+ // the current document stays mounted and keeps its scroll position, like a browser. Skipping
117
+ // activate() also skips the active-file watcher swap; clicking the tab later activates normally.
118
+ if (!opts.background)
119
+ await activate(path);
120
+ // The document is on screen once activation lands. Release the spinner and mark the open
121
+ // READY here rather than after the library writes below: those only feed the sidebar's
122
+ // recent list, and awaiting them used to hold the indicator up for ~1s on a large recents
123
+ // DB (see the 3.7s open:ready in the 2026-08-08 diagnostics). The `finally` clear becomes a
124
+ // no-op second call. The bookkeeping is still awaited BELOW rather than detached, so it can
125
+ // never settle after the tab (or the whole app) is torn down.
126
+ setOpeningPath((current) => (current === path ? null : current));
127
+ logPerf("open:ready", startedAt);
128
+ // recordRecent and syncPinned touch independent DB tables (record_recent vs. is_pinned_path)
129
+ // and neither depends on the other's result — run them concurrently. Callers doing a batch
130
+ // of background opens (session restore) skip the per-open library refresh and refresh once
131
+ // after the whole batch instead — refreshLibrary re-queries the entire recents list, so
132
+ // doing it per-tab is redundant work during a multi-tab restore.
133
+ const libraryStartedAt = performance.now();
134
+ await Promise.all([recordRecent(loaded), syncPinned(loaded.path)]);
135
+ if (!opts.deferLibraryRefresh)
136
+ await refreshLibrary();
137
+ logPerf("open:library", libraryStartedAt);
138
+ }
139
+ catch (e) {
140
+ const message = errorMessage(e);
141
+ setError(message);
142
+ logError("open:failed");
143
+ logPerf("open:error", startedAt);
144
+ }
145
+ finally {
146
+ // Several paths may be opened concurrently (for example Ctrl+click). Only the request
147
+ // that still owns the indicator may clear it.
148
+ setOpeningPath((current) => (current === path ? null : current));
149
+ }
150
+ }, [
151
+ activate,
152
+ refreshLibrary,
153
+ syncPinned,
154
+ setOpenDocs,
155
+ setViews,
156
+ viewsRef,
157
+ viewMetaRef,
158
+ withTabKey,
159
+ fingerprints,
160
+ writeEdits,
161
+ setError,
162
+ setOpeningPath,
163
+ ]);
164
+ const openLiveUrl = useCallback(async (input, opts = {}) => {
165
+ setError(null);
166
+ const url = normalizeLivePreviewUrl(input);
167
+ if (!url) {
168
+ setError("Open URL accepts http(s) URLs, localhost previews, and bare domains.");
169
+ return;
170
+ }
171
+ if (isHostAppOrigin(url)) {
172
+ setError(`${hostAppName()} cannot frame its own app origin. Use the localhost port for the app you want to preview.`);
173
+ return;
174
+ }
175
+ const path = webTabPath(url);
176
+ if (openDocs.some((d) => d.path === path)) {
177
+ if (!opts.background)
178
+ await activate(path);
179
+ return;
180
+ }
181
+ const web = { kind: "url", url };
182
+ const doc = withTabKey({
183
+ path,
184
+ displayName: webDisplayTitle(web),
185
+ format: "web",
186
+ content: "",
187
+ web,
188
+ });
189
+ setOpenDocs((prev) => [...prev, doc]);
190
+ setViews((v) => ({ ...v, [path]: "preview" }));
191
+ viewsRef.current[path] = "preview";
192
+ viewMetaRef.current[path] = {};
193
+ // Background (new-tab gesture): add it without stealing focus from the current document.
194
+ if (!opts.background)
195
+ await activate(path);
196
+ }, [activate, openDocs, setOpenDocs, setViews, viewsRef, viewMetaRef, withTabKey, setError]);
197
+ const openRecent = useCallback(async (entry) => {
198
+ const path = await grantKnownFileToken(entry.token);
199
+ if (!path)
200
+ return;
201
+ if (openDocs.some((d) => d.path === path))
202
+ return activate(path);
203
+ return open(path);
204
+ }, [openDocs, activate, open]);
205
+ const onOpenDialog = useCallback(async () => {
206
+ const path = await openFileDialog();
207
+ if (path)
208
+ await open(path);
209
+ }, [open]);
210
+ // Page to a neighbouring image in the same folder (Windows-Photos style): REPLACE the current
211
+ // image tab's document in place — same tab slot, no pile-up — so scrolling a 200-image folder
212
+ // never spawns 200 tabs. Guards: a current image with unsaved edits opens the neighbour as its own
213
+ // tab instead (never silently discards edits); a neighbour already open elsewhere just activates.
214
+ // The target path was pre-granted by `list_sibling_images`, so readDocument is authorized.
215
+ const openImageSibling = useCallback(async (currentPath, targetPath) => {
216
+ if (targetPath === currentPath)
217
+ return;
218
+ const cur = openDocs.find((d) => d.path === currentPath);
219
+ if (cur && isDirty(cur)) {
220
+ await open(targetPath);
221
+ return;
222
+ }
223
+ if (openDocs.some((d) => d.path === targetPath)) {
224
+ await activate(targetPath);
225
+ return;
226
+ }
227
+ try {
228
+ const loaded = await readDocument(targetPath);
229
+ if (loaded.fingerprint)
230
+ fingerprints.current.set(targetPath, loaded.fingerprint);
231
+ setOpenDocs((prev) => prev.map((d) => (d.path === currentPath ? withTabKey(loaded, d.tabKey) : d)));
232
+ setViews((v) => ({ ...v, [targetPath]: "preview" }));
233
+ viewsRef.current[targetPath] = "preview";
234
+ viewMetaRef.current[targetPath] = {};
235
+ clearImageDirty(currentPath);
236
+ await activate(targetPath);
237
+ // Deliberately NOT recordRecent here: paging through a folder shouldn't flood the recent
238
+ // list with every image scrolled past (only the file you explicitly opened is "recent").
239
+ await syncPinned(loaded.path);
240
+ }
241
+ catch (e) {
242
+ setError(errorMessage(e));
243
+ }
244
+ }, [
245
+ openDocs,
246
+ isDirty,
247
+ open,
248
+ activate,
249
+ clearImageDirty,
250
+ syncPinned,
251
+ setOpenDocs,
252
+ setViews,
253
+ viewsRef,
254
+ viewMetaRef,
255
+ withTabKey,
256
+ fingerprints,
257
+ setError,
258
+ ]);
259
+ return {
260
+ activate,
261
+ open,
262
+ openLiveUrl,
263
+ openRecent,
264
+ onOpenDialog,
265
+ openImageSibling,
266
+ };
267
+ }
@@ -0,0 +1,6 @@
1
+ import type { LoadedDocument } from "@rightkit/document-core/documents/types";
2
+ /** External-edit and print entry points (no shared document state beyond the error setter). */
3
+ export declare function useDocumentOutputActions(setError: (message: string | null) => void): {
4
+ onEditImageExternal: (doc: LoadedDocument | null) => void;
5
+ onPrint: (doc: LoadedDocument | null) => void;
6
+ };
@@ -0,0 +1,45 @@
1
+ import { useCallback } from "react";
2
+ import { editImageExternal, printCurrentWebview } from "@rightkit/document-core/state/tauriBridge";
3
+ import { printPdf } from "@rightkit/document-core/app/documentPrinting";
4
+ import { errorMessage } from "@rightkit/document-core/state/errors";
5
+ import { toast } from "@rightkit/platform-ui/headless";
6
+ import { logError, logEvent } from "@rightkit/document-core/state/diagnostics";
7
+ /** External-edit and print entry points (no shared document state beyond the error setter). */
8
+ export function useDocumentOutputActions(setError) {
9
+ const onEditImageExternal = useCallback((doc) => {
10
+ if (doc?.format !== "image")
11
+ return;
12
+ void editImageExternal(doc.path).catch((e) => setError(errorMessage(e)));
13
+ }, [setError]);
14
+ const onPrint = useCallback((doc) => {
15
+ if (doc?.format === "web") {
16
+ toast("Printing web tabs isn't supported yet", "info");
17
+ return;
18
+ }
19
+ if (doc?.format === "pdf" && doc.assetUrl) {
20
+ void printPdf(doc.assetUrl)
21
+ .then((printed) => {
22
+ if (printed)
23
+ return;
24
+ logError("print:failed:pdf");
25
+ toast("Couldn't open print dialog", "error");
26
+ })
27
+ .catch(() => {
28
+ logError("print:failed:pdf");
29
+ toast("Couldn't open print dialog", "error");
30
+ });
31
+ return;
32
+ }
33
+ if (!window.dispatchEvent(new Event("viewright:print", { cancelable: true })))
34
+ return;
35
+ // Frame previews claim print above; native documents use the window-level print command.
36
+ logEvent("print:requested");
37
+ void printCurrentWebview().then((printed) => {
38
+ if (printed)
39
+ return;
40
+ logError("print:failed");
41
+ toast("Couldn't open print dialog", "error");
42
+ });
43
+ }, []);
44
+ return { onEditImageExternal, onPrint };
45
+ }
@@ -0,0 +1,36 @@
1
+ import { type Dispatch, type MutableRefObject, type SetStateAction } from "react";
2
+ import { type LoadedDocument, type ViewMode } from "@rightkit/document-core/documents/types";
3
+ import type { BlockDraftRegistry } from "@rightkit/document-core/app/blockDrafts";
4
+ import type { OpenDocumentOptions } from "./useDocumentLoader.js";
5
+ /** Route native open-file events after the Markdown canvas has had a chance to consume image drops. */
6
+ export declare function handleOpenFileEvent(path: string, open: (path: string) => Promise<unknown>): void;
7
+ /** Persist the session tabs with at most one native write in flight, newest snapshot last, so
8
+ * out-of-order IPC completion can never leave an older, shorter tab list as the saved session. */
9
+ export declare function saveSessionOrdered(tabs: Array<[string, boolean]>): void;
10
+ /** Drop any queued session snapshot and wait for the in-flight native write to finish, so a purge
11
+ * cannot be followed by a stale write that resurrects deleted rows. The next normal save writes the
12
+ * current tabs only. */
13
+ export declare function drainSessionSaves(): Promise<void>;
14
+ export declare function nativeAutomationOwnsSession(): boolean;
15
+ interface RuntimeEffectsDeps {
16
+ openDocs: LoadedDocument[];
17
+ activePath: string | null;
18
+ hydrated: MutableRefObject<boolean>;
19
+ untitledSeq: MutableRefObject<number>;
20
+ refreshLibrary: () => Promise<void>;
21
+ setOpenDocs: Dispatch<SetStateAction<LoadedDocument[]>>;
22
+ writeEdits: (updater: (prev: Record<string, string>) => Record<string, string>) => void;
23
+ setViews: Dispatch<SetStateAction<Record<string, ViewMode>>>;
24
+ activate: (path: string) => Promise<void>;
25
+ open: (path: string, opts?: OpenDocumentOptions) => Promise<void>;
26
+ reload: (path: string) => void;
27
+ isDirty: (doc: LoadedDocument) => boolean;
28
+ setConfirmQuit: Dispatch<SetStateAction<boolean>>;
29
+ prepareSetupRecovery: () => Promise<void>;
30
+ restoreSetupRecovery: () => Promise<string | null>;
31
+ restoreSession: boolean;
32
+ blockDrafts?: BlockDraftRegistry;
33
+ setRestoringSession: Dispatch<SetStateAction<boolean>>;
34
+ }
35
+ export declare function useDocumentRuntimeEffects({ openDocs, activePath, hydrated, untitledSeq, refreshLibrary, setOpenDocs, writeEdits, setViews, activate, open, reload, isDirty, setConfirmQuit, prepareSetupRecovery, restoreSetupRecovery, restoreSession, blockDrafts, setRestoringSession, }: RuntimeEffectsDeps): void;
36
+ export {};