@rightkit/document-core 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2 (unpublished)
4
+
5
+ - Removed (breaking, Adrian, 2026-11-03: "no dummy data, no fucking made up shit. real data."): the browser-QA fixture layer. `BROWSER_QA_BUILD`, `isBrowserQaMode()`, `qaReloadPdfBytes()`, every fixture branch of `state/tauriBridge` and the `state/browserQa`, `browserQaText`, `browserQaDocx` and `browserQaPdf` modules (and their `index` exports) are gone. Outside the Tauri webview the bridge keeps its plain no-op returns.
6
+ - Removed (breaking, Adrian, 2026-11-03: no dummy data): the in-memory browser-QA licence client. `browserQaLicenseClient()` is gone from `state/tauriBridge`, `state/browserQaLicense` is deleted, and the `@rightkit/license` dependency with it (`@rightkit/license/testing` no longer exists).
7
+ - `@rightkit/license` is a dependency again: the settings sections `document-core` exports come from `@rightkit/settings-ui`, whose `sections` entry imports its peer `@rightkit/license`; without it an installed `document-core` failed to import.
8
+
3
9
  ## 0.1.1
4
10
 
5
11
  - Ask 1: the host bridge (`state/tauriBridge`), diagnostics (`state/diagnostics`) and platform modules are the single copy the document editors import. Hosts delete their own copies. Host-facing changes a host must adopt:
@@ -4,7 +4,7 @@ import { pdfDocumentInit, pdfWasmUrl } from "@rightkit/pdf-ui";
4
4
  import { prepareAndVerifyPdfSave } from "./pdfSave.js";
5
5
  // New PDF-edit backend commands (docs/PDF-EDITOR-NOW.md §10.1-§10.3), imported by name from
6
6
  // tauriBridge.ts against the frozen type contract in pdfEditModel.ts.
7
- import { preparePdfSave, commitPdfSave, qaReloadPdfBytes } from "../../state/tauriBridge.js";
7
+ import { preparePdfSave, commitPdfSave } from "../../state/tauriBridge.js";
8
8
  import { logError, logEvent } from "../../state/diagnostics.js";
9
9
  /**
10
10
  * Forces a fresh native stream fetch after a successful same-path commit. Tauri's stream URL can
@@ -184,24 +184,11 @@ export function usePdfSavePlan({ documentPath, url, editSession, pdfEditor, hand
184
184
  // disposing the stale task until React has committed this fresh `pdfDoc`, so pages
185
185
  // still rendering off the old document mid-swap can't crash against a destroyed worker.
186
186
  //
187
- // Browser QA has no such revocable token: `url` there is a static `data:` URL captured
188
- // once at document-open time, so re-fetching it would always reload the ORIGINAL
189
- // pre-edit bytes — silently discarding every annotation just committed, and (worse)
190
- // breaking a second save's incremental-update prefix check against the QA disk's
191
- // now-updated bytes. `qaReloadPdfBytes` resolves to `null` outside QA mode, so this is a
192
187
  // Native reloads bind URL identity to committed revision below so WKWebView cannot
193
188
  // hand the next edit session cached pre-commit bytes.
194
- const qaBytes = await qaReloadPdfBytes(documentPath);
195
- // `.slice()`: pdf.js's worker transport can transfer (detach) the backing ArrayBuffer
196
- // of `data` — `qaBytes` is a live reference into the QA disk's stored bytes (never
197
- // copied on read, so later saves keep seeing the same array), so handing it over
198
- // directly would corrupt that entry the moment this load transfers it. Same convention
199
- // `handleRecomposePreview` above already uses for its own `data:` load.
200
- const freshTask = pdfjs.getDocument(qaBytes
201
- ? pdfDocumentInit(qaBytes.slice(), { wasmUrl: pdfWasmUrl() })
202
- : pdfDocumentInit(withPdfRevisionCacheBust(url, result.revision.documentSha256), {
203
- wasmUrl: pdfWasmUrl(),
204
- }));
189
+ const freshTask = pdfjs.getDocument(pdfDocumentInit(withPdfRevisionCacheBust(url, result.revision.documentSha256), {
190
+ wasmUrl: pdfWasmUrl(),
191
+ }));
205
192
  const fresh = await freshTask.promise;
206
193
  // A user can mutate annotation storage while fresh bytes are loading. Check again
207
194
  // immediately before swapping pdfDoc so reload cannot erase that newer state.
@@ -268,12 +255,9 @@ export function usePdfSavePlan({ documentPath, url, editSession, pdfEditor, hand
268
255
  return false;
269
256
  setSaveLocked?.(true);
270
257
  try {
271
- const qaBytes = await qaReloadPdfBytes(documentPath);
272
- const freshTask = pdfjs.getDocument(qaBytes
273
- ? pdfDocumentInit(qaBytes.slice(), { wasmUrl: pdfWasmUrl() })
274
- : pdfDocumentInit(withPdfRevisionCacheBust(url, sourceShaRef.current ?? "retry"), {
275
- wasmUrl: pdfWasmUrl(),
276
- }));
258
+ const freshTask = pdfjs.getDocument(pdfDocumentInit(withPdfRevisionCacheBust(url, sourceShaRef.current ?? "retry"), {
259
+ wasmUrl: pdfWasmUrl(),
260
+ }));
277
261
  const fresh = await freshTask.promise;
278
262
  replaceDocument(fresh, freshTask);
279
263
  reloadRequiredRef.current = false;
package/dist/index.d.ts CHANGED
@@ -78,10 +78,6 @@ export * from "./settings/settings.js";
78
78
  export * from "./settings/settingsBackend.js";
79
79
  export * from "./settings/themes.js";
80
80
  export * from "./state/bridgeEvents.js";
81
- export * from "./state/browserQa.js";
82
- export * from "./state/browserQaDocx.js";
83
- export * from "./state/browserQaPdf.js";
84
- export * from "./state/browserQaText.js";
85
81
  export * from "./state/diagnostics.js";
86
82
  export * from "./state/errors.js";
87
83
  export * from "./state/motion.js";
package/dist/index.js CHANGED
@@ -80,10 +80,6 @@ export * from "./settings/settings.js";
80
80
  export * from "./settings/settingsBackend.js";
81
81
  export * from "./settings/themes.js";
82
82
  export * from "./state/bridgeEvents.js";
83
- export * from "./state/browserQa.js";
84
- export * from "./state/browserQaDocx.js";
85
- export * from "./state/browserQaPdf.js";
86
- export * from "./state/browserQaText.js";
87
83
  export * from "./state/diagnostics.js";
88
84
  export * from "./state/errors.js";
89
85
  export * from "./state/motion.js";
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useState } from "react";
3
3
  import { documentFormatEntries } from "../../documents/documentFormats.js";
4
- import { fileAssociationStatus, inTauri, isBrowserQaMode, openFileAssociationSettings, setFileAssociationDefaults, } from "../../state/tauriBridge.js";
4
+ import { fileAssociationStatus, inTauri, openFileAssociationSettings, setFileAssociationDefaults, } from "../../state/tauriBridge.js";
5
5
  import { isMac, isWindows } from "../../platform.js";
6
6
  import { hostAppName } from "../../appName.js";
7
7
  export const SUPPORTED_ASSOCIATION_FORMATS = [
@@ -66,7 +66,7 @@ export function FileAssociationsBlock() {
66
66
  const [notice, setNotice] = useState("");
67
67
  const mac = isMac();
68
68
  const windows = isWindows();
69
- const available = (mac || windows) && (inTauri() || isBrowserQaMode());
69
+ const available = (mac || windows) && inTauri();
70
70
  const allSelected = allFormatsSelected(selected);
71
71
  const extensionCounts = useMemo(() => new Map(SUPPORTED_ASSOCIATION_FORMATS.map((format) => [
72
72
  format.id,
@@ -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). */
@@ -164,12 +156,6 @@ export declare const preparePdfSave: PreparePdfSave;
164
156
  * `targetPath`/`documentSha256`; `affectedPages` is returned empty and `revision` as an
165
157
  * empty-pages placeholder so the return type still satisfies `PdfCommitResult` structurally. */
166
158
  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
159
  export declare function setInspectableHtmlPreview(token: string | null): void;
174
160
  /** Clear the DevTools owner, but only if `token` still owns it (a newer frame may have replaced it). */
175
161
  export declare function releaseInspectableHtmlPreview(token: string): void;
@@ -1,14 +1,6 @@
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");
12
4
  /** True when running inside the Tauri webview (vs. plain `vite dev` in a browser). */
13
5
  const inTauri = isTauri;
14
6
  let shell = null;
@@ -17,13 +9,6 @@ export function getShell() {
17
9
  shell ??= createShell();
18
10
  return shell;
19
11
  }
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
12
  /**
28
13
  * Write text to the system clipboard: through the shell's native clipboard in the app, through the
29
14
  * browser clipboard in browser QA. It rejects when no clipboard is available, so a caller never
@@ -39,15 +24,6 @@ export async function writeClipboardText(text) {
39
24
  }
40
25
  await navigator.clipboard.writeText(text);
41
26
  }
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
27
  async function invoke(cmd, args) {
52
28
  const { invoke } = await import("@tauri-apps/api/core");
53
29
  return invoke(cmd, args);
@@ -62,16 +38,6 @@ async function listen(event, callback) {
62
38
  * update Bearer; the webview only sends commands and receives validated display views.
63
39
  */
64
40
  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
41
  /** Classify an IPC rejection from `invoke()` into `{code, message}`. Tauri serializes a command's
76
42
  * `Err(HostError)` using the envelope's own `#[serde(tag = "code", content = "message")]`
77
43
  * representation, so a migrated command's rejection value IS already `{code, message}` — this
@@ -97,9 +63,6 @@ export function parseHostError(e) {
97
63
  return { code: "unknown", message: String(e) };
98
64
  }
99
65
  export async function openFileDialog() {
100
- if (BROWSER_QA_BUILD && isBrowserQaMode()) {
101
- return (await loadQaFixtures()).qaDialogPath();
102
- }
103
66
  if (!inTauri()) {
104
67
  console.warn("openFileDialog: not in Tauri — open a real document via `pnpm tauri dev`.");
105
68
  return null;
@@ -108,27 +71,6 @@ export async function openFileDialog() {
108
71
  }
109
72
  export async function readDocument(path) {
110
73
  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
74
  if (!inTauri()) {
133
75
  return { path, displayName: baseName(path), format, content: "" };
134
76
  }
@@ -191,10 +133,6 @@ export async function getPersistenceStatus() {
191
133
  return { degraded: status.degraded === true, quarantinedTo: status.quarantined_to ?? null };
192
134
  }
193
135
  export async function getInitialOpenPaths() {
194
- if (BROWSER_QA_BUILD && isBrowserQaMode()) {
195
- const path = (await loadQaFixtures()).qaInitialOpenPath();
196
- return path ? [path] : [];
197
- }
198
136
  if (!inTauri())
199
137
  return [];
200
138
  // The shell's open-paths gate: it grants and drains what argv / a second launch / macOS
@@ -325,8 +263,6 @@ export async function revokePath(path) {
325
263
  /** Write text, preserving the file's `encoding` (default UTF-8). Returns actual encoding plus the
326
264
  * committed source SHA-256; in-place callers send the prior SHA for stale-source protection. */
327
265
  export async function writeDocument(path, content, encoding, expectedSourceSha256) {
328
- if (BROWSER_QA_BUILD && isBrowserQaMode())
329
- return (await loadQaText()).writeQaTextDocument(path, content, encoding, expectedSourceSha256);
330
266
  if (!inTauri())
331
267
  throw new Error("Saving requires the desktop app.");
332
268
  const result = await invoke("write_document", {
@@ -344,9 +280,6 @@ export async function writeDocument(path, content, encoding, expectedSourceSha25
344
280
  // changing writeDocumentBytes's contract (out of scope here) — and a 32k-arg spread/apply call is
345
281
  // exactly the call-stack-pressure risk the loop below was already written to avoid. Not a clear
346
282
  // improvement, so kept unchanged per the audit's own guidance.
347
- function arrayBufferToBase64(ab) {
348
- return byteArrayToBase64(new Uint8Array(ab));
349
- }
350
283
  function byteArrayToBase64(bytes) {
351
284
  let bin = "";
352
285
  const chunk = 0x8000;
@@ -362,11 +295,6 @@ function byteArrayToBase64(bytes) {
362
295
  return btoa(bin);
363
296
  }
364
297
  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
298
  if (!inTauri())
371
299
  throw new Error("Saving requires the desktop app.");
372
300
  // Raw request body (like ocr_page): the DOCX bytes cross IPC as-is, with no JS base64 encode on
@@ -386,8 +314,6 @@ function pathToHex(path) {
386
314
  * reserializing its bytes. The desktop command owns both broker checks and the filesystem copy so
387
315
  * PDF Save As preserves signatures, encryption, widgets, and any bytes pdf.js cannot round-trip. */
388
316
  export async function copyDocument(sourcePath, targetPath) {
389
- if (BROWSER_QA_BUILD && isBrowserQaMode())
390
- return;
391
317
  if (!inTauri())
392
318
  throw new Error("Saving requires the desktop app.");
393
319
  await invoke("copy_document", { sourcePath, targetPath });
@@ -412,9 +338,6 @@ export async function editImageExternal(path) {
412
338
  return;
413
339
  await invoke("edit_image_external", { path });
414
340
  }
415
- import { hostAppName } from "../appName.js";
416
- if (BROWSER_QA_BUILD && isBrowserQaMode())
417
- void loadQaPdf().then((qaPdf) => qaPdf.registerViewrightQaGlobal());
418
341
  /** Normalizes an `invoke()` raw-body result (an `ArrayBuffer`, or occasionally already a
419
342
  * `Uint8Array`/plain byte array depending on Tauri's IPC transport) into a `Uint8Array`. */
420
343
  function toUint8Array(body) {
@@ -431,9 +354,8 @@ function toUint8Array(body) {
431
354
  * dynamic `@tauri-apps/api/core` import reach into `window.__TAURI_INTERNALS__`, which is
432
355
  * `undefined` there and throws a raw, unclassified "Cannot read properties of undefined (reading
433
356
  * '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
357
+ * show meaningfully. These six PDF-editor commands need the real Rust backend for source
358
+ * inspection/composition/save, so the correct graceful behavior is a clean, classified rejection
437
359
  * rather than either a raw crash or a fabricated result. */
438
360
  function requireTauriForPdfEdit(command) {
439
361
  if (!inTauri()) {
@@ -444,8 +366,6 @@ function requireTauriForPdfEdit(command) {
444
366
  export const ocrPage = async (png) => {
445
367
  // Browser QA has no Rust OCR backend, so the IPC-boundary fixture keeps the scanned-page UI
446
368
  // exercisable without moving a mock into the document components.
447
- if (BROWSER_QA_BUILD && isBrowserQaMode())
448
- return (await loadQaFixtures()).qaOcrLines();
449
369
  requireTauriForPdfEdit("ocr_page");
450
370
  const { invoke: tauriInvoke } = await import("@tauri-apps/api/core");
451
371
  return tauriInvoke("ocr_page", png);
@@ -460,8 +380,6 @@ let ocrAvailableProbe = null;
460
380
  export const ocrAvailable = () => {
461
381
  // Browser QA has no Rust OCR backend, so the IPC-boundary fixture keeps the scanned-page UI
462
382
  // exercisable without moving a mock into the document components.
463
- if (BROWSER_QA_BUILD && isBrowserQaMode())
464
- return Promise.resolve(true);
465
383
  if (!inTauri())
466
384
  return Promise.resolve(false);
467
385
  ocrAvailableProbe ??= invoke("ocr_available").catch(() => {
@@ -488,15 +406,11 @@ export const ocrBackendMissing = () => {
488
406
  export const inspectPdfPage = async (path, page) => {
489
407
  // Browser QA has no Rust content-stream walker, so the IPC-boundary fixture keeps Edit-text mode
490
408
  // 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
409
  requireTauriForPdfEdit("inspect_pdf_page");
494
410
  return invoke("inspect_pdf_page", { path, page });
495
411
  };
496
412
  /** §7.2 `list_pdf_fonts`. Returns the bundled + system font catalog available for embedding. */
497
413
  export const listPdfFonts = async () => {
498
- if (BROWSER_QA_BUILD && isBrowserQaMode())
499
- return (await loadQaPdf()).qaListPdfFonts();
500
414
  requireTauriForPdfEdit("list_pdf_fonts");
501
415
  return invoke("list_pdf_fonts");
502
416
  };
@@ -504,8 +418,6 @@ export const listPdfFonts = async () => {
504
418
  * disk bytes, for committed-unsaved-edit preview re-render — no token, no retained candidate, no
505
419
  * writes. Candidate bytes use JSON; affected pages derive from request commands. */
506
420
  export const composePdfPreview = async (request) => {
507
- if (BROWSER_QA_BUILD && isBrowserQaMode())
508
- return (await loadQaPdf()).qaComposePdfPreview(request);
509
421
  requireTauriForPdfEdit("compose_pdf_preview");
510
422
  const body = await invoke("compose_pdf_preview", {
511
423
  request: {
@@ -537,8 +449,6 @@ export const composePdfPreview = async (request) => {
537
449
  * populated as an empty string (the backend recheck matches by page + text substring only, not by
538
450
  * island id; see `pdf_save.rs`'s `structurally_verify_candidate`). */
539
451
  export const preparePdfSave = async (request) => {
540
- if (BROWSER_QA_BUILD && isBrowserQaMode())
541
- return (await loadQaPdf()).qaPreparePdfSave(request);
542
452
  requireTauriForPdfEdit("prepare_pdf_save");
543
453
  const meta = await invoke("prepare_pdf_save", {
544
454
  request: {
@@ -573,8 +483,6 @@ export const preparePdfSave = async (request) => {
573
483
  * `targetPath`/`documentSha256`; `affectedPages` is returned empty and `revision` as an
574
484
  * empty-pages placeholder so the return type still satisfies `PdfCommitResult` structurally. */
575
485
  export const commitPdfSave = async (token, targetPath) => {
576
- if (BROWSER_QA_BUILD && isBrowserQaMode())
577
- return (await loadQaPdf()).qaCommitPdfSave(token, targetPath);
578
486
  requireTauriForPdfEdit("commit_pdf_save");
579
487
  const result = await invoke("commit_pdf_save", {
580
488
  token,
@@ -587,16 +495,6 @@ export const commitPdfSave = async (token, targetPath) => {
587
495
  revision: { documentSha256: result.documentSha256, pageCount: 0, pages: [] },
588
496
  };
589
497
  };
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
498
  /** Token of the interactive HTML preview that currently owns DevTools (the active tab's frame), or
601
499
  * null. Set by the HTML preview frame; DevTools opens exactly that registered document. */
602
500
  let inspectableHtmlToken = null;
@@ -613,14 +511,14 @@ export function releaseInspectableHtmlPreview(token) {
613
511
  * main webview is never inspectable. No-op without an active HTML preview or outside Tauri. */
614
512
  export async function openDevtools() {
615
513
  const token = inspectableHtmlToken;
616
- if (!token || (BROWSER_QA_BUILD && isBrowserQaMode()) || !inTauri())
514
+ if (!token || !inTauri())
617
515
  return;
618
516
  await invoke("open_html_devtools", { token });
619
517
  }
620
518
  /** No-op: the macOS inspect-accelerator scope command was removed with the shell port (viewright#375). */
621
519
  export async function setDevtoolsShortcutScope(_enabled) { }
622
520
  export async function revealInFileExplorer(path) {
623
- if ((BROWSER_QA_BUILD && isBrowserQaMode()) || !inTauri())
521
+ if (!inTauri())
624
522
  return;
625
523
  await invoke("reveal_in_file_explorer", { path });
626
524
  }
@@ -651,24 +549,10 @@ export async function printCurrentWebview() {
651
549
  }
652
550
  }
653
551
  export async function listSystemFonts() {
654
- if (BROWSER_QA_BUILD && isBrowserQaMode())
655
- return ["Inter", "Source Serif 4", "JetBrains Mono", "Geist", "Newsreader"];
656
552
  if (!inTauri())
657
553
  return [];
658
554
  return invoke("list_system_fonts");
659
555
  }
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
556
  function toFormatStatus(group) {
673
557
  return {
674
558
  formatId: group.id,
@@ -682,17 +566,10 @@ function toFormatStatus(group) {
682
566
  };
683
567
  }
684
568
  export async function fileAssociationStatus() {
685
- if (BROWSER_QA_BUILD && isBrowserQaMode())
686
- return qaFileAssociationStatus();
687
569
  const status = await getShell().associations.status();
688
570
  return { formats: status.groups.map(toFormatStatus) };
689
571
  }
690
572
  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
573
  // macOS: one NSWorkspace request per content type (system consent sheet), one shared 60 s
697
574
  // deadline (the shell default; the host clamps a given value to 1 to 120 s).
698
575
  const result = await getShell().associations.set(formatIds);
@@ -701,8 +578,6 @@ export async function setFileAssociationDefaults(formatIds) {
701
578
  /** Windows owns protected default-app choices. This opens Windows Default apps; the shell never
702
579
  * writes UserChoice registry hashes or claims a default changed. */
703
580
  export async function openFileAssociationSettings(_formatIds) {
704
- if (BROWSER_QA_BUILD && isBrowserQaMode())
705
- return;
706
581
  await getShell().associations.openDefaultApps();
707
582
  }
708
583
  export async function saveSession(tabs) {
@@ -818,15 +693,11 @@ export function installExternalLinkGuard() {
818
693
  });
819
694
  }
820
695
  export async function saveFileDialog(defaultName, format) {
821
- if (BROWSER_QA_BUILD && isBrowserQaMode())
822
- return `D:\\${hostAppName()} QA\\${defaultName ?? `untitled.${format ?? "txt"}`}`;
823
696
  if (!inTauri())
824
697
  return null;
825
698
  return invoke("save_file_dialog", { defaultName, format });
826
699
  }
827
700
  export async function grantKnownFileToken(token) {
828
- if (BROWSER_QA_BUILD && isBrowserQaMode())
829
- return token;
830
701
  if (!inTauri())
831
702
  return null;
832
703
  return invoke("grant_known_file_token", { token });
@@ -840,15 +711,11 @@ export async function grantKnownFileToken(token) {
840
711
  // - regrantSavedTarget: re-grant a path the host app itself just wrote (save-as, duplicate, DOCX
841
712
  // save-as) — refused unless the exact path was written in the last few writes.
842
713
  export async function openLinkedDocument(target, from) {
843
- if (BROWSER_QA_BUILD && isBrowserQaMode())
844
- return target;
845
714
  if (!inTauri())
846
715
  return null;
847
716
  return invoke("open_linked_document", { target, from: from ?? null });
848
717
  }
849
718
  export async function regrantSavedTarget(path) {
850
- if (BROWSER_QA_BUILD && isBrowserQaMode())
851
- return path;
852
719
  if (!inTauri())
853
720
  return null;
854
721
  return invoke("regrant_saved_target", { path });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rightkit/document-core",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Shared base for the document editors: platform bridge, settings, state, PDF helpers and the format registry.",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "type": "module",
@@ -248,14 +248,14 @@
248
248
  "dependencies": {
249
249
  "@codemirror/view": "^6.43.14",
250
250
  "@phosphor-icons/react": "^2.1.10",
251
- "@rightkit/license": "^0.3.0",
251
+ "@rightkit/license": "^0.3.1",
252
252
  "@rightkit/logs": "^0.1.6",
253
- "@rightkit/pdf-ui": "^0.2.0",
254
- "@rightkit/platform-ui": "^0.4.0",
255
- "@rightkit/react-shell": "^0.7.2",
256
- "@rightkit/settings-ui": "^0.7.0",
257
- "@rightkit/tauri-shell": "^0.1.3",
258
- "@rightkit/theme": "^0.3.1",
253
+ "@rightkit/pdf-ui": "^0.3.0",
254
+ "@rightkit/platform-ui": "^0.4.1",
255
+ "@rightkit/react-shell": "^0.7.3",
256
+ "@rightkit/settings-ui": "^0.7.1",
257
+ "@rightkit/tauri-shell": "^0.1.4",
258
+ "@rightkit/theme": "^0.3.2",
259
259
  "@tauri-apps/api": "^2.12.2",
260
260
  "pdfjs-dist": "6.4.299"
261
261
  },
@@ -270,8 +270,7 @@
270
270
  "react": "^19.3.1",
271
271
  "react-dom": "^19.3.1",
272
272
  "typescript": "^7.0.2",
273
- "vite": "^8.3.4",
274
- "vitest": "^5.0.5"
273
+ "vite": "^8.3.4"
275
274
  },
276
275
  "publishConfig": {
277
276
  "registry": "https://registry.npmjs.org",
@@ -284,7 +283,6 @@
284
283
  },
285
284
  "scripts": {
286
285
  "build": "tsc -p tsconfig.json && node scripts/copy-assets.mjs",
287
- "typecheck": "tsc -p tsconfig.json --noEmit",
288
- "test": "pnpm --filter \"@rightkit/document-core^...\" run build && vitest run"
286
+ "typecheck": "tsc -p tsconfig.json --noEmit"
289
287
  }
290
288
  }
@@ -1,21 +0,0 @@
1
- /**
2
- * Fixture documents + IPC-boundary fallbacks for browser QA mode (`VITE_RIGHTKIT_BROWSER_QA=1`,
3
- * `?qa=1&doc=<key>`) — the headless-Chrome harness `scripts/qa-browser-functional.mjs` drives, and
4
- * the manual escape hatch for poking the app in a plain browser tab outside Tauri. `qaOcrLines`
5
- * lives here; the PDF-edit-command mocks (`inspect_pdf_page`/`compose_pdf_preview`/
6
- * `prepare_pdf_save`/`commit_pdf_save`/`list_pdf_fonts`, plus the in-memory PDF "disk" and the
7
- * `window.__viewrightQa` assertion surface) live in the sibling `browserQaPdf.ts` instead, since
8
- * they need their own save-token/sha bookkeeping this file has no reason to carry.
9
- *
10
- * PDF coverage split: annotation editing (FreeText/Ink/Highlight/Stamp, via pdf.js's own
11
- * `AnnotationEditorLayer`/`saveDocument()`) + two-phase Save/Save As is fully exercised in browser
12
- * QA against `browserQaPdf.ts`'s mocks. Existing-text/add-text editing (`inspect_pdf_page`'s
13
- * `islands`) stays native-only — browser QA has no Rust content-stream walker, so that path is only
14
- * reachable/non-crashing here, never a real edit round trip.
15
- */
16
- import { type LoadedDocument } from "../documents/types.js";
17
- import type { OcrLine } from "../documents/pdf/pdfOcr.js";
18
- export declare function qaOcrLines(): OcrLine[];
19
- export declare function qaDialogPath(): string;
20
- export declare function qaInitialOpenPath(): string | null;
21
- export declare function qaDocumentForPath(path: string): Promise<LoadedDocument | null>;