From 0de0e692d22a9058610a262a06a1797b7d892ae3 Mon Sep 17 00:00:00 2001 From: krflol Date: Tue, 20 Oct 2026 12:03:52 -0500 Subject: [PATCH 1/3] Preserve searchable invoice references in the React starter --- examples/react/src/documents/Invoice.tsx | 2 +- examples/react/src/documents/invoiceStyles.ts | 1 + tools/prepare-react-check.mjs | 2 +- tools/react_component_checks.py | 27 +- verification/react-invoice-references.json | 306 ++++++++++++++++++ 5 files changed, 333 insertions(+), 5 deletions(-) create mode 100644 verification/react-invoice-references.json diff --git a/examples/react/src/documents/Invoice.tsx b/examples/react/src/documents/Invoice.tsx index 0b1eaac..64c6d41 100644 --- a/examples/react/src/documents/Invoice.tsx +++ b/examples/react/src/documents/Invoice.tsx @@ -49,7 +49,7 @@ export function Invoice({ data, ink = '#17382e' }: { data: InvoiceData; ink?: st return
NORTHSTAR / STUDIO {'Independent design.\nThoughtful by nature.'}
-
{`Invoice ${data.number} · ${data.period}`} +
Invoice {data.number}{` · ${data.period}`} Good work.Clear numbers. A shared vision, made tangible. Here’s the work we brought to life together.
diff --git a/examples/react/src/documents/invoiceStyles.ts b/examples/react/src/documents/invoiceStyles.ts index 22e3177..11f296f 100644 --- a/examples/react/src/documents/invoiceStyles.ts +++ b/examples/react/src/documents/invoiceStyles.ts @@ -11,6 +11,7 @@ export const invoiceStyles = new StyleSheet({ ".brand-note": {"textAlign": "right", "fontSize": "8pt", "color": "#63756e"}, ".intro": {"paddingTop": "26pt", "paddingBottom": "24pt"}, ".eyebrow": {"color": "#a0442e", "fontSize": "8pt", "fontWeight": "700", "letterSpacing": "1.5pt", "textTransform": "uppercase"}, + ".invoice-reference": {"letterSpacing": "0pt", "textTransform": "none"}, "h1": {"fontSize": "42pt", "fontWeight": "700", "lineHeight": "1.08", "letterSpacing": "-2pt", "margin": "9pt 0 13pt"}, ".lede": {"width": "80%", "fontSize": "11pt", "color": "#63756e"}, ".meta": {"display": "grid", "gridTemplateColumns": "2fr 1fr 1fr", "gap": "14pt", "borderTop": "1pt solid #d3ded8", "paddingTop": "13pt", "marginBottom": "26pt"}, diff --git a/tools/prepare-react-check.mjs b/tools/prepare-react-check.mjs index eed6080..5c4b31a 100644 --- a/tools/prepare-react-check.mjs +++ b/tools/prepare-react-check.mjs @@ -42,7 +42,7 @@ const { sample } = await import(pathToFileURL(path.join(compiled, 'data.js'))); const componentCases = [ ['component-invoice', sample, '#17382e'], ['component-paid', { ...sample, paid: true }, '#17382e'], - ['component-edited', { ...sample, customer: 'Birch & ', number: 'TSX-2042' }, '#233b79'], + ['component-edited', { ...sample, customer: 'Birch & ', number: 'Ref-aBc-2042' }, '#233b79'], ['component-empty', { ...sample, items: [] }, '#17382e'], ['component-credit', { ...sample, items: [{ name: 'Credit & ', detail: '', quantity: 2, rate_cents: -1255, progress: 100 }] }, '#17382e'], ['component-long', { ...sample, items: Array.from({ length: 20 }, (_, index) => ({ diff --git a/tools/react_component_checks.py b/tools/react_component_checks.py index dcbf9a2..6909aad 100644 --- a/tools/react_component_checks.py +++ b/tools/react_component_checks.py @@ -4,6 +4,25 @@ from playwright.sync_api import expect from pypdf import PdfReader +import pypdfium2 as pdfium + + +def check_reference(path, reference, check): + # Identifiers must survive reader extraction and search without removing + # whitespace or changing case, even when the surrounding label is tracked. + text = PdfReader(path).pages[0].extract_text() + check(path.stem + ': reference extracts with exact spacing and case', reference in text) + with pdfium.PdfDocument(str(path)) as document: + page = document[0] + textpage = page.get_textpage() + search = textpage.search(reference, match_case=True) + try: + found = search.get_next() is not None + finally: + search.close() + textpage.close() + page.close() + check(path.stem + ': reference is searchable in PDFium', found) def check_components(page, out, baseline, check, ready, download, hold_engine, resume_engine, requested): @@ -25,7 +44,8 @@ def compare(name, text): ready() check('components are the initial design', page.locator('#kind').input_value() == 'components') - compare('component-invoice', ['Maple & Finch', 'NS-1042', '8,100.00']) + initial = compare('component-invoice', ['Maple & Finch', 'NS-1042', '8,100.00']) + check_reference(initial, 'NS-1042', check) check('component design needs no HTML editor', page.locator('#html-source').count() == 0) page.locator('#component-source summary').click() source = page.locator('#component-source code').last.inner_text() @@ -57,10 +77,11 @@ def compare(name, text): page.locator('#customer').fill('Obsolete component input') assert requested.wait(8), 'Expected a held component renderer.' page.locator('#customer').fill('Birch & ') - page.locator('#reference').fill('TSX-2042') + page.locator('#reference').fill('Ref-aBc-2042') page.locator('#accent').fill('#233b79') resume_engine() - edited = compare('component-edited', ['Birch & ', 'TSX-2042']) + edited = compare('component-edited', ['Birch & ', 'Ref-aBc-2042']) + check_reference(edited, 'Ref-aBc-2042', check) check('superseded component results cannot replace newer input', 'Obsolete component input' not in PdfReader(edited).pages[0].extract_text()) page.screenshot(path=str(out / 'component-edited.png'), full_page=True) page.locator('#auto-preview').uncheck() diff --git a/verification/react-invoice-references.json b/verification/react-invoice-references.json new file mode 100644 index 0000000..cbbd2e3 --- /dev/null +++ b/verification/react-invoice-references.json @@ -0,0 +1,306 @@ +{ + "ok": true, + "checked_at": "2026-10-20T17:03:51.665810+00:00", + "problem": "Tracked and uppercased invoice references cannot be searched or copied exactly.", + "package": "fullbleed@0.6.0", + "engine": "2.5.24", + "node": "v22.23.3", + "source_hashes": { + "examples/react/src/documents/Invoice.tsx": "6737ef4e107935cf0e66291f1ef1576d6aef37e647555e81ce875bf37a5cb589", + "examples/react/src/documents/invoiceStyles.ts": "d9d63ef41fccae67d1a36de315b348c56ab5a680a18b0b2c43ffd63c46f18895", + "tools/prepare-react-check.mjs": "ad306e576d6d60db5e334de7d43a0c166761d5288e74b35ef1de61895df1bbcb", + "tools/react_component_checks.py": "2e5a5e68aa12639ca15cc1b6e3194e43a79e32f4e638f5424b3f609fe0d8f688" + }, + "references": [ + { + "case": "public-before", + "path": "verified-desktop.pdf", + "reference": "NS-1042", + "sha256": "7ab921d0f55604f84fd519514586f8d162e2912e925ea4071a59dfbd93993f56", + "exact_pypdf_text": false, + "exact_pdfium_text": false, + "pdfium_case_sensitive_search": null, + "regression_checks": [ + { + "name": "verified-desktop: reference extracts with exact spacing and case", + "passed": false + }, + { + "name": "verified-desktop: reference is searchable in PDFium", + "passed": false + } + ], + "expected_result": false + }, + { + "case": "fixed-default", + "path": "component-invoice.pdf", + "reference": "NS-1042", + "sha256": "f2553f220e9d30e26df785a4ed251f07ff1e615afd3972a5c29c19a0c5fd3f6c", + "exact_pypdf_text": true, + "exact_pdfium_text": true, + "pdfium_case_sensitive_search": [ + 77, + 7 + ], + "regression_checks": [ + { + "name": "component-invoice: reference extracts with exact spacing and case", + "passed": true + }, + { + "name": "component-invoice: reference is searchable in PDFium", + "passed": true + } + ], + "expected_result": true + }, + { + "case": "fixed-mixed-case", + "path": "component-edited.pdf", + "reference": "Ref-aBc-2042", + "sha256": "a664f9fd84af1d2354537645aa6b70dd9abe3ab762b8a9631047aab742b1297e", + "exact_pypdf_text": true, + "exact_pdfium_text": true, + "pdfium_case_sensitive_search": [ + 77, + 12 + ], + "regression_checks": [ + { + "name": "component-edited: reference extracts with exact spacing and case", + "passed": true + }, + { + "name": "component-edited: reference is searchable in PDFium", + "passed": true + } + ], + "expected_result": true + } + ], + "local_browser": { + "browser_engine": "chrome", + "browser": "155.0.8059.40", + "react_mode": "production", + "playwright": "1.63.0", + "pypdf": "6.19.0" + }, + "browser_checks": [ + "components are the initial design", + "component-invoice: real PDF has expected pages and text", + "component-invoice: matches the installed Node component renderer", + "component-invoice: every page has a preview", + "component-invoice: page 1 preview matches Node", + "component-invoice: reference extracts with exact spacing and case", + "component-invoice: reference is searchable in PDFium", + "component design needs no HTML editor", + "component source shows typed JSX, mapping and conditional content", + "components at 1440px: no horizontal overflow", + "components at 390px: no horizontal overflow", + "components at 320px: no horizontal overflow", + "component-paid: real PDF has expected pages and text", + "component-paid: matches the installed Node component renderer", + "component-paid: every page has a preview", + "component-paid: page 1 preview matches Node", + "invalid component data clears stale output without starting a worker", + "invalid cents cannot generate or retain a download", + "component-edited: real PDF has expected pages and text", + "component-edited: matches the installed Node component renderer", + "component-edited: every page has a preview", + "component-edited: page 1 preview matches Node", + "component-edited: reference extracts with exact spacing and case", + "component-edited: reference is searchable in PDFium", + "superseded component results cannot replace newer input", + "component manual mode waits for an explicit render", + "component-empty: real PDF has expected pages and text", + "component-empty: matches the installed Node component renderer", + "component-empty: every page has a preview", + "component-empty: page 1 preview matches Node", + "component-credit: real PDF has expected pages and text", + "component-credit: matches the installed Node component renderer", + "component-credit: every page has a preview", + "component-credit: page 1 preview matches Node", + "demo line-item bound is visible", + "component-long: real PDF has expected pages and text", + "component-long: matches the installed Node component renderer", + "component-long: every page has a preview", + "component-long: page 1 preview matches Node", + "component-long: page 2 preview matches Node", + "component-long: page 3 preview matches Node", + "all twenty component rows appear once", + "expanded line-item controls fit 320px", + "component cancellation releases worker and URLs", + "component cancellation recovers", + "component unmount releases workers and output URLs", + "invoice: real PDF has expected pages and text", + "invoice matches the installed Node package", + "initial automatic render starts one worker", + "settled render terminates its worker", + "runtime resolves beneath a nested deployment path", + "bundled interface font loads", + "1440px: no horizontal overflow", + "390px: no horizontal overflow", + "320px: no horizontal overflow", + "on-demand mode does not schedule a render after edits", + "manual: real PDF has expected pages and text", + "automatic rendering can be re-enabled", + "edits immediately hide the old preview and release its URLs", + "latest-input: real PDF has expected pages and text", + "superseded render cannot overwrite the latest input", + "a burst of edits is debounced into one new worker", + "replacement does not overlap active worker handles", + "only current PDF and preview URLs remain", + "edited-template: real PDF has expected pages and text", + "edited print CSS changes the page size", + "document scripts are not executed in the app", + "edited template uses unembedded standard Helvetica faces", + "edited standard-font preview contains visible text", + "engine-counter-sibling-reset: downloaded PDF has the expected page count", + "engine-counter-sibling-reset: sibling counter resets produce the required labels", + "engine-overflow-nested-clips: downloaded PDF has the expected page count", + "engine-overflow-nested-clips: downloaded PDF has the expected dimensions", + "engine-overflow-nested-clips: downloaded PDF matches independent color probes", + "engine-overflow-nested-clips: displayed preview has the expected dimensions", + "engine-overflow-nested-clips: displayed preview matches independent color probes", + "engine-border-center-zero-top-falls-back-bottom-svg: downloaded PDF has the expected page count", + "engine-border-center-zero-top-falls-back-bottom-svg: downloaded PDF has the expected dimensions", + "engine-border-center-zero-top-falls-back-bottom-svg: downloaded PDF matches independent color probes", + "engine-border-center-zero-top-falls-back-bottom-svg: displayed preview has the expected dimensions", + "engine-border-center-zero-top-falls-back-bottom-svg: displayed preview matches independent color probes", + "engine-first-letter-decorated-left: downloaded PDF has the expected page count", + "engine-first-letter-decorated-left: downloaded text matches the expected visible content", + "engine-first-letter-decorated-left: downloaded PDF has the expected dimensions", + "engine-first-letter-decorated-left: downloaded PDF matches independent color probes", + "engine-first-letter-decorated-left: displayed preview has the expected dimensions", + "engine-first-letter-decorated-left: displayed preview matches independent color probes", + "visibility-script-blocks: downloaded PDF has the expected page count", + "visibility-script-blocks: downloaded text matches the expected visible content", + "visibility-script-blocks: PDF matches the released source/control fixture", + "visibility-style-blocks: downloaded PDF has the expected page count", + "visibility-style-blocks: downloaded text matches the expected visible content", + "visibility-style-blocks: PDF matches the released source/control fixture", + "visibility-hidden-span-paragraph: downloaded PDF has the expected page count", + "visibility-hidden-span-paragraph: downloaded text matches the expected visible content", + "visibility-hidden-span-paragraph: PDF matches the released source/control fixture", + "visibility-script-revert: downloaded PDF has the expected page count", + "visibility-script-revert: downloaded text matches the expected visible content", + "visibility-script-revert: PDF matches the released source/control fixture", + "visibility-script-inline-paragraph: downloaded PDF has the expected page count", + "visibility-script-inline-paragraph: downloaded text matches the expected visible content", + "visibility-script-inline-paragraph: PDF matches the released source/control fixture", + "decoration-inter-40-underline-font: downloaded PDF has the expected page count", + "decoration-inter-40-underline-font: downloaded text matches the expected visible content", + "decoration-inter-40-underline-font: decoration stroke count matches", + "decoration-inter-40-underline-font: decoration uses the released font or CSS width", + "decoration-inter-40-underline-length: downloaded PDF has the expected page count", + "decoration-inter-40-underline-length: downloaded text matches the expected visible content", + "decoration-inter-40-underline-length: decoration stroke count matches", + "decoration-inter-40-underline-length: decoration uses the released font or CSS width", + "decoration-inter-40-underline-auto: downloaded PDF has the expected page count", + "decoration-inter-40-underline-auto: downloaded text matches the expected visible content", + "decoration-inter-40-underline-auto: decoration stroke count matches", + "decoration-inter-40-underline-auto: decoration uses the released font or CSS width", + "report: real PDF has expected pages and text", + "report matches the installed Node package", + "all report pages have previews", + "each design preserves its template edits", + "invalid input cannot leave an old download", + "cancel removes the worker and stale download", + "recovered: real PDF has expected pages and text", + "unmount releases ready PDF and preview URLs", + "unmount aborts a pending worker", + "remounted: real PDF has expected pages and text", + "remount starts from clean state and recovers", + "all created URLs and workers are released at final unmount", + "no uploads or cross-origin document requests", + "no uncaught page errors" + ], + "component_pdf_png_equal_node": true, + "html_invoice_report_pdf_bytes_unchanged": true, + "baselines": [ + { + "design": "invoice", + "pages": 1, + "pdfSha256": "3078d155f30536f5ad721f7be3c241cd78661ae41249131fc0fd65f37f23e408", + "previews": [ + "72420749ccc3deaa8025daf7baffcdf79be716b6e8e93ed605effebed01e86c7" + ] + }, + { + "design": "report", + "pages": 3, + "pdfSha256": "139df89824e6a73c13ff62f1237bb8eb9f71e790dcbd051b1bb713ed2bc47802", + "previews": [ + "33ee3398d59f6836368cdab0f3c725c3cf12cbf2f9f3a3ac6a6b168f6c1886f5", + "c8fe5102e9219962ea068201be0a6730cb37de797649f4b5f44478433e79b018", + "a3e8acbc7ba4147c56d45b606d3974ec4aecbda1b5d7362451e5e8890491dc7d" + ] + }, + { + "design": "component-invoice", + "pages": 1, + "pdfSha256": "f2553f220e9d30e26df785a4ed251f07ff1e615afd3972a5c29c19a0c5fd3f6c", + "previews": [ + "54a931df6a4741745ae651800ff74311cb59f1c74b58a0a67936a497e288648c" + ], + "repeatedPdfPreviewsEqual": true + }, + { + "design": "component-paid", + "pages": 1, + "pdfSha256": "91d8c8360fac37cd413fe669ed8f06650658b9ca01777129879207aac014d4c2", + "previews": [ + "8028cdd4d9b967e61084f54711072a1cc93a208b557749187dc186eec251d531" + ], + "repeatedPdfPreviewsEqual": true + }, + { + "design": "component-edited", + "pages": 1, + "pdfSha256": "a664f9fd84af1d2354537645aa6b70dd9abe3ab762b8a9631047aab742b1297e", + "previews": [ + "2ebbfde2c38975be8abf5312947a07d6375039f422fef3ad81aaad7d67cfe115" + ], + "repeatedPdfPreviewsEqual": true + }, + { + "design": "component-empty", + "pages": 1, + "pdfSha256": "cc1982eaea9dfa05b014cffea44644ecc2f4d33e8eb22ae6f6a501ab6d7f1248", + "previews": [ + "2c7539839be57f85421a98fad5a1abc403d914b87d0539f9128c91691c24f783" + ], + "repeatedPdfPreviewsEqual": true + }, + { + "design": "component-credit", + "pages": 1, + "pdfSha256": "cd57b56ec26e86365ab0223656d7e21b1543c9fbf404e967f0aef2e9b98024e6", + "previews": [ + "8937d9ac9b42cf13c1dd28fcdb150dabd3f404e8bf7816050824c7905d6898e3" + ], + "repeatedPdfPreviewsEqual": true + }, + { + "design": "component-long", + "pages": 3, + "pdfSha256": "589210d6b5bb0684fdbf5dbf62c5e32742c7166d3a8cfc5ae304c430e08357a3", + "previews": [ + "a22c18101f3303398a17c655b88f005fffde67dddb3f0b28521a78041125f4fe", + "61e388cdd7c7d133c647b07886bd818d91382b072323c79281c80c698983b24b", + "7648f8591967cf8be2ad40f7743cc95464606687f8bcd14ac48098d9f48560c5" + ], + "repeatedPdfPreviewsEqual": true + } + ], + "visual_review": { + "passed": true, + "files": { + "component-invoice-1.png": "54a931df6a4741745ae651800ff74311cb59f1c74b58a0a67936a497e288648c", + "component-edited-1.png": "2ebbfde2c38975be8abf5312947a07d6375039f422fef3ad81aaad7d67cfe115" + }, + "observations": "Default and mixed-case reference are readable; page content, spacing, totals and footer fit without clipping." + }, + "scope": "Windows Chrome production browser downloads, exact pypdf extraction and PDFium case-sensitive search. CI independently repeats supported browser checks. This is a template fix, not a new package release or a conformance claim." +} From 7a79b374784e60998fd163e0f86a76dae37523e5 Mon Sep 17 00:00:00 2001 From: krflol Date: Tue, 20 Oct 2026 16:19:33 -0500 Subject: [PATCH 2/3] Refresh framework starters to Fullbleed 0.7.1 --- examples/browser/README.md | 4 +- examples/browser/package-lock.json | 8 +- examples/browser/package.json | 2 +- examples/nextjs/README.md | 4 +- examples/nextjs/package-lock.json | 8 +- examples/nextjs/package.json | 2 +- examples/react/README.md | 4 +- examples/react/package-lock.json | 8 +- examples/react/package.json | 2 +- examples/vue/README.md | 4 +- examples/vue/package-lock.json | 8 +- examples/vue/package.json | 2 +- verification/starters-071.json | 342 +++++++++++++++++++++++++++++ 13 files changed, 370 insertions(+), 28 deletions(-) create mode 100644 verification/starters-071.json diff --git a/examples/browser/README.md b/examples/browser/README.md index 8d2e956..f01cde5 100644 --- a/examples/browser/README.md +++ b/examples/browser/README.md @@ -63,8 +63,8 @@ explains CSP, custom assets, structured errors, and browser worker lifecycle lim ## Versions and verification -The lockfile pins Fullbleed npm 0.5.1 / engine 2.5.24 and Vite 8.3.2. -The browser SDK is covered by the [versioned release evidence](https://github.com/fullbleed-engine/fullbleed-node/releases/tag/v0.5.1). +The lockfile pins Fullbleed npm 0.7.1 / engine 2.5.29 and Vite 8.3.2. +The browser SDK is covered by the [versioned release evidence](https://github.com/fullbleed-engine/fullbleed-node/releases/tag/v0.7.1). The repository's starter check builds this app, serves it below `/nested/demo/`, downloads actual PDFs, checks their text and page size, exercises editing and cancellation, and captures desktop/mobile screenshots. Playwright WebKit is not diff --git a/examples/browser/package-lock.json b/examples/browser/package-lock.json index 07e6174..ec01bcc 100644 --- a/examples/browser/package-lock.json +++ b/examples/browser/package-lock.json @@ -8,7 +8,7 @@ "name": "fullbleed-browser-starter", "version": "1.0.0", "dependencies": { - "fullbleed": "0.5.1" + "fullbleed": "0.7.1" }, "devDependencies": { "vite": "8.3.2" @@ -357,9 +357,9 @@ } }, "node_modules/fullbleed": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.5.1.tgz", - "integrity": "sha512-NSrRDtxqMqm1/tZdEeJm1rSRSvLwL9xpX4emE0cV9wTYSIBuynoQwgV+WbC04/OXfCZpPau7vt5hS0zxYbZaMQ==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.7.1.tgz", + "integrity": "sha512-lCN2tzqSJwrwqObrPT0StCEKLrwvSbikejqQR3lof0pFq8TlgO1bS3fDpA1+Xh5UcRZnMKx6BfT54or93pQyGw==", "license": "MIT", "dependencies": { "@bjorn3/browser_wasi_shim": "0.4.2" diff --git a/examples/browser/package.json b/examples/browser/package.json index 33ac88f..6f8fece 100644 --- a/examples/browser/package.json +++ b/examples/browser/package.json @@ -10,6 +10,6 @@ "build": "npm run assets && vite build", "preview": "vite preview --host 127.0.0.1" }, - "dependencies": { "fullbleed": "0.5.1" }, + "dependencies": { "fullbleed": "0.7.1" }, "devDependencies": { "vite": "8.3.2" } } diff --git a/examples/nextjs/README.md b/examples/nextjs/README.md index aa9cfa0..d29945d 100644 --- a/examples/nextjs/README.md +++ b/examples/nextjs/README.md @@ -20,8 +20,8 @@ Open `http://127.0.0.1:3000` and select **Download sample PDF**. The route IDs return 404. Use `npm run dev` while editing the app. `PORT` changes the port; `FULLBLEED_HOST` changes the production server's loopback binding. -The lockfile pins Next.js 16.3.8, React 19.3.0 and Fullbleed Node 0.5.1 -(engine 2.5.24) from npm. The registry package is byte-identical to the +The lockfile pins Next.js 16.3.8, React 19.3.0 and Fullbleed Node 0.7.1 +(engine 2.5.29) from npm. The registry package is byte-identical to the verified GitHub release tarball. Installing this starter needs no Python, Rust, system fonts or browser renderer. diff --git a/examples/nextjs/package-lock.json b/examples/nextjs/package-lock.json index 0d80bfb..cb405a4 100644 --- a/examples/nextjs/package-lock.json +++ b/examples/nextjs/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "fullbleed": "0.5.1", + "fullbleed": "0.7.1", "next": "16.3.8", "react": "19.3.0", "react-dom": "19.3.0" @@ -787,9 +787,9 @@ } }, "node_modules/fullbleed": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.5.1.tgz", - "integrity": "sha512-NSrRDtxqMqm1/tZdEeJm1rSRSvLwL9xpX4emE0cV9wTYSIBuynoQwgV+WbC04/OXfCZpPau7vt5hS0zxYbZaMQ==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.7.1.tgz", + "integrity": "sha512-lCN2tzqSJwrwqObrPT0StCEKLrwvSbikejqQR3lof0pFq8TlgO1bS3fDpA1+Xh5UcRZnMKx6BfT54or93pQyGw==", "license": "MIT", "dependencies": { "@bjorn3/browser_wasi_shim": "0.4.2" diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json index 6bc226d..7887775 100644 --- a/examples/nextjs/package.json +++ b/examples/nextjs/package.json @@ -16,7 +16,7 @@ "verify": "node tools/verify.mjs" }, "dependencies": { - "fullbleed": "0.5.1", + "fullbleed": "0.7.1", "next": "16.3.8", "react": "19.3.0", "react-dom": "19.3.0" diff --git a/examples/react/README.md b/examples/react/README.md index cb4d6c4..2215118 100644 --- a/examples/react/README.md +++ b/examples/react/README.md @@ -49,7 +49,7 @@ See [supported print CSS](https://docs.fullbleed.dev/css-coverage/) for layout f ## Reuse the hook Copy `src/usePdfPreview.ts` and the `src/documents/` directory into your client application, install -`fullbleed@0.6.0`, and copy the runtime with +`fullbleed@0.7.1`, and copy the runtime with `npx fullbleed-browser-assets public/fullbleed`. Keep the document input stable with `useMemo` so ordinary component updates do not schedule another render: @@ -112,7 +112,7 @@ WebAssembly, and Web Crypto are required. ## Versions and verification -The lockfile pins Fullbleed 0.6.0 / engine 2.5.24, React 19.3.0, TypeScript 7.0.2, +The lockfile pins Fullbleed 0.7.1 / engine 2.5.29, React 19.3.0, TypeScript 7.0.2, and Vite 8.3.2. No Fullbleed engine or package release is needed for this example. The repository's `React starter` workflow builds the locked project and checks diff --git a/examples/react/package-lock.json b/examples/react/package-lock.json index cba2b2c..777dc83 100644 --- a/examples/react/package-lock.json +++ b/examples/react/package-lock.json @@ -8,7 +8,7 @@ "name": "fullbleed-react-starter", "version": "1.0.0", "dependencies": { - "fullbleed": "0.6.0", + "fullbleed": "0.7.1", "react": "19.3.0", "react-dom": "19.3.0" }, @@ -760,9 +760,9 @@ } }, "node_modules/fullbleed": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.6.0.tgz", - "integrity": "sha512-8tptrfaBbP4cjHohzPDPD1Lh3NuBFbRyVebn4kQhFsSX2VsMJwR6GURImfr9tbO3+iPjsZBFwZFxRYz238xc8g==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.7.1.tgz", + "integrity": "sha512-lCN2tzqSJwrwqObrPT0StCEKLrwvSbikejqQR3lof0pFq8TlgO1bS3fDpA1+Xh5UcRZnMKx6BfT54or93pQyGw==", "license": "MIT", "dependencies": { "@bjorn3/browser_wasi_shim": "0.4.2" diff --git a/examples/react/package.json b/examples/react/package.json index 7817642..d0642e1 100644 --- a/examples/react/package.json +++ b/examples/react/package.json @@ -14,7 +14,7 @@ "preview": "vite preview --host 127.0.0.1" }, "dependencies": { - "fullbleed": "0.6.0", + "fullbleed": "0.7.1", "react": "19.3.0", "react-dom": "19.3.0" }, diff --git a/examples/vue/README.md b/examples/vue/README.md index 537b8ba..17ab588 100644 --- a/examples/vue/README.md +++ b/examples/vue/README.md @@ -38,7 +38,7 @@ Use [supported print CSS](https://docs.fullbleed.dev/css-coverage/). ## Reuse the composable Copy `src/usePdfPreview.ts` into your client application, install -`fullbleed@0.5.1`, and copy the runtime with +`fullbleed@0.7.1`, and copy the runtime with `npx fullbleed-browser-assets public/fullbleed`: ```vue @@ -106,7 +106,7 @@ WebAssembly, and Web Crypto are required. ## Versions and verification -The lockfile pins Fullbleed 0.5.1 / engine 2.5.24, Vue 3.5.43, TypeScript 6.0.3, +The lockfile pins Fullbleed 0.7.1 / engine 2.5.29, Vue 3.5.43, TypeScript 6.0.3, and Vite 8.3.2. TypeScript 6 is used because the pinned `vue-tsc` still needs TypeScript's JavaScript compiler entry; TypeScript 7 does not expose it. diff --git a/examples/vue/package-lock.json b/examples/vue/package-lock.json index 80c00fe..19727a5 100644 --- a/examples/vue/package-lock.json +++ b/examples/vue/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "fullbleed": "0.5.1", + "fullbleed": "0.7.1", "vue": "3.5.43" }, "devDependencies": { @@ -605,9 +605,9 @@ } }, "node_modules/fullbleed": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.5.1.tgz", - "integrity": "sha512-NSrRDtxqMqm1/tZdEeJm1rSRSvLwL9xpX4emE0cV9wTYSIBuynoQwgV+WbC04/OXfCZpPau7vt5hS0zxYbZaMQ==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.7.1.tgz", + "integrity": "sha512-lCN2tzqSJwrwqObrPT0StCEKLrwvSbikejqQR3lof0pFq8TlgO1bS3fDpA1+Xh5UcRZnMKx6BfT54or93pQyGw==", "license": "MIT", "dependencies": { "@bjorn3/browser_wasi_shim": "0.4.2" diff --git a/examples/vue/package.json b/examples/vue/package.json index 03033ab..c99c73e 100644 --- a/examples/vue/package.json +++ b/examples/vue/package.json @@ -15,7 +15,7 @@ "preview": "vite preview --host 127.0.0.1" }, "dependencies": { - "fullbleed": "0.5.1", + "fullbleed": "0.7.1", "vue": "3.5.43" }, "devDependencies": { diff --git a/verification/starters-071.json b/verification/starters-071.json new file mode 100644 index 0000000..23cf196 --- /dev/null +++ b/verification/starters-071.json @@ -0,0 +1,342 @@ +{ + "ok": true, + "checkedAt": "2026-10-20T21:19:32.149448+00:00", + "package": "0.7.1", + "engine": "2.5.29", + "localBrowser": { + "browser": { + "browser": "155.0.8059.40", + "checks": 79, + "documents": [ + { + "name": "invoice", + "filename": "invoice-NS-1042.pdf", + "pages": 1, + "sha256": "3078d155f30536f5ad721f7be3c241cd78661ae41249131fc0fd65f37f23e408" + }, + { + "name": "color-only", + "filename": "invoice-NS-1042.pdf", + "pages": 1, + "sha256": "a2b69ba41f7ad4c9bcbd38d803a46e70f0b9ccacd90ffbab5b0f7d87ffca5e4b" + }, + { + "name": "customized", + "filename": "invoice-TEST-2042.pdf", + "pages": 1, + "sha256": "31ff314b44a6f13534cc383e0cf24929f0695c1dd3d84769dedf8828c6486a93" + }, + { + "name": "edited", + "filename": "invoice-TEST-2042.pdf", + "pages": 1, + "sha256": "fecff59c1bfd6b59de2f0fb56965a5668023b3b88e64b5c6c30f95875592c4b2" + }, + { + "name": "report", + "filename": "community-report.pdf", + "pages": 3, + "sha256": "139df89824e6a73c13ff62f1237bb8eb9f71e790dcbd051b1bb713ed2bc47802" + }, + { + "name": "recovered", + "filename": "invoice-TEST-2042.pdf", + "pages": 1, + "sha256": "31ff314b44a6f13534cc383e0cf24929f0695c1dd3d84769dedf8828c6486a93" + } + ], + "pageErrors": [], + "packageLockSha256": "1d7f5a85463a76852b014e664352432619e09eb2f32dda9d5309a1aa9c36d8c6" + }, + "react": { + "browser": "155.0.8059.40", + "checks": 128, + "documents": [ + { + "name": "component-invoice", + "pages": 1, + "filename": "invoice-NS-1042.pdf", + "sha256": "f2553f220e9d30e26df785a4ed251f07ff1e615afd3972a5c29c19a0c5fd3f6c", + "whitespace_normalized_text_checks": true + }, + { + "name": "component-paid", + "pages": 1, + "filename": "invoice-NS-1042.pdf", + "sha256": "91d8c8360fac37cd413fe669ed8f06650658b9ca01777129879207aac014d4c2", + "whitespace_normalized_text_checks": true + }, + { + "name": "component-edited", + "pages": 1, + "filename": "invoice-Ref-aBc-2042.pdf", + "sha256": "a664f9fd84af1d2354537645aa6b70dd9abe3ab762b8a9631047aab742b1297e", + "whitespace_normalized_text_checks": true + }, + { + "name": "component-empty", + "pages": 1, + "filename": "invoice-NS-1042.pdf", + "sha256": "cc1982eaea9dfa05b014cffea44644ecc2f4d33e8eb22ae6f6a501ab6d7f1248", + "whitespace_normalized_text_checks": true + }, + { + "name": "component-credit", + "pages": 1, + "filename": "invoice-NS-1042.pdf", + "sha256": "cd57b56ec26e86365ab0223656d7e21b1543c9fbf404e967f0aef2e9b98024e6", + "whitespace_normalized_text_checks": true + }, + { + "name": "component-long", + "pages": 3, + "filename": "invoice-NS-1042.pdf", + "sha256": "589210d6b5bb0684fdbf5dbf62c5e32742c7166d3a8cfc5ae304c430e08357a3", + "whitespace_normalized_text_checks": true + }, + { + "name": "invoice", + "pages": 1, + "filename": "invoice-NS-1042.pdf", + "sha256": "3078d155f30536f5ad721f7be3c241cd78661ae41249131fc0fd65f37f23e408", + "whitespace_normalized_text_checks": false + }, + { + "name": "manual", + "pages": 1, + "filename": "invoice-NS-1042.pdf", + "sha256": "1df9b1852ad96e07d00520d190140044f528b14047df6e30432e91a512be8883", + "whitespace_normalized_text_checks": false + }, + { + "name": "latest-input", + "pages": 1, + "filename": "invoice-REACT-2042.pdf", + "sha256": "fccdad8d56abef7206e5efefc716af2c44c717c0e1c3619827c6d27931ed76be", + "whitespace_normalized_text_checks": false + }, + { + "name": "edited-template", + "pages": 1, + "filename": "invoice-REACT-2042.pdf", + "sha256": "c1fe667a39ac3eaae3292c3a1fab1c466999e798a5ab0a4616b73689acc79880", + "whitespace_normalized_text_checks": false + }, + { + "name": "report", + "pages": 3, + "filename": "community-report.pdf", + "sha256": "139df89824e6a73c13ff62f1237bb8eb9f71e790dcbd051b1bb713ed2bc47802", + "whitespace_normalized_text_checks": false + }, + { + "name": "recovered", + "pages": 1, + "filename": "invoice-REACT-2042.pdf", + "sha256": "fccdad8d56abef7206e5efefc716af2c44c717c0e1c3619827c6d27931ed76be", + "whitespace_normalized_text_checks": false + }, + { + "name": "remounted", + "pages": 1, + "filename": "invoice-NS-1042.pdf", + "sha256": "3078d155f30536f5ad721f7be3c241cd78661ae41249131fc0fd65f37f23e408", + "whitespace_normalized_text_checks": false + } + ], + "pageErrors": [], + "packageLockSha256": "cc4f2b5ed5fe698b1201041c4f36fb976d17119441c072a786beed0f41ad520b", + "baselineArtifactsUnchanged": 20 + }, + "vue": { + "browser": "155.0.8059.40", + "checks": 87, + "documents": [ + { + "name": "invoice", + "pages": 1, + "filename": "invoice-NS-1042.pdf", + "sha256": "3078d155f30536f5ad721f7be3c241cd78661ae41249131fc0fd65f37f23e408" + }, + { + "name": "manual", + "pages": 1, + "filename": "invoice-NS-1042.pdf", + "sha256": "1df9b1852ad96e07d00520d190140044f528b14047df6e30432e91a512be8883" + }, + { + "name": "latest-input", + "pages": 1, + "filename": "invoice-VUE-2042.pdf", + "sha256": "f63a42a366b08f4f054cca5594dbcd22510455ddfe2e9129623facfb8371e8ee" + }, + { + "name": "edited-template", + "pages": 1, + "filename": "invoice-VUE-2042.pdf", + "sha256": "071d2bd98b7b90b52aa5b60e74ce0934ef54d3c6032c222d7237786dc12fc953" + }, + { + "name": "report", + "pages": 3, + "filename": "community-report.pdf", + "sha256": "139df89824e6a73c13ff62f1237bb8eb9f71e790dcbd051b1bb713ed2bc47802" + }, + { + "name": "outage-recovery", + "pages": 1, + "filename": "invoice-VUE-2042.pdf", + "sha256": "f63a42a366b08f4f054cca5594dbcd22510455ddfe2e9129623facfb8371e8ee" + }, + { + "name": "recovered", + "pages": 1, + "filename": "invoice-VUE-2042.pdf", + "sha256": "f63a42a366b08f4f054cca5594dbcd22510455ddfe2e9129623facfb8371e8ee" + }, + { + "name": "remounted", + "pages": 1, + "filename": "invoice-NS-1042.pdf", + "sha256": "3078d155f30536f5ad721f7be3c241cd78661ae41249131fc0fd65f37f23e408" + } + ], + "pageErrors": [], + "packageLockSha256": "ea2c616c264d449e601236bf5806f486dc5a403cc68d36faf49a28dec4b56cf1", + "baselineArtifactsUnchanged": 6 + }, + "nextjs": { + "node": "v22.23.3", + "platform": "win32", + "next": "16.3.8", + "isolation": "process", + "pages": 1, + "pdfSha256": "3078d155f30536f5ad721f7be3c241cd78661ae41249131fc0fd65f37f23e408", + "burst": [ + 200, + 503, + 503, + 503, + 503, + 503, + 503, + 503, + 503, + 503, + 503, + 503 + ], + "checks": [ + { + "name": "standalone artifact is isolated from the source application", + "passed": true + }, + { + "name": "standalone tracing retains the verified engine and all bundled fonts", + "passed": true + }, + { + "name": "isolated production server starts on loopback", + "passed": true + }, + { + "name": "app exposes the sample download without a prefetched render", + "passed": true + }, + { + "name": "standalone server includes the actual PDF preview asset", + "passed": true + }, + { + "name": "production route returns complete private PDF bytes with a useful filename", + "passed": true + }, + { + "name": "unknown invoice IDs return a private JSON error", + "passed": true + }, + { + "name": "unsupported writes are rejected by the route", + "passed": true + }, + { + "name": "a concurrent burst receives explicit capacity errors instead of an unbounded queue", + "passed": true + }, + { + "name": "normal PDF downloads recover after a capacity burst", + "passed": true + }, + { + "name": "a real missing-glyph failure returns a generic non-PDF error", + "passed": true + }, + { + "name": "a valid document renders after an engine failure", + "passed": true + }, + { + "name": "the route enforces its page cap against a real multipage document", + "passed": true + }, + { + "name": "a valid document renders after the page limit rejects a job", + "passed": true + }, + { + "name": "a failed render child returns an HTTP error while the production server stays alive", + "passed": true + }, + { + "name": "the production server returns a valid PDF after a render-process failure", + "passed": true + }, + { + "name": "client JavaScript excludes the server PDF engine and worker", + "passed": true + } + ], + "scope": "Real next build standalone server with released Fullbleed npm package and fictional data; HTTP recovery after deliberate child failure. No hosted platform or application-authentication claim.", + "previewSha256": "72420749ccc3deaa8025daf7baffcdf79be716b6e8e93ed605effebed01e86c7", + "packageLockSha256": "ffc199cb54dfc51e1da70ee9b2685cb7a376ea62e65353c86c3175dee9746245" + } + }, + "reactStrictModeChecks": 128, + "reactDevelopmentChecks": [ + "initial: actual PDF includes source and data edits", + "development JSX matches installed Node output", + "data-edit: actual PDF includes source and data edits", + "tsx-edit: actual PDF includes source and data edits", + "editing print TSX retains React form state", + "style-edit: actual PDF includes source and data edits", + "editing the attached stylesheet changes the PDF", + "the edited heading size reaches the PDF", + "development source edits have no uncaught page errors" + ], + "sourceFileSha256": { + "examples/browser/README.md": "b45dc07f943ae2339c549d84fa2ff9f4b354a9c062cea7e3a36c005c9e8ea0a3", + "examples/browser/package-lock.json": "1d7f5a85463a76852b014e664352432619e09eb2f32dda9d5309a1aa9c36d8c6", + "examples/browser/package.json": "d2fe05482a01ac835988ace476c6f727e806759188c71ad6fa99a8cfb70fa5f8", + "examples/nextjs/README.md": "61bee7f7c17168a2061893762142cc795fe7992e97d92feab9b73f4c95d66bd3", + "examples/nextjs/package-lock.json": "ffc199cb54dfc51e1da70ee9b2685cb7a376ea62e65353c86c3175dee9746245", + "examples/nextjs/package.json": "2ce3a14c9fa5e182f844597dd89de16f38ad27450c8811d2682dae18249c26fa", + "examples/react/README.md": "bed4d14935f0381265742bd7396a314db79fc246059b32ae198befd4f053252f", + "examples/react/package-lock.json": "cc4f2b5ed5fe698b1201041c4f36fb976d17119441c072a786beed0f41ad520b", + "examples/react/package.json": "a5c84e2b74f39bcc5aa76e60782a8c7427fe550f29a3a86d687a98aaa68647f3", + "examples/vue/README.md": "449f147a8d1e88c3087cf41bbd646ff312649028f1cc672e45cea70c37d9d103", + "examples/vue/package-lock.json": "ea2c616c264d449e601236bf5806f486dc5a403cc68d36faf49a28dec4b56cf1", + "examples/vue/package.json": "deec17c348cbb28c6e635a03bdb4e4dd4afc37322a82285752159eeaa38ad17a" + }, + "visualReview": { + "ok": true, + "files": [ + "component-invoice-1.png", + "report-1.png", + "report-2.png", + "report-3.png", + "nextjs/invoice.png" + ], + "findings": "No clipping, missing content or pagination regression in the inspected invoice and three-page report previews." + }, + "scope": "Public npm package, locked dependencies, actual Chrome PDF downloads and source edits, Next.js standalone HTTP checks. CI separately checks Firefox, WebKit and additional Node/OS combinations. No universal browser, CSS or conformance claim." +} From a947db8cd40212ac565652fdc1564121a6a73f21 Mon Sep 17 00:00:00 2001 From: krflol Date: Tue, 20 Oct 2026 16:56:46 -0500 Subject: [PATCH 3/3] Replace starter lettermark favicons with a print mark --- examples/browser/index.html | 2 +- examples/browser/public/favicon.svg | 8 +++++++- examples/react/index.html | 2 +- examples/react/public/favicon.svg | 8 +++++++- examples/vue/index.html | 2 +- examples/vue/public/favicon.svg | 8 +++++++- 6 files changed, 24 insertions(+), 6 deletions(-) diff --git a/examples/browser/index.html b/examples/browser/index.html index 7b41962..0a084df 100644 --- a/examples/browser/index.html +++ b/examples/browser/index.html @@ -5,7 +5,7 @@ Browser PDF starter · Fullbleed - +