diff --git a/src/source/pdf/viewer-url.test.ts b/src/source/pdf/viewer-url.test.ts index 2e553ba..ce6b602 100644 --- a/src/source/pdf/viewer-url.test.ts +++ b/src/source/pdf/viewer-url.test.ts @@ -38,11 +38,16 @@ describe("resolvePdfViewerUrl", () => { ); }); - it("falls back to fixture path when only a stale blob uri is stored", () => { + it("returns null for stale blob uri without live bytes (no fixture guess)", () => { const store = createPdfByteStore(); expect( resolvePdfViewerUrl(doc({ uri: "blob:revoked", title: "a.pdf" }), store), - ).toBe("/fixtures/pdfs/a.pdf"); + ).toBeNull(); + }); + + it("returns null when uri was stripped on persist and bytes are gone", () => { + const store = createPdfByteStore(); + expect(resolvePdfViewerUrl(doc({ uri: undefined, title: "upload.pdf" }), store)).toBeNull(); }); }); @@ -51,4 +56,4 @@ describe("isEphemeralBlobUri", () => { expect(isEphemeralBlobUri("blob:http://localhost/x")).toBe(true); expect(isEphemeralBlobUri("/fixtures/pdfs/x.pdf")).toBe(false); }); -}); \ No newline at end of file +}); diff --git a/src/source/pdf/viewer-url.ts b/src/source/pdf/viewer-url.ts index 4768b0f..da36819 100644 --- a/src/source/pdf/viewer-url.ts +++ b/src/source/pdf/viewer-url.ts @@ -4,6 +4,10 @@ * Uploaded PDFs are served via ephemeral `blob:` URLs owned by * `PdfByteStore`. Those URLs must not be persisted (see persistence * sanitization) and must be re-resolved from live bytes on every render. + * + * After a reload, bytes come back via IndexedDB hydrate into the byte store. + * Never invent a `/fixtures/pdfs/` URL for arbitrary uploads — that + * path 404s (or returns HTML) and PDF.js reports "Invalid PDF structure". */ import type { Document } from "@shared/document"; @@ -20,7 +24,8 @@ export function isEphemeralBlobUri(uri: string | undefined): boolean { /** * Prefer the byte store's live blob URL for uploaded documents; fall back - * to a stable HTTP fixture path or a non-blob `document.uri`. + * to a stable non-blob `document.uri` (e.g. fixture HTTP path). Returns + * `null` when bytes are not available — ViewerShell shows re-upload copy. */ export function resolvePdfViewerUrl( document: Document, @@ -33,8 +38,8 @@ export function resolvePdfViewerUrl( return document.uri; } - const titleOrId = document.title ?? document.id; - return `/fixtures/pdfs/${encodeURIComponent(titleOrId)}`; + // Stale blob URI and no in-memory/IDB bytes: do not guess a fixture path. + return null; } /** True when bytes exist in the store but the document record lacks a URI. */ @@ -43,4 +48,4 @@ export function documentHasUploadedBytes( byteStore: PdfByteStore, ): boolean { return byteStore.has(documentId); -} \ No newline at end of file +} diff --git a/tests/integration/helpers/seed-session.ts b/tests/integration/helpers/seed-session.ts index a7aa2e9..c2e8848 100644 --- a/tests/integration/helpers/seed-session.ts +++ b/tests/integration/helpers/seed-session.ts @@ -49,10 +49,18 @@ export function seedSessionWithDoc(opts: SeedOptions): SeedResult { lastOpenedAt: now, }; + // Stable HTTP fixture URI so the viewer mounts without IndexedDB bytes. + // (resolvePdfViewerUrl no longer invents /fixtures paths from title alone — + // that produced "Invalid PDF structure" for arbitrary uploads after reload.) const document: Document = { id: documentId, mediaType: "application/pdf", - ...(opts.documentTitle ? { title: opts.documentTitle } : {}), + ...(opts.documentTitle + ? { + title: opts.documentTitle, + uri: `/fixtures/pdfs/${encodeURIComponent(opts.documentTitle)}`, + } + : {}), fingerprint: "seed-fingerprint", createdAt: now, updatedAt: now,