@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,302 +0,0 @@
1
- /**
2
- * Deterministic PDF-edit-command mocks for browser QA mode (`VITE_RIGHTKIT_BROWSER_QA=1`,
3
- * `?qa=1&doc=pdf`). Never active in production builds — every export here is only ever reached
4
- * through `tauriBridge.ts`'s `isBrowserQaMode()` gate, exactly like `qaOcrLines` already is for
5
- * OCR (see `browserQa.ts`).
6
- *
7
- * Before this module, `requireTauriForPdfEdit` threw `unsupported_outside_tauri` for every
8
- * PDF-editor command (`inspect_pdf_page`, `compose_pdf_preview`, `prepare_pdf_save`,
9
- * `commit_pdf_save`, `list_pdf_fonts`), so Save and Edit-text had zero coverage in the automated
10
- * `qa:functional:browser` harness — every PDF save regression had to be found by hand in the
11
- * native app. This module gives the harness a real, in-memory PDF "disk" and mirrors the Rust
12
- * save-protocol rules (`src-tauri/crates/pdf/src/save.rs`'s `select_compose_base`, the stale-sha
13
- * check, single-use tokens) closely enough that ANNOTATION editing + save (stamp/ink/highlight, via
14
- * pdf.js's own `AnnotationEditorLayer`/`saveDocument()`) is now exercised end to end in the
15
- * headless browser harness. Source-TEXT editing (`inspect_pdf_page`'s `islands`) stays native-only:
16
- * browser QA has no Rust content-stream walker, so `qaInspectPdfPage` always reports zero editable
17
- * islands — good enough to keep the Edit-text UI reachable/non-crashing, not to exercise a real
18
- * text-replace round trip.
19
- */
20
- import { qaDocumentForPath } from "./browserQa.js";
21
- // -------------------------------------------------------------------------------------------
22
- // byte/base64/sha helpers
23
- // -------------------------------------------------------------------------------------------
24
- /** One JS UTF-16 code unit per byte value (0-255) — the "binary string" `btoa` expects, and
25
- * exactly the "latin1 string of bytes" `window.__viewrightQa.pdfDiskLatin1` exposes so QA
26
- * assertions can `.includes("/Subtype /Stamp")` etc. against raw (uncompressed) PDF syntax. */
27
- function bytesToBinaryString(bytes) {
28
- let out = "";
29
- const chunk = 0x8000;
30
- for (let i = 0; i < bytes.length; i += chunk) {
31
- const slice = bytes.subarray(i, i + chunk);
32
- for (let j = 0; j < slice.length; j += 1)
33
- out += String.fromCharCode(slice[j]);
34
- }
35
- return out;
36
- }
37
- function bytesToBase64(bytes) {
38
- return btoa(bytesToBinaryString(bytes));
39
- }
40
- function base64ToBytes(base64) {
41
- const binary = atob(base64);
42
- const bytes = new Uint8Array(binary.length);
43
- for (let i = 0; i < binary.length; i += 1)
44
- bytes[i] = binary.charCodeAt(i);
45
- return bytes;
46
- }
47
- /** `qaDocumentForPath`'s `assetUrl` is always a `data:...;base64,<payload>` URL (the static QA
48
- * fixture constant, or a corpus file re-encoded by `bytesToBase64PdfUrl`) — decode straight to
49
- * bytes rather than going through a real `fetch(dataUrl)` round trip. */
50
- function dataUrlToBytes(dataUrl) {
51
- const comma = dataUrl.indexOf(",");
52
- if (comma === -1)
53
- throw new Error("qa pdf fixture: not a data: URL");
54
- return base64ToBytes(dataUrl.slice(comma + 1));
55
- }
56
- /** Matches `document_sha256` (`src-tauri/crates/pdf/src/model.rs`): plain SHA-256 of the whole
57
- * byte sequence, lowercase hex. */
58
- export async function sha256Hex(bytes) {
59
- // `crypto.subtle.digest` hashes exactly a `BufferSource` view's byteOffset..byteLength range
60
- // (not its whole backing buffer), and never transfers/detaches it — safe to pass a live reference
61
- // into `qaDisk`. The `new Uint8Array(bytes)` copy is only here to satisfy `BufferSource`'s
62
- // plain-`ArrayBuffer` (vs. `ArrayBufferLike`/`SharedArrayBuffer`) typing, not for safety.
63
- const digest = await crypto.subtle.digest("SHA-256", new Uint8Array(bytes));
64
- return Array.from(new Uint8Array(digest))
65
- .map((b) => b.toString(16).padStart(2, "0"))
66
- .join("");
67
- }
68
- function startsWithBytes(haystack, prefix) {
69
- if (haystack.length < prefix.length)
70
- return false;
71
- for (let i = 0; i < prefix.length; i += 1) {
72
- if (haystack[i] !== prefix[i])
73
- return false;
74
- }
75
- return true;
76
- }
77
- function affectedPagesFor(commands) {
78
- const pages = new Set();
79
- for (const command of commands) {
80
- pages.add(command.kind === "replaceSourceText" ? command.locator.page : command.page);
81
- }
82
- return Array.from(pages).sort((a, b) => a - b);
83
- }
84
- // -------------------------------------------------------------------------------------------
85
- // In-memory QA "disk"
86
- // -------------------------------------------------------------------------------------------
87
- const qaDisk = new Map();
88
- /** Lazily seeds `qaDisk` for `path` from `qaDocumentForPath`'s fixture/corpus data URL on first
89
- * touch, then always returns the (possibly since-committed) current bytes from the map. */
90
- async function currentDiskBytes(path) {
91
- const existing = qaDisk.get(path);
92
- if (existing)
93
- return existing;
94
- const doc = await qaDocumentForPath(path);
95
- if (!doc?.assetUrl)
96
- return null;
97
- const bytes = dataUrlToBytes(doc.assetUrl);
98
- qaDisk.set(path, bytes);
99
- return bytes;
100
- }
101
- /** Exported for PdfViewer.tsx's post-commit reload only (see its own call site's comment): the
102
- * `url` prop it otherwise reloads from is a static `data:` URL captured once at document-open
103
- * time — unlike a real Tauri stream token, it can never reflect a later commit on its own, so the
104
- * reload needs to ask this in-memory disk for the CURRENT bytes directly. Returns `null` outside
105
- * QA mode or for a path never touched through the PDF-edit bridge. */
106
- export async function qaCurrentPdfBytes(path) {
107
- return currentDiskBytes(path);
108
- }
109
- export async function qaCurrentPdfAssetUrl(path) {
110
- const bytes = await qaCurrentPdfBytes(path);
111
- return bytes ? `data:application/pdf;base64,${bytesToBase64(bytes)}` : null;
112
- }
113
- /** Test-only: clears the in-memory QA disk and save-token registry so each test starts from a
114
- * fresh fixture seed, independent of what earlier tests in the same file committed. Mirrors
115
- * `resourceCensus.ts`'s `resetResourceCensusForTests` convention. */
116
- export function resetQaPdfStateForTests() {
117
- qaDisk.clear();
118
- qaSaveTokens.clear();
119
- }
120
- // -------------------------------------------------------------------------------------------
121
- // inspect_pdf_page / list_pdf_fonts
122
- // -------------------------------------------------------------------------------------------
123
- export async function qaInspectPdfPage(path, page) {
124
- const bytes = await currentDiskBytes(path);
125
- if (!bytes)
126
- throw { code: "malformed_content", message: `qa disk has no PDF at ${path}` };
127
- const documentSha256 = await sha256Hex(bytes);
128
- return {
129
- documentSha256,
130
- page,
131
- // Matches the QA fixture's own `/MediaBox [0 0 612 792]` (both pages, US Letter) — a corpus
132
- // PDF's real box would differ, but source-text editing (the only consumer of these bounds)
133
- // never runs against a corpus fixture in the automated harness.
134
- mediaBox: [0, 0, 612, 792],
135
- cropBox: [0, 0, 612, 792],
136
- rotation: 0,
137
- islands: page === 1 ? [qaFixtureIsland(documentSha256)] : [],
138
- };
139
- }
140
- /**
141
- * One hand-built editable island for the QA fixture's page 1 title run `BT /F1 24 Tf 72 700 Td
142
- * (Page 1: external link below) Tj ET` (Helvetica 24pt, baseline at y=700). The title run is
143
- * deliberately chosen over the "Visit example.com" / "Go to page 2" runs below it because those
144
- * two carry Link annotations whose `<a>` overlays would intercept the click before it reaches the
145
- * page hit-test; the title has no annotation over it. Geometry uses Helvetica's nominal metrics
146
- * (ascender 0.718em, descender 0.207em, ~0.5em average advance) so the inline editor's on-screen
147
- * box lines up with the painted glyphs closely enough to dogfood click-to-edit UX (font face,
148
- * size at zoom, glyph masking). The locator carries no spans: browser QA has no content-stream
149
- * walker or composer, so a committed edit is staged but never composed into the bytes — the round
150
- * trip that IS covered natively.
151
- */
152
- function qaFixtureIsland(documentSha256) {
153
- const x0 = 72;
154
- const baseline = 700;
155
- const sizePt = 24;
156
- const text = "Page 1: external link below";
157
- const width = text.length * sizePt * 0.5;
158
- const top = baseline + sizePt * 0.718;
159
- const bottom = baseline - sizePt * 0.207;
160
- return {
161
- id: "qa-island-visit-example",
162
- text,
163
- quads: [[x0, bottom, x0 + width, bottom, x0 + width, top, x0, top]],
164
- baseline: [x0, baseline, x0 + width, baseline],
165
- appearance: {
166
- fontId: "base14:helvetica",
167
- family: "Helvetica",
168
- sizePt,
169
- color: { r: 0, g: 0, b: 0, alpha: 1 },
170
- },
171
- locator: { documentSha256, page: 1, spans: [], islandDigest: "qa-fixture" },
172
- editable: true,
173
- rejection: null,
174
- };
175
- }
176
- export async function qaListPdfFonts() {
177
- return [];
178
- }
179
- // -------------------------------------------------------------------------------------------
180
- // compose_pdf_preview (§9.4 memory-only preview)
181
- // -------------------------------------------------------------------------------------------
182
- export async function qaComposePdfPreview(request) {
183
- const bytes = await currentDiskBytes(request.sourcePath);
184
- if (!bytes) {
185
- throw { code: "malformed_content", message: `qa disk has no PDF at ${request.sourcePath}` };
186
- }
187
- const actualSha = await sha256Hex(bytes);
188
- if (request.expectedSourceSha256 !== actualSha) {
189
- throw { code: "stale_source", message: "PDF changed since edit started" };
190
- }
191
- // Source-text commands are never applied here (native-only, per module doc) — an empty command
192
- // list is exactly what the real composer also does with no-op input (see `compose_candidate`'s
193
- // own `if commands.is_empty() { return source.to_vec() }` fast path), so this only diverges from
194
- // real behavior if a caller stages a non-empty `sourceCommands` list, which QA scenarios don't.
195
- return { candidateBytes: bytes, affectedPages: affectedPagesFor(request.sourceCommands) };
196
- }
197
- // -------------------------------------------------------------------------------------------
198
- // prepare_pdf_save / commit_pdf_save (§10.1/§10.3 two-phase save)
199
- // -------------------------------------------------------------------------------------------
200
- /** Mirrors `src-tauri/crates/pdf/src/save.rs`'s `select_compose_base` exactly: a `<=1`-byte
201
- * annotation payload is the frontend's "no annotation edits" sentinel (base = source); a real
202
- * pdf.js incremental update is the original bytes verbatim plus appended annotation objects/xref
203
- * (base = those bytes); anything else is rejected rather than silently guessed at. */
204
- function selectComposeBase(source, annotationPdf) {
205
- if (annotationPdf.length <= 1)
206
- return source;
207
- if (annotationPdf.length > source.length && startsWithBytes(annotationPdf, source)) {
208
- return annotationPdf;
209
- }
210
- throw {
211
- code: "annotation_serialization_failed",
212
- message: "annotations could not be serialized",
213
- };
214
- }
215
- const qaSaveTokens = new Map();
216
- export async function qaPreparePdfSave(request) {
217
- // Matches `pdf_adapter.rs`'s own explicit empty check, which runs before `select_compose_base`
218
- // even sees the bytes (its own `<=1` branch would otherwise also accept a genuinely-empty
219
- // payload as "no annotation edits", which is not what an empty/failed serialization means).
220
- if (request.annotationPdfBytes.length === 0) {
221
- throw {
222
- code: "annotation_serialization_failed",
223
- message: "annotations could not be serialized",
224
- };
225
- }
226
- const source = await currentDiskBytes(request.sourcePath);
227
- if (!source) {
228
- throw { code: "malformed_content", message: `qa disk has no PDF at ${request.sourcePath}` };
229
- }
230
- const actualSha = await sha256Hex(source);
231
- if (request.expectedSourceSha256 !== actualSha) {
232
- throw { code: "stale_source", message: "PDF changed since edit started" };
233
- }
234
- const candidateBytes = selectComposeBase(source, request.annotationPdfBytes);
235
- const candidateSha256 = await sha256Hex(candidateBytes);
236
- const token = crypto.randomUUID();
237
- qaSaveTokens.set(token, {
238
- sourcePath: request.sourcePath,
239
- sourceSha256: actualSha,
240
- candidateBytes,
241
- });
242
- return {
243
- token,
244
- candidateSha256,
245
- candidateBytes,
246
- affectedPages: affectedPagesFor(request.sourceCommands),
247
- expectedText: [],
248
- };
249
- }
250
- export async function qaCommitPdfSave(token, targetPath) {
251
- // Single-use regardless of outcome — mirrors `PdfSaveTokenStore::consume`'s "removes the token
252
- // record on the very first commit_pdf_save call for it, success or failure."
253
- const record = qaSaveTokens.get(token);
254
- qaSaveTokens.delete(token);
255
- if (!record) {
256
- throw { code: "write_conflict", message: "save token invalid or already used" };
257
- }
258
- const target = targetPath ?? record.sourcePath;
259
- if (target === record.sourcePath) {
260
- // Same-path Save rechecks source sha immediately before the "atomic" write, same as
261
- // `resolve_commit`'s same-path recheck.
262
- const current = await currentDiskBytes(record.sourcePath);
263
- const currentSha = current ? await sha256Hex(current) : "";
264
- if (currentSha !== record.sourceSha256) {
265
- throw { code: "write_conflict", message: "target changed before save" };
266
- }
267
- }
268
- qaDisk.set(target, record.candidateBytes);
269
- const committedSha256 = await sha256Hex(record.candidateBytes);
270
- return {
271
- committedPath: target,
272
- committedSha256,
273
- affectedPages: [],
274
- revision: { documentSha256: committedSha256, pageCount: 0, pages: [] },
275
- };
276
- }
277
- let registered = false;
278
- /** Registers `window.__viewrightQa` once. Callers (`tauriBridge.ts`) must gate this behind their
279
- * own `isBrowserQaMode()` check — kept unconditional here (idempotent via `registered`) rather
280
- * than duplicating that check locally, since importing `isBrowserQaMode` from `tauriBridge.ts`
281
- * would cycle back into this module's own `./browserQa` import. */
282
- export function registerViewrightQaGlobal() {
283
- if (registered || typeof window === "undefined")
284
- return;
285
- registered = true;
286
- window.__viewrightQa = {
287
- pdfDiskBase64(path) {
288
- const bytes = qaDisk.get(path);
289
- return bytes ? bytesToBase64(bytes) : null;
290
- },
291
- pdfDiskLatin1(path) {
292
- const bytes = qaDisk.get(path);
293
- return bytes ? bytesToBinaryString(bytes) : null;
294
- },
295
- async originalPdfDiskLatin1(path) {
296
- const doc = await qaDocumentForPath(path);
297
- if (!doc?.assetUrl)
298
- return null;
299
- return bytesToBinaryString(dataUrlToBytes(doc.assetUrl));
300
- },
301
- };
302
- }
@@ -1,6 +0,0 @@
1
- import type { LoadedDocument } from "../documents/types.js";
2
- export declare function readQaTextDocument(doc: LoadedDocument): Promise<LoadedDocument>;
3
- export declare function writeQaTextDocument(path: string, content: string, encoding?: string, expected?: string): Promise<{
4
- encoding: string;
5
- sourceSha256: string;
6
- }>;
@@ -1,48 +0,0 @@
1
- // IPC-boundary disk for browser journeys. Session storage belongs to disposable QA
2
- // browser profile; survives reload, never reads/writes native app data or user files.
3
- const PREFIX = "viewright.qa.text-disk:";
4
- function diskKey(path) {
5
- return (PREFIX +
6
- (new URLSearchParams(location.search).get("qaQuality") === "1" ? "quality:" : "default:") +
7
- path);
8
- }
9
- function read(path) {
10
- const raw = sessionStorage.getItem(diskKey(path));
11
- return raw ? JSON.parse(raw) : null;
12
- }
13
- async function digest(content) {
14
- const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(content));
15
- return Array.from(new Uint8Array(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
16
- }
17
- function publishReadback() {
18
- window.__viewrightQaText = { read };
19
- }
20
- export async function readQaTextDocument(doc) {
21
- if (!["txt", "md", "mdx", "html"].includes(doc.format))
22
- return doc;
23
- const content = doc.format === "md" && new URLSearchParams(location.search).get("qaQuality") === "1"
24
- ? doc.content + "\n\n protected code\n second line\n"
25
- : doc.content;
26
- const entry = read(doc.path) ?? {
27
- content,
28
- encoding: doc.encoding ?? "UTF-8",
29
- sourceSha256: await digest(content),
30
- };
31
- sessionStorage.setItem(diskKey(doc.path), JSON.stringify(entry));
32
- publishReadback();
33
- return {
34
- ...doc,
35
- ...entry,
36
- fingerprint: { mtimeMs: 1, size: new TextEncoder().encode(entry.content).length },
37
- };
38
- }
39
- export async function writeQaTextDocument(path, content, encoding = "UTF-8", expected) {
40
- const previous = read(path);
41
- if (expected && previous && expected !== previous.sourceSha256) {
42
- throw new Error("QA disk changed before save");
43
- }
44
- const entry = { content, encoding, sourceSha256: await digest(content) };
45
- sessionStorage.setItem(diskKey(path), JSON.stringify(entry));
46
- publishReadback();
47
- return { encoding, sourceSha256: entry.sourceSha256 };
48
- }