@rightkit/document-core 0.1.1 → 0.2.0

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 (136) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +31 -7
  3. package/dist/app/chrome.js +1 -1
  4. package/dist/app/documentPrinting.d.ts +0 -6
  5. package/dist/app/documentPrinting.js +0 -18
  6. package/dist/app/shell/nativeMenu.d.ts +3 -2
  7. package/dist/app/shell/nativeMenu.js +8 -5
  8. package/dist/documents/perfEventCounters.d.ts +0 -8
  9. package/dist/documents/perfEventCounters.js +2 -1
  10. package/dist/documents/preview/previewFrame.d.ts +2 -1
  11. package/dist/documents/preview/previewFrame.js +9 -7
  12. package/dist/documents/searchUtils.js +1 -1
  13. package/dist/host.d.ts +36 -0
  14. package/dist/host.js +54 -0
  15. package/dist/index.d.ts +1 -50
  16. package/dist/index.js +1 -50
  17. package/dist/settings/themes.js +1 -1
  18. package/dist/state/bridgeEvents.d.ts +1 -1
  19. package/dist/state/bridgeEvents.js +7 -4
  20. package/dist/state/recentUrls.js +4 -3
  21. package/dist/state/tauriBridge.d.ts +3 -61
  22. package/dist/state/tauriBridge.js +8 -297
  23. package/dist/styles.css +2 -2
  24. package/package.json +25 -98
  25. package/dist/app/shortcutChords.d.ts +0 -6
  26. package/dist/app/shortcutChords.js +0 -14
  27. package/dist/app/useCloseFindOnSurfaceChange.d.ts +0 -14
  28. package/dist/app/useCloseFindOnSurfaceChange.js +0 -23
  29. package/dist/app/useEditText.d.ts +0 -6
  30. package/dist/app/useEditText.js +0 -10
  31. package/dist/appName.d.ts +0 -2
  32. package/dist/appName.js +0 -8
  33. package/dist/documents/FullscreenIcon.d.ts +0 -4
  34. package/dist/documents/FullscreenIcon.js +0 -7
  35. package/dist/documents/mdx/mdxRuntimeScript.d.ts +0 -2
  36. package/dist/documents/mdx/mdxRuntimeScript.js +0 -3
  37. package/dist/documents/mdx/mdxRuntimeScript.txt +0 -584
  38. package/dist/documents/pdf/MacPdfReadPane.d.ts +0 -23
  39. package/dist/documents/pdf/MacPdfReadPane.js +0 -708
  40. package/dist/documents/pdf/NativePdfDocument.d.ts +0 -9
  41. package/dist/documents/pdf/NativePdfDocument.js +0 -41
  42. package/dist/documents/pdf/PdfInlineTextEditor.d.ts +0 -53
  43. package/dist/documents/pdf/PdfInlineTextEditor.js +0 -225
  44. package/dist/documents/pdf/PdfOcrLayer.d.ts +0 -4
  45. package/dist/documents/pdf/PdfOcrLayer.js +0 -30
  46. package/dist/documents/pdf/PdfOutline.d.ts +0 -6
  47. package/dist/documents/pdf/PdfOutline.js +0 -12
  48. package/dist/documents/pdf/PdfPasswordPrompt.d.ts +0 -5
  49. package/dist/documents/pdf/PdfPasswordPrompt.js +0 -24
  50. package/dist/documents/pdf/PdfThumbnails.d.ts +0 -8
  51. package/dist/documents/pdf/PdfThumbnails.js +0 -201
  52. package/dist/documents/pdf/PdfToolbar.d.ts +0 -47
  53. package/dist/documents/pdf/PdfToolbar.js +0 -76
  54. package/dist/documents/pdf/PdfViewer.d.ts +0 -36
  55. package/dist/documents/pdf/PdfViewer.js +0 -1522
  56. package/dist/documents/pdf/PdfViewerBody.d.ts +0 -22
  57. package/dist/documents/pdf/PdfViewerBody.js +0 -42
  58. package/dist/documents/pdf/macPdfNativeBridge.d.ts +0 -118
  59. package/dist/documents/pdf/macPdfNativeBridge.js +0 -134
  60. package/dist/documents/pdf/nearestVisiblePdfPage.d.ts +0 -2
  61. package/dist/documents/pdf/nearestVisiblePdfPage.js +0 -22
  62. package/dist/documents/pdf/pdfBench.d.ts +0 -88
  63. package/dist/documents/pdf/pdfBench.js +0 -535
  64. package/dist/documents/pdf/pdfBenchAutopilot.d.ts +0 -48
  65. package/dist/documents/pdf/pdfBenchAutopilot.js +0 -154
  66. package/dist/documents/pdf/pdfBenchTransport.d.ts +0 -21
  67. package/dist/documents/pdf/pdfBenchTransport.js +0 -233
  68. package/dist/documents/pdf/pdfEditModel.d.ts +0 -294
  69. package/dist/documents/pdf/pdfEditModel.js +0 -105
  70. package/dist/documents/pdf/pdfEditor.d.ts +0 -100
  71. package/dist/documents/pdf/pdfEditor.js +0 -68
  72. package/dist/documents/pdf/pdfOcr.d.ts +0 -36
  73. package/dist/documents/pdf/pdfOcr.js +0 -88
  74. package/dist/documents/pdf/pdfOcrEdit.d.ts +0 -32
  75. package/dist/documents/pdf/pdfOcrEdit.js +0 -138
  76. package/dist/documents/pdf/pdfPrinting.d.ts +0 -28
  77. package/dist/documents/pdf/pdfPrinting.js +0 -110
  78. package/dist/documents/pdf/pdfSave.d.ts +0 -87
  79. package/dist/documents/pdf/pdfSave.js +0 -250
  80. package/dist/documents/pdf/pdfShared.d.ts +0 -16
  81. package/dist/documents/pdf/pdfShared.js +0 -20
  82. package/dist/documents/pdf/pdfToMarkdown.d.ts +0 -11
  83. package/dist/documents/pdf/pdfToMarkdown.js +0 -67
  84. package/dist/documents/pdf/pdfjsLibGlobal.d.ts +0 -1
  85. package/dist/documents/pdf/pdfjsLibGlobal.js +0 -12
  86. package/dist/documents/pdf/toolbarIcons.d.ts +0 -18
  87. package/dist/documents/pdf/toolbarIcons.js +0 -31
  88. package/dist/documents/pdf/usePdfDocumentLoader.d.ts +0 -40
  89. package/dist/documents/pdf/usePdfDocumentLoader.js +0 -243
  90. package/dist/documents/pdf/usePdfEditSession.d.ts +0 -88
  91. package/dist/documents/pdf/usePdfEditSession.js +0 -341
  92. package/dist/documents/pdf/usePdfEditor.d.ts +0 -59
  93. package/dist/documents/pdf/usePdfEditor.js +0 -317
  94. package/dist/documents/pdf/usePdfFit.d.ts +0 -19
  95. package/dist/documents/pdf/usePdfFit.js +0 -55
  96. package/dist/documents/pdf/usePdfOcr.d.ts +0 -38
  97. package/dist/documents/pdf/usePdfOcr.js +0 -337
  98. package/dist/documents/pdf/usePdfPreviewRecompose.d.ts +0 -28
  99. package/dist/documents/pdf/usePdfPreviewRecompose.js +0 -256
  100. package/dist/documents/pdf/usePdfSavePlan.d.ts +0 -61
  101. package/dist/documents/pdf/usePdfSavePlan.js +0 -303
  102. package/dist/documents/pdf/usePdfSearch.d.ts +0 -31
  103. package/dist/documents/pdf/usePdfSearch.js +0 -367
  104. package/dist/documents/pdf/usePdfViewControls.d.ts +0 -72
  105. package/dist/documents/pdf/usePdfViewControls.js +0 -152
  106. package/dist/documents/scopedSelectAll.d.ts +0 -5
  107. package/dist/documents/scopedSelectAll.js +0 -77
  108. package/dist/documents/txt/cmScroll.d.ts +0 -11
  109. package/dist/documents/txt/cmScroll.js +0 -52
  110. package/dist/documents/useMirroredEditText.d.ts +0 -15
  111. package/dist/documents/useMirroredEditText.js +0 -29
  112. package/dist/documents/useSplitResize.d.ts +0 -7
  113. package/dist/documents/useSplitResize.js +0 -37
  114. package/dist/settings/rk-settings.css +0 -38
  115. package/dist/settings/sections/EditorSection.d.ts +0 -1
  116. package/dist/settings/sections/EditorSection.js +0 -18
  117. package/dist/settings/sections/FileAssociationsSection.d.ts +0 -1
  118. package/dist/settings/sections/FileAssociationsSection.js +0 -5
  119. package/dist/settings/sections/MacFileAssociationsBlock.d.ts +0 -33
  120. package/dist/settings/sections/MacFileAssociationsBlock.js +0 -151
  121. package/dist/settings/sections/ThemeSection.d.ts +0 -1
  122. package/dist/settings/sections/ThemeSection.js +0 -31
  123. package/dist/settings/sections/TypographySection.d.ts +0 -1
  124. package/dist/settings/sections/TypographySection.js +0 -19
  125. package/dist/settings/sections/onOff.d.ts +0 -11
  126. package/dist/settings/sections/onOff.js +0 -8
  127. package/dist/state/browserQa.d.ts +0 -21
  128. package/dist/state/browserQa.js +0 -303
  129. package/dist/state/browserQaDocx.d.ts +0 -3
  130. package/dist/state/browserQaDocx.js +0 -35
  131. package/dist/state/browserQaLicense.d.ts +0 -8
  132. package/dist/state/browserQaLicense.js +0 -7
  133. package/dist/state/browserQaPdf.d.ts +0 -60
  134. package/dist/state/browserQaPdf.js +0 -302
  135. package/dist/state/browserQaText.d.ts +0 -6
  136. package/dist/state/browserQaText.js +0 -48
@@ -1,5 +1,4 @@
1
1
  import { type FileFingerprint, type LoadedDocument } from "../documents/types.js";
2
- import type { LicenseClient } from "@rightkit/license";
3
2
  import { isTauri } from "@rightkit/platform-ui";
4
3
  import { type Shell } from "@rightkit/tauri-shell";
5
4
  /** True when running inside the Tauri webview (vs. plain `vite dev` in a browser). */
@@ -12,7 +11,6 @@ export declare function getShell(): Shell;
12
11
  * reports a copy that did not happen (an absent browser clipboard used to read as success).
13
12
  */
14
13
  export declare function writeClipboardText(text: string): Promise<void>;
15
- export declare function isBrowserQaMode(): boolean;
16
14
  declare function invoke<T>(cmd: string, args?: Record<string, unknown>): Promise<T>;
17
15
  declare function listen<T>(event: string, callback: (event: {
18
16
  payload: T;
@@ -26,12 +24,6 @@ export declare const nativeIpc: {
26
24
  invoke: typeof invoke;
27
25
  listen: typeof listen;
28
26
  };
29
- /**
30
- * Browser-QA licence client (`@rightkit/license/testing`, in-memory, no network). Returns null
31
- * outside browser QA, so the caller uses the native client. The `testing` export resolves only
32
- * under the `development` condition and the call site folds to `false` in a release build.
33
- */
34
- export declare function browserQaLicenseClient(): Promise<LicenseClient | null>;
35
27
  /** Classified shape every bridge caller can rely on. `code` matches the Rust `HostError`
36
28
  * variant in snake_case (e.g. "broker_denied", "file_not_found"), or "unknown" when the rejection
37
29
  * didn't carry the envelope shape at all (a raw JS Error, a plain string, or something else). */
@@ -79,7 +71,7 @@ export declare function readRelativeScript(docPath: string, rel: string, rootPat
79
71
  * rejection (escapes the granted root, not an openable type). The grant is brokered in Rust. */
80
72
  export declare function grantRelativeFile(docPath: string, rel: string, rootPath?: string, siteToken?: string): Promise<string | null>;
81
73
  /** Infer and grant this HTML tab's site root (nearest enclosing project directory), returning the
82
- * opaque token that backs `viewright-site://` subresource URLs plus the granted directory for
74
+ * opaque token that backs `<host id>-site://` subresource URLs plus the granted directory for
83
75
  * display. Returns null outside Tauri, so browser/dev/test builds fall back to inlining. */
84
76
  export declare function grantSiteRoot(docPath: string): Promise<{
85
77
  token: string;
@@ -111,6 +103,7 @@ export declare function revokePath(path: string): Promise<void>;
111
103
  /** Write text, preserving the file's `encoding` (default UTF-8). Returns actual encoding plus the
112
104
  * committed source SHA-256; in-place callers send the prior SHA for stale-source protection. */
113
105
  export declare function writeDocument(path: string, content: string, encoding?: string, expectedSourceSha256?: string): Promise<WriteDocumentResult>;
106
+ export declare function byteArrayToBase64(bytes: Uint8Array): string;
114
107
  export declare function writeDocumentBytes(path: string, data: ArrayBuffer): Promise<void>;
115
108
  /** Copy an already-authorized document to a user-selected write target without decoding or
116
109
  * reserializing its bytes. The desktop command owns both broker checks and the filesystem copy so
@@ -119,57 +112,6 @@ export declare function copyDocument(sourcePath: string, targetPath: string): Pr
119
112
  /** Open a web/mail link in the system browser (never navigate the app webview away). */
120
113
  export declare function openExternal(url: string): Promise<void>;
121
114
  export declare function editImageExternal(path: string): Promise<void>;
122
- import type { ComposePdfPreview, CommitPdfSave, InspectPdfPage, ListPdfFonts, PreparePdfSave } from "../documents/pdf/pdfEditModel.js";
123
- import type { OcrLine } from "../documents/pdf/pdfOcr.js";
124
- /** OCR one rasterized PDF page. The PNG is the raw request body, not JSON metadata. */
125
- export declare const ocrPage: (png: ArrayBuffer | Uint8Array) => Promise<OcrLine[]>;
126
- /** Probe whether native OCR can be offered. This deliberately returns false outside Tauri instead
127
- * of using requireTauriForPdfEdit: callers use it to decide whether to offer recognition at all.
128
- * Non-throwing contract: a backend/IPC failure resolves false. */
129
- export declare const ocrAvailable: () => Promise<boolean>;
130
- export declare const ocrBackendMissing: () => Promise<boolean>;
131
- /** §5.2 `inspect_pdf_page`. Walks one page's content stream and returns its authoritative source
132
- * graph (islands, locators, digests). `path` must already be a broker-granted read target. */
133
- export declare const inspectPdfPage: InspectPdfPage;
134
- /** §7.2 `list_pdf_fonts`. Returns the bundled + system font catalog available for embedding. */
135
- export declare const listPdfFonts: ListPdfFonts;
136
- /** §9.4 `compose_pdf_preview`. Memory-only recomposition of staged source commands against fresh
137
- * disk bytes, for committed-unsaved-edit preview re-render — no token, no retained candidate, no
138
- * writes. Candidate bytes use JSON; affected pages derive from request commands. */
139
- export declare const composePdfPreview: ComposePdfPreview;
140
- /** §10.1 `prepare_pdf_save`. The frozen frontend `PreparePdfSave` type (`pdfEditModel.ts`) expects
141
- * one call returning `PreparedPdfSave` (metadata + `candidateBytes` together), but the actual Rust
142
- * implementation returns candidate bytes as a numeric JSON array because WKWebView cannot
143
- * reliably consume this staged candidate through Tauri's raw-response or large-base64 paths.
144
- *
145
- * Judgment call: this bridge function internally calls both backend commands in sequence and
146
- * combines their results into the single `PreparedPdfSave` shape B7's `pdfSave.ts` was written
147
- * against, so the frozen frontend type/signature is satisfied without B7 needing to know about the
148
- * two-call split. `expectedText` field names (`page`/`text`) come from the backend's
149
- * `ExpectedTextCheck { page, text }`; mapped here to `pdfEditModel.ts`'s `PdfExpectedTextCheck`
150
- * shape (`page`/`islandId`/`expectedText`) — no `islandId` exists in the backend response, so it is
151
- * populated as an empty string (the backend recheck matches by page + text substring only, not by
152
- * island id; see `pdf_save.rs`'s `structurally_verify_candidate`). */
153
- export declare const preparePdfSave: PreparePdfSave;
154
- /** §10.3 `commit_pdf_save`. Consumes the single-use save token and atomically writes the staged
155
- * candidate to `targetPath` (or back to the original source path for a same-path Save). The
156
- * backend's `PdfCommitResult` (`pdf_save.rs`) carries only `{ targetPath, documentSha256 }` — it
157
- * has no `revision`/`affectedPages` fields, unlike the richer shape `pdfEditModel.ts` sketches as
158
- * "inferred" (that file's own comment flags this exact field set as not given anywhere in the
159
- * contract text). Rather than fabricate an `affectedPages`/`revision` this command never returns,
160
- * this bridge function follows the caller-refresh pattern already used elsewhere in this file
161
- * (e.g. `readDocument`'s post-write re-read): the caller (`pdfSave.ts`) is expected to re-run
162
- * `inspectPdfPage` per page after a successful commit to obtain a fresh revision, rather than this
163
- * bridge inventing data. `committedPath`/`committedSha256` map 1:1 from the backend's
164
- * `targetPath`/`documentSha256`; `affectedPages` is returned empty and `revision` as an
165
- * empty-pages placeholder so the return type still satisfies `PdfCommitResult` structurally. */
166
- export declare const commitPdfSave: CommitPdfSave;
167
- /** Browser-QA-only: current bytes of the in-memory QA PDF "disk" at `path`, or `null` outside QA
168
- * mode / for an untouched path. PdfViewer.tsx's post-commit reload uses this instead of
169
- * re-fetching its `url` prop — a real Tauri stream token is a revocable token that re-resolves to
170
- * the same (now-committed) file on every fetch, but browser QA's `url` is a static `data:` URL
171
- * captured once at document-open time, which can never reflect a later commit on its own. */
172
- export declare function qaReloadPdfBytes(path: string): Promise<Uint8Array | null>;
173
115
  export declare function setInspectableHtmlPreview(token: string | null): void;
174
116
  /** Clear the DevTools owner, but only if `token` still owns it (a newer frame may have replaced it). */
175
117
  export declare function releaseInspectableHtmlPreview(token: string): void;
@@ -177,7 +119,7 @@ export declare function releaseInspectableHtmlPreview(token: string): void;
177
119
  * content window that has no IPC, and the shell opens that window's inspector (decision D2). The
178
120
  * main webview is never inspectable. No-op without an active HTML preview or outside Tauri. */
179
121
  export declare function openDevtools(): Promise<void>;
180
- /** No-op: the macOS inspect-accelerator scope command was removed with the shell port (viewright#375). */
122
+ /** No-op: the macOS inspect-accelerator scope command was removed with the shell port (host shell port). */
181
123
  export declare function setDevtoolsShortcutScope(_enabled: boolean): Promise<void>;
182
124
  export declare function revealInFileExplorer(path: string): Promise<void>;
183
125
  /** What the clipboard held when "Open from clipboard" / paste ran. Files win, then a bitmap (written
@@ -1,14 +1,7 @@
1
1
  import { baseName, formatFromPath, normalizeViewMode, } from "../documents/types.js";
2
2
  import { isTauri } from "@rightkit/platform-ui";
3
3
  import { createShell } from "@rightkit/tauri-shell";
4
- // Browser-QA fixture modules load on demand. Every call site sits behind `BROWSER_QA_BUILD` (a
5
- // build-time constant), so production builds fold the guard to `false`, drop these dynamic imports,
6
- // and never emit the fixture chunks. Keep static imports of these modules out of this file.
7
- const loadQaFixtures = () => import("./browserQa.js");
8
- const loadQaText = () => import("./browserQaText.js");
9
- const loadQaDocx = () => import("./browserQaDocx.js");
10
- const loadQaPdf = () => import("./browserQaPdf.js");
11
- const loadQaLicense = () => import("./browserQaLicense.js");
4
+ import { hostHeader, hostScheme } from "../host.js";
12
5
  /** True when running inside the Tauri webview (vs. plain `vite dev` in a browser). */
13
6
  const inTauri = isTauri;
14
7
  let shell = null;
@@ -17,13 +10,6 @@ export function getShell() {
17
10
  shell ??= createShell();
18
11
  return shell;
19
12
  }
20
- /**
21
- * Build-time switch for the browser-QA fixtures: `true` in `vite dev` and in the
22
- * `VITE_RIGHTKIT_BROWSER_QA=1` build, a literal `false` in production builds. Every QA branch in
23
- * this module is guarded with it, so the minifier drops those branches and nothing references the
24
- * `browserQa*` fixture modules in a production bundle.
25
- */
26
- const BROWSER_QA_BUILD = import.meta.env?.DEV || import.meta.env?.VITE_RIGHTKIT_BROWSER_QA === "1";
27
13
  /**
28
14
  * Write text to the system clipboard: through the shell's native clipboard in the app, through the
29
15
  * browser clipboard in browser QA. It rejects when no clipboard is available, so a caller never
@@ -39,15 +25,6 @@ export async function writeClipboardText(text) {
39
25
  }
40
26
  await navigator.clipboard.writeText(text);
41
27
  }
42
- export function isBrowserQaMode() {
43
- const qaBuild = import.meta.env?.VITE_RIGHTKIT_BROWSER_QA === "1";
44
- if (!import.meta.env?.DEV && !qaBuild)
45
- return false;
46
- if (typeof window === "undefined" || inTauri())
47
- return false;
48
- const params = new URLSearchParams(window.location.search);
49
- return params.has("qa") || qaBuild;
50
- }
51
28
  async function invoke(cmd, args) {
52
29
  const { invoke } = await import("@tauri-apps/api/core");
53
30
  return invoke(cmd, args);
@@ -62,16 +39,6 @@ async function listen(event, callback) {
62
39
  * update Bearer; the webview only sends commands and receives validated display views.
63
40
  */
64
41
  export const nativeIpc = { invoke, listen };
65
- /**
66
- * Browser-QA licence client (`@rightkit/license/testing`, in-memory, no network). Returns null
67
- * outside browser QA, so the caller uses the native client. The `testing` export resolves only
68
- * under the `development` condition and the call site folds to `false` in a release build.
69
- */
70
- export async function browserQaLicenseClient() {
71
- if (BROWSER_QA_BUILD && isBrowserQaMode())
72
- return (await loadQaLicense()).qaLicenseClient();
73
- return null;
74
- }
75
42
  /** Classify an IPC rejection from `invoke()` into `{code, message}`. Tauri serializes a command's
76
43
  * `Err(HostError)` using the envelope's own `#[serde(tag = "code", content = "message")]`
77
44
  * representation, so a migrated command's rejection value IS already `{code, message}` — this
@@ -97,9 +64,6 @@ export function parseHostError(e) {
97
64
  return { code: "unknown", message: String(e) };
98
65
  }
99
66
  export async function openFileDialog() {
100
- if (BROWSER_QA_BUILD && isBrowserQaMode()) {
101
- return (await loadQaFixtures()).qaDialogPath();
102
- }
103
67
  if (!inTauri()) {
104
68
  console.warn("openFileDialog: not in Tauri — open a real document via `pnpm tauri dev`.");
105
69
  return null;
@@ -108,27 +72,6 @@ export async function openFileDialog() {
108
72
  }
109
73
  export async function readDocument(path) {
110
74
  const format = formatFromPath(path);
111
- if (BROWSER_QA_BUILD && isBrowserQaMode()) {
112
- const qaDocx = await loadQaDocx();
113
- const qaText = await loadQaText();
114
- const savedDocx = qaDocx.readQaDocxDocument({
115
- path,
116
- displayName: baseName(path),
117
- format,
118
- content: "",
119
- });
120
- if (savedDocx)
121
- return savedDocx;
122
- const doc = await (await loadQaFixtures()).qaDocumentForPath(path);
123
- if (doc) {
124
- if (format === "pdf") {
125
- const assetUrl = await (await loadQaPdf()).qaCurrentPdfAssetUrl(path);
126
- return { ...doc, assetUrl: assetUrl ?? doc.assetUrl };
127
- }
128
- return qaText.readQaTextDocument(doc);
129
- }
130
- return qaText.readQaTextDocument({ path, displayName: baseName(path), format, content: "" });
131
- }
132
75
  if (!inTauri()) {
133
76
  return { path, displayName: baseName(path), format, content: "" };
134
77
  }
@@ -140,7 +83,7 @@ export async function readDocument(path) {
140
83
  const { convertFileSrc } = await import("@tauri-apps/api/core");
141
84
  if (!raw.stream_token)
142
85
  throw new Error("binary stream token missing");
143
- assetUrl = `${convertFileSrc(raw.stream_token, "viewright-stream")}?t=${Date.now()}`;
86
+ assetUrl = `${convertFileSrc(raw.stream_token, hostScheme("stream"))}?t=${Date.now()}`;
144
87
  }
145
88
  let viewMeta;
146
89
  if (raw.view_meta) {
@@ -191,10 +134,6 @@ export async function getPersistenceStatus() {
191
134
  return { degraded: status.degraded === true, quarantinedTo: status.quarantined_to ?? null };
192
135
  }
193
136
  export async function getInitialOpenPaths() {
194
- if (BROWSER_QA_BUILD && isBrowserQaMode()) {
195
- const path = (await loadQaFixtures()).qaInitialOpenPath();
196
- return path ? [path] : [];
197
- }
198
137
  if (!inTauri())
199
138
  return [];
200
139
  // The shell's open-paths gate: it grants and drains what argv / a second launch / macOS
@@ -245,7 +184,7 @@ export async function grantRelativeFile(docPath, rel, rootPath, siteToken) {
245
184
  }
246
185
  }
247
186
  /** Infer and grant this HTML tab's site root (nearest enclosing project directory), returning the
248
- * opaque token that backs `viewright-site://` subresource URLs plus the granted directory for
187
+ * opaque token that backs `<host id>-site://` subresource URLs plus the granted directory for
249
188
  * display. Returns null outside Tauri, so browser/dev/test builds fall back to inlining. */
250
189
  export async function grantSiteRoot(docPath) {
251
190
  if (!inTauri())
@@ -325,8 +264,6 @@ export async function revokePath(path) {
325
264
  /** Write text, preserving the file's `encoding` (default UTF-8). Returns actual encoding plus the
326
265
  * committed source SHA-256; in-place callers send the prior SHA for stale-source protection. */
327
266
  export async function writeDocument(path, content, encoding, expectedSourceSha256) {
328
- if (BROWSER_QA_BUILD && isBrowserQaMode())
329
- return (await loadQaText()).writeQaTextDocument(path, content, encoding, expectedSourceSha256);
330
267
  if (!inTauri())
331
268
  throw new Error("Saving requires the desktop app.");
332
269
  const result = await invoke("write_document", {
@@ -344,10 +281,7 @@ export async function writeDocument(path, content, encoding, expectedSourceSha25
344
281
  // changing writeDocumentBytes's contract (out of scope here) — and a 32k-arg spread/apply call is
345
282
  // exactly the call-stack-pressure risk the loop below was already written to avoid. Not a clear
346
283
  // improvement, so kept unchanged per the audit's own guidance.
347
- function arrayBufferToBase64(ab) {
348
- return byteArrayToBase64(new Uint8Array(ab));
349
- }
350
- function byteArrayToBase64(bytes) {
284
+ export function byteArrayToBase64(bytes) {
351
285
  let bin = "";
352
286
  const chunk = 0x8000;
353
287
  for (let i = 0; i < bytes.length; i += chunk) {
@@ -362,18 +296,13 @@ function byteArrayToBase64(bytes) {
362
296
  return btoa(bin);
363
297
  }
364
298
  export async function writeDocumentBytes(path, data) {
365
- if (BROWSER_QA_BUILD && isBrowserQaMode()) {
366
- if (formatFromPath(path) === "docx")
367
- await (await loadQaDocx()).writeQaDocxDocument(path, data, arrayBufferToBase64(data));
368
- return;
369
- }
370
299
  if (!inTauri())
371
300
  throw new Error("Saving requires the desktop app.");
372
301
  // Raw request body (like ocr_page): the DOCX bytes cross IPC as-is, with no JS base64 encode on
373
302
  // the main thread. The target path rides in a hex header so any filesystem path is header-safe.
374
303
  const { invoke: tauriInvoke } = await import("@tauri-apps/api/core");
375
304
  await tauriInvoke("write_document_base64", new Uint8Array(data), {
376
- headers: { "x-viewright-path-hex": pathToHex(path) },
305
+ headers: { [hostHeader("path-hex")]: pathToHex(path) },
377
306
  });
378
307
  }
379
308
  function pathToHex(path) {
@@ -386,8 +315,6 @@ function pathToHex(path) {
386
315
  * reserializing its bytes. The desktop command owns both broker checks and the filesystem copy so
387
316
  * PDF Save As preserves signatures, encryption, widgets, and any bytes pdf.js cannot round-trip. */
388
317
  export async function copyDocument(sourcePath, targetPath) {
389
- if (BROWSER_QA_BUILD && isBrowserQaMode())
390
- return;
391
318
  if (!inTauri())
392
319
  throw new Error("Saving requires the desktop app.");
393
320
  await invoke("copy_document", { sourcePath, targetPath });
@@ -412,191 +339,6 @@ export async function editImageExternal(path) {
412
339
  return;
413
340
  await invoke("edit_image_external", { path });
414
341
  }
415
- import { hostAppName } from "../appName.js";
416
- if (BROWSER_QA_BUILD && isBrowserQaMode())
417
- void loadQaPdf().then((qaPdf) => qaPdf.registerViewrightQaGlobal());
418
- /** Normalizes an `invoke()` raw-body result (an `ArrayBuffer`, or occasionally already a
419
- * `Uint8Array`/plain byte array depending on Tauri's IPC transport) into a `Uint8Array`. */
420
- function toUint8Array(body) {
421
- if (body instanceof Uint8Array)
422
- return body;
423
- if (body instanceof ArrayBuffer)
424
- return new Uint8Array(body);
425
- if (Array.isArray(body))
426
- return new Uint8Array(body);
427
- throw new Error("Expected a raw byte response from the PDF bridge.");
428
- }
429
- /** Every other bridge function in this file short-circuits outside a real Tauri webview
430
- * (`readDocument`, `fileFingerprint`, `getInitialOpenPaths`, …) rather than letting `invoke()`'s
431
- * dynamic `@tauri-apps/api/core` import reach into `window.__TAURI_INTERNALS__`, which is
432
- * `undefined` there and throws a raw, unclassified "Cannot read properties of undefined (reading
433
- * 'invoke')" instead of a `ClassifiedError` a caller's existing catch/`errorMessage` handling can
434
- * show meaningfully. These six PDF-editor commands have no browser-QA fixture to fall back to
435
- * (unlike `readDocument`'s `qaDocumentForPath`) — a real Rust backend is unavoidable for source
436
- * inspection/composition/save — so the correct graceful behavior is a clean, classified rejection
437
- * rather than either a raw crash or a fabricated result. */
438
- function requireTauriForPdfEdit(command) {
439
- if (!inTauri()) {
440
- throw { code: "unsupported_outside_tauri", message: `${command} requires the native app` };
441
- }
442
- }
443
- /** OCR one rasterized PDF page. The PNG is the raw request body, not JSON metadata. */
444
- export const ocrPage = async (png) => {
445
- // Browser QA has no Rust OCR backend, so the IPC-boundary fixture keeps the scanned-page UI
446
- // exercisable without moving a mock into the document components.
447
- if (BROWSER_QA_BUILD && isBrowserQaMode())
448
- return (await loadQaFixtures()).qaOcrLines();
449
- requireTauriForPdfEdit("ocr_page");
450
- const { invoke: tauriInvoke } = await import("@tauri-apps/api/core");
451
- return tauriInvoke("ocr_page", png);
452
- };
453
- // Cached OCR-capability probe. OCR capability cannot change mid-session, so N concurrent
454
- // PdfViewer mounts share ONE `ocr_available` invoke instead of one IPC each. A failed probe is
455
- // NOT cached (reset below) — the next caller retries instead of pinning a transient IPC error.
456
- let ocrAvailableProbe = null;
457
- /** Probe whether native OCR can be offered. This deliberately returns false outside Tauri instead
458
- * of using requireTauriForPdfEdit: callers use it to decide whether to offer recognition at all.
459
- * Non-throwing contract: a backend/IPC failure resolves false. */
460
- export const ocrAvailable = () => {
461
- // Browser QA has no Rust OCR backend, so the IPC-boundary fixture keeps the scanned-page UI
462
- // exercisable without moving a mock into the document components.
463
- if (BROWSER_QA_BUILD && isBrowserQaMode())
464
- return Promise.resolve(true);
465
- if (!inTauri())
466
- return Promise.resolve(false);
467
- ocrAvailableProbe ??= invoke("ocr_available").catch(() => {
468
- ocrAvailableProbe = null;
469
- return false;
470
- });
471
- return ocrAvailableProbe;
472
- };
473
- /** True only when the backend answered that this machine has no OCR. A timeout, an IPC failure or
474
- * running outside Tauri is not an answer: OCR stays offered and the run itself reports the
475
- * failure. Only a real answer is cached for the session. */
476
- let ocrBackendAnswer = null;
477
- export const ocrBackendMissing = () => {
478
- if (!inTauri())
479
- return Promise.resolve(false);
480
- ocrBackendAnswer ??= invoke("ocr_available").then((available) => available === false, () => {
481
- ocrBackendAnswer = null;
482
- return false;
483
- });
484
- return ocrBackendAnswer;
485
- };
486
- /** §5.2 `inspect_pdf_page`. Walks one page's content stream and returns its authoritative source
487
- * graph (islands, locators, digests). `path` must already be a broker-granted read target. */
488
- export const inspectPdfPage = async (path, page) => {
489
- // Browser QA has no Rust content-stream walker, so the IPC-boundary fixture keeps Edit-text mode
490
- // reachable/non-crashing (islands always empty — source-text editing stays native-only).
491
- if (BROWSER_QA_BUILD && isBrowserQaMode())
492
- return (await loadQaPdf()).qaInspectPdfPage(path, page);
493
- requireTauriForPdfEdit("inspect_pdf_page");
494
- return invoke("inspect_pdf_page", { path, page });
495
- };
496
- /** §7.2 `list_pdf_fonts`. Returns the bundled + system font catalog available for embedding. */
497
- export const listPdfFonts = async () => {
498
- if (BROWSER_QA_BUILD && isBrowserQaMode())
499
- return (await loadQaPdf()).qaListPdfFonts();
500
- requireTauriForPdfEdit("list_pdf_fonts");
501
- return invoke("list_pdf_fonts");
502
- };
503
- /** §9.4 `compose_pdf_preview`. Memory-only recomposition of staged source commands against fresh
504
- * disk bytes, for committed-unsaved-edit preview re-render — no token, no retained candidate, no
505
- * writes. Candidate bytes use JSON; affected pages derive from request commands. */
506
- export const composePdfPreview = async (request) => {
507
- if (BROWSER_QA_BUILD && isBrowserQaMode())
508
- return (await loadQaPdf()).qaComposePdfPreview(request);
509
- requireTauriForPdfEdit("compose_pdf_preview");
510
- const body = await invoke("compose_pdf_preview", {
511
- request: {
512
- sourcePath: request.sourcePath,
513
- expectedSourceSha256: request.expectedSourceSha256,
514
- sourceCommands: request.sourceCommands,
515
- },
516
- });
517
- const pages = new Set();
518
- for (const command of request.sourceCommands) {
519
- pages.add(command.kind === "replaceSourceText" ? command.locator.page : command.page);
520
- }
521
- return {
522
- candidateBytes: toUint8Array(body),
523
- affectedPages: Array.from(pages).sort((a, b) => a - b),
524
- };
525
- };
526
- /** §10.1 `prepare_pdf_save`. The frozen frontend `PreparePdfSave` type (`pdfEditModel.ts`) expects
527
- * one call returning `PreparedPdfSave` (metadata + `candidateBytes` together), but the actual Rust
528
- * implementation returns candidate bytes as a numeric JSON array because WKWebView cannot
529
- * reliably consume this staged candidate through Tauri's raw-response or large-base64 paths.
530
- *
531
- * Judgment call: this bridge function internally calls both backend commands in sequence and
532
- * combines their results into the single `PreparedPdfSave` shape B7's `pdfSave.ts` was written
533
- * against, so the frozen frontend type/signature is satisfied without B7 needing to know about the
534
- * two-call split. `expectedText` field names (`page`/`text`) come from the backend's
535
- * `ExpectedTextCheck { page, text }`; mapped here to `pdfEditModel.ts`'s `PdfExpectedTextCheck`
536
- * shape (`page`/`islandId`/`expectedText`) — no `islandId` exists in the backend response, so it is
537
- * populated as an empty string (the backend recheck matches by page + text substring only, not by
538
- * island id; see `pdf_save.rs`'s `structurally_verify_candidate`). */
539
- export const preparePdfSave = async (request) => {
540
- if (BROWSER_QA_BUILD && isBrowserQaMode())
541
- return (await loadQaPdf()).qaPreparePdfSave(request);
542
- requireTauriForPdfEdit("prepare_pdf_save");
543
- const meta = await invoke("prepare_pdf_save", {
544
- request: {
545
- sourcePath: request.sourcePath,
546
- expectedSourceSha256: request.expectedSourceSha256,
547
- annotationPdfBase64: byteArrayToBase64(request.annotationPdfBytes),
548
- sourceCommands: request.sourceCommands,
549
- },
550
- });
551
- return {
552
- token: meta.token,
553
- candidateSha256: meta.candidateSha256,
554
- candidateBytes: Uint8Array.from(meta.candidateBytes),
555
- affectedPages: meta.affectedPages,
556
- expectedText: meta.expectedText.map((check) => ({
557
- page: check.page,
558
- islandId: "",
559
- expectedText: check.text,
560
- })),
561
- };
562
- };
563
- /** §10.3 `commit_pdf_save`. Consumes the single-use save token and atomically writes the staged
564
- * candidate to `targetPath` (or back to the original source path for a same-path Save). The
565
- * backend's `PdfCommitResult` (`pdf_save.rs`) carries only `{ targetPath, documentSha256 }` — it
566
- * has no `revision`/`affectedPages` fields, unlike the richer shape `pdfEditModel.ts` sketches as
567
- * "inferred" (that file's own comment flags this exact field set as not given anywhere in the
568
- * contract text). Rather than fabricate an `affectedPages`/`revision` this command never returns,
569
- * this bridge function follows the caller-refresh pattern already used elsewhere in this file
570
- * (e.g. `readDocument`'s post-write re-read): the caller (`pdfSave.ts`) is expected to re-run
571
- * `inspectPdfPage` per page after a successful commit to obtain a fresh revision, rather than this
572
- * bridge inventing data. `committedPath`/`committedSha256` map 1:1 from the backend's
573
- * `targetPath`/`documentSha256`; `affectedPages` is returned empty and `revision` as an
574
- * empty-pages placeholder so the return type still satisfies `PdfCommitResult` structurally. */
575
- export const commitPdfSave = async (token, targetPath) => {
576
- if (BROWSER_QA_BUILD && isBrowserQaMode())
577
- return (await loadQaPdf()).qaCommitPdfSave(token, targetPath);
578
- requireTauriForPdfEdit("commit_pdf_save");
579
- const result = await invoke("commit_pdf_save", {
580
- token,
581
- targetPath: targetPath ?? null,
582
- });
583
- return {
584
- committedPath: result.targetPath,
585
- committedSha256: result.documentSha256,
586
- affectedPages: [],
587
- revision: { documentSha256: result.documentSha256, pageCount: 0, pages: [] },
588
- };
589
- };
590
- /** Browser-QA-only: current bytes of the in-memory QA PDF "disk" at `path`, or `null` outside QA
591
- * mode / for an untouched path. PdfViewer.tsx's post-commit reload uses this instead of
592
- * re-fetching its `url` prop — a real Tauri stream token is a revocable token that re-resolves to
593
- * the same (now-committed) file on every fetch, but browser QA's `url` is a static `data:` URL
594
- * captured once at document-open time, which can never reflect a later commit on its own. */
595
- export async function qaReloadPdfBytes(path) {
596
- if (!BROWSER_QA_BUILD || !isBrowserQaMode())
597
- return null;
598
- return (await loadQaPdf()).qaCurrentPdfBytes(path);
599
- }
600
342
  /** Token of the interactive HTML preview that currently owns DevTools (the active tab's frame), or
601
343
  * null. Set by the HTML preview frame; DevTools opens exactly that registered document. */
602
344
  let inspectableHtmlToken = null;
@@ -613,14 +355,14 @@ export function releaseInspectableHtmlPreview(token) {
613
355
  * main webview is never inspectable. No-op without an active HTML preview or outside Tauri. */
614
356
  export async function openDevtools() {
615
357
  const token = inspectableHtmlToken;
616
- if (!token || (BROWSER_QA_BUILD && isBrowserQaMode()) || !inTauri())
358
+ if (!token || !inTauri())
617
359
  return;
618
360
  await invoke("open_html_devtools", { token });
619
361
  }
620
- /** No-op: the macOS inspect-accelerator scope command was removed with the shell port (viewright#375). */
362
+ /** No-op: the macOS inspect-accelerator scope command was removed with the shell port (host shell port). */
621
363
  export async function setDevtoolsShortcutScope(_enabled) { }
622
364
  export async function revealInFileExplorer(path) {
623
- if ((BROWSER_QA_BUILD && isBrowserQaMode()) || !inTauri())
365
+ if (!inTauri())
624
366
  return;
625
367
  await invoke("reveal_in_file_explorer", { path });
626
368
  }
@@ -651,24 +393,10 @@ export async function printCurrentWebview() {
651
393
  }
652
394
  }
653
395
  export async function listSystemFonts() {
654
- if (BROWSER_QA_BUILD && isBrowserQaMode())
655
- return ["Inter", "Source Serif 4", "JetBrains Mono", "Geist", "Newsreader"];
656
396
  if (!inTauri())
657
397
  return [];
658
398
  return invoke("list_system_fonts");
659
399
  }
660
- const QA_FILE_ASSOCIATION_FORMATS = ["md", "mdx", "txt", "pdf", "html", "docx", "image"];
661
- let qaPreferredAssociationFormats = new Set(["md", "mdx"]);
662
- function qaFileAssociationStatus() {
663
- return {
664
- formats: QA_FILE_ASSOCIATION_FORMATS.map((formatId) => ({
665
- formatId,
666
- extensions: [],
667
- handlers: [],
668
- allSet: qaPreferredAssociationFormats.has(formatId),
669
- })),
670
- };
671
- }
672
400
  function toFormatStatus(group) {
673
401
  return {
674
402
  formatId: group.id,
@@ -682,17 +410,10 @@ function toFormatStatus(group) {
682
410
  };
683
411
  }
684
412
  export async function fileAssociationStatus() {
685
- if (BROWSER_QA_BUILD && isBrowserQaMode())
686
- return qaFileAssociationStatus();
687
413
  const status = await getShell().associations.status();
688
414
  return { formats: status.groups.map(toFormatStatus) };
689
415
  }
690
416
  export async function setFileAssociationDefaults(formatIds) {
691
- if (BROWSER_QA_BUILD && isBrowserQaMode()) {
692
- qaPreferredAssociationFormats = new Set(formatIds);
693
- const status = qaFileAssociationStatus();
694
- return { formats: status.formats, allSet: formatIds.length > 0 };
695
- }
696
417
  // macOS: one NSWorkspace request per content type (system consent sheet), one shared 60 s
697
418
  // deadline (the shell default; the host clamps a given value to 1 to 120 s).
698
419
  const result = await getShell().associations.set(formatIds);
@@ -701,8 +422,6 @@ export async function setFileAssociationDefaults(formatIds) {
701
422
  /** Windows owns protected default-app choices. This opens Windows Default apps; the shell never
702
423
  * writes UserChoice registry hashes or claims a default changed. */
703
424
  export async function openFileAssociationSettings(_formatIds) {
704
- if (BROWSER_QA_BUILD && isBrowserQaMode())
705
- return;
706
425
  await getShell().associations.openDefaultApps();
707
426
  }
708
427
  export async function saveSession(tabs) {
@@ -818,15 +537,11 @@ export function installExternalLinkGuard() {
818
537
  });
819
538
  }
820
539
  export async function saveFileDialog(defaultName, format) {
821
- if (BROWSER_QA_BUILD && isBrowserQaMode())
822
- return `D:\\${hostAppName()} QA\\${defaultName ?? `untitled.${format ?? "txt"}`}`;
823
540
  if (!inTauri())
824
541
  return null;
825
542
  return invoke("save_file_dialog", { defaultName, format });
826
543
  }
827
544
  export async function grantKnownFileToken(token) {
828
- if (BROWSER_QA_BUILD && isBrowserQaMode())
829
- return token;
830
545
  if (!inTauri())
831
546
  return null;
832
547
  return invoke("grant_known_file_token", { token });
@@ -840,15 +555,11 @@ export async function grantKnownFileToken(token) {
840
555
  // - regrantSavedTarget: re-grant a path the host app itself just wrote (save-as, duplicate, DOCX
841
556
  // save-as) — refused unless the exact path was written in the last few writes.
842
557
  export async function openLinkedDocument(target, from) {
843
- if (BROWSER_QA_BUILD && isBrowserQaMode())
844
- return target;
845
558
  if (!inTauri())
846
559
  return null;
847
560
  return invoke("open_linked_document", { target, from: from ?? null });
848
561
  }
849
562
  export async function regrantSavedTarget(path) {
850
- if (BROWSER_QA_BUILD && isBrowserQaMode())
851
- return path;
852
563
  if (!inTauri())
853
564
  return null;
854
565
  return invoke("regrant_saved_target", { path });
package/dist/styles.css CHANGED
@@ -1,2 +1,2 @@
1
- /* Every stylesheet this package's components use. Hosts import "<package>/styles.css" once (Node cannot import CSS from JS). */
2
- @import "pdfjs-dist/web/pdf_viewer.css";
1
+ /* The editor base ships no stylesheet of its own. The PDF viewer styles (pdf.js `pdf_viewer.css`) moved
2
+ with the PDF editor to @rightkit/document-workspace in 0.2.0; this file stays so `<package>/styles.css` keeps resolving. */