diff --git a/.github/workflows/browser-starter.yml b/.github/workflows/browser-starter.yml index 79caf29..baff938 100644 --- a/.github/workflows/browser-starter.yml +++ b/.github/workflows/browser-starter.yml @@ -3,9 +3,9 @@ name: Browser starter on: push: branches: [main] - paths: ['examples/browser/**', 'tools/check-browser-starter.py', 'tools/browser-requirements.txt', '.github/workflows/browser-starter.yml'] + paths: ['examples/browser/**', 'tools/check-browser-starter.py', 'tools/browser-requirements.txt', 'tools/starter_standard_fonts.py', '.github/workflows/browser-starter.yml'] pull_request: - paths: ['examples/browser/**', 'tools/check-browser-starter.py', 'tools/browser-requirements.txt', '.github/workflows/browser-starter.yml'] + paths: ['examples/browser/**', 'tools/check-browser-starter.py', 'tools/browser-requirements.txt', 'tools/starter_standard_fonts.py', '.github/workflows/browser-starter.yml'] workflow_dispatch: permissions: diff --git a/.github/workflows/react-starter.yml b/.github/workflows/react-starter.yml index 94ad094..b63b6af 100644 --- a/.github/workflows/react-starter.yml +++ b/.github/workflows/react-starter.yml @@ -3,9 +3,9 @@ name: React starter on: push: branches: [main] - paths: ['examples/react/**', 'tools/check-react-starter.py', 'tools/prepare-react-check.mjs', 'tools/browser-requirements.txt', '.github/workflows/react-starter.yml'] + paths: ['examples/react/**', 'tools/check-react-starter.py', 'tools/prepare-react-check.mjs', 'tools/browser-requirements.txt', 'tools/starter_standard_fonts.py', '.github/workflows/react-starter.yml'] pull_request: - paths: ['examples/react/**', 'tools/check-react-starter.py', 'tools/prepare-react-check.mjs', 'tools/browser-requirements.txt', '.github/workflows/react-starter.yml'] + paths: ['examples/react/**', 'tools/check-react-starter.py', 'tools/prepare-react-check.mjs', 'tools/browser-requirements.txt', 'tools/starter_standard_fonts.py', '.github/workflows/react-starter.yml'] workflow_dispatch: permissions: diff --git a/.github/workflows/vue-starter.yml b/.github/workflows/vue-starter.yml index 188a61b..dfe05ae 100644 --- a/.github/workflows/vue-starter.yml +++ b/.github/workflows/vue-starter.yml @@ -3,9 +3,9 @@ name: Vue starter on: push: branches: [main] - paths: ['examples/vue/**', 'tools/check-vue-starter.py', 'tools/prepare-vue-check.mjs', 'tools/browser-requirements.txt', '.github/workflows/vue-starter.yml'] + paths: ['examples/vue/**', 'tools/check-vue-starter.py', 'tools/prepare-vue-check.mjs', 'tools/browser-requirements.txt', 'tools/starter_standard_fonts.py', '.github/workflows/vue-starter.yml'] pull_request: - paths: ['examples/vue/**', 'tools/check-vue-starter.py', 'tools/prepare-vue-check.mjs', 'tools/browser-requirements.txt', '.github/workflows/vue-starter.yml'] + paths: ['examples/vue/**', 'tools/check-vue-starter.py', 'tools/prepare-vue-check.mjs', 'tools/browser-requirements.txt', 'tools/starter_standard_fonts.py', '.github/workflows/vue-starter.yml'] workflow_dispatch: permissions: diff --git a/examples/browser/README.md b/examples/browser/README.md index 711dd04..6b1ad18 100644 --- a/examples/browser/README.md +++ b/examples/browser/README.md @@ -58,8 +58,8 @@ explains CSP, custom assets, structured errors, and browser worker lifecycle lim ## Versions and verification -The lockfile pins Fullbleed npm 0.3.1 / engine 2.5.10 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.3.1). +The lockfile pins Fullbleed npm 0.3.2 / engine 2.5.11 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.3.2). 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 9861509..30d1398 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.3.1" + "fullbleed": "0.3.2" }, "devDependencies": { "vite": "8.3.2" @@ -357,9 +357,9 @@ } }, "node_modules/fullbleed": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.3.1.tgz", - "integrity": "sha512-95CVTnYKduXD5KcEj9MLfzPsqofahZB2BsJ62Dfg0tOTlPFEHT8vx/T3f8xp4VQYkihOPQdMDiWQzYNHJkLpWA==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.3.2.tgz", + "integrity": "sha512-bzbTUn6Hcxu0YZoP38wkrJ/nZ33+rQfZq5itdpQUl7lIO9mR1m9LC5n/2KLxjj+7dAb8IhpYoVTSmbuhQyA5Vg==", "license": "MIT", "dependencies": { "@bjorn3/browser_wasi_shim": "0.4.2" diff --git a/examples/browser/package.json b/examples/browser/package.json index 268bb13..f8c317a 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.3.1" }, + "dependencies": { "fullbleed": "0.3.2" }, "devDependencies": { "vite": "8.3.2" } } diff --git a/examples/nextjs/README.md b/examples/nextjs/README.md index 1e4e607..06dceed 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.3.1 -(engine 2.5.10) 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.3.2 +(engine 2.5.11) 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 3abb592..2a60492 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.3.1", + "fullbleed": "0.3.2", "next": "16.3.8", "react": "19.3.0", "react-dom": "19.3.0" @@ -787,9 +787,9 @@ } }, "node_modules/fullbleed": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.3.1.tgz", - "integrity": "sha512-95CVTnYKduXD5KcEj9MLfzPsqofahZB2BsJ62Dfg0tOTlPFEHT8vx/T3f8xp4VQYkihOPQdMDiWQzYNHJkLpWA==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.3.2.tgz", + "integrity": "sha512-bzbTUn6Hcxu0YZoP38wkrJ/nZ33+rQfZq5itdpQUl7lIO9mR1m9LC5n/2KLxjj+7dAb8IhpYoVTSmbuhQyA5Vg==", "license": "MIT", "dependencies": { "@bjorn3/browser_wasi_shim": "0.4.2" diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json index 039412e..9b8f154 100644 --- a/examples/nextjs/package.json +++ b/examples/nextjs/package.json @@ -16,7 +16,7 @@ "verify": "node tools/verify.mjs" }, "dependencies": { - "fullbleed": "0.3.1", + "fullbleed": "0.3.2", "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 8600205..c9ce011 100644 --- a/examples/react/README.md +++ b/examples/react/README.md @@ -35,7 +35,7 @@ renders supplied static HTML/CSS rather than capturing the React page. ## Reuse the hook Copy `src/usePdfPreview.ts` into your client application, install -`fullbleed@0.3.1`, and copy the runtime with +`fullbleed@0.3.2`, 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: @@ -94,7 +94,7 @@ WebAssembly, and Web Crypto are required. ## Versions and verification -The lockfile pins Fullbleed 0.3.1 / engine 2.5.10, React 19.3.0, TypeScript 7.0.2, +The lockfile pins Fullbleed 0.3.2 / engine 2.5.11, 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 e44d28c..a624217 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.3.1", + "fullbleed": "0.3.2", "react": "19.3.0", "react-dom": "19.3.0" }, @@ -760,9 +760,9 @@ } }, "node_modules/fullbleed": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.3.1.tgz", - "integrity": "sha512-95CVTnYKduXD5KcEj9MLfzPsqofahZB2BsJ62Dfg0tOTlPFEHT8vx/T3f8xp4VQYkihOPQdMDiWQzYNHJkLpWA==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.3.2.tgz", + "integrity": "sha512-bzbTUn6Hcxu0YZoP38wkrJ/nZ33+rQfZq5itdpQUl7lIO9mR1m9LC5n/2KLxjj+7dAb8IhpYoVTSmbuhQyA5Vg==", "license": "MIT", "dependencies": { "@bjorn3/browser_wasi_shim": "0.4.2" diff --git a/examples/react/package.json b/examples/react/package.json index 08ae981..3c1f80f 100644 --- a/examples/react/package.json +++ b/examples/react/package.json @@ -12,7 +12,7 @@ "preview": "vite preview --host 127.0.0.1" }, "dependencies": { - "fullbleed": "0.3.1", + "fullbleed": "0.3.2", "react": "19.3.0", "react-dom": "19.3.0" }, diff --git a/examples/vue/README.md b/examples/vue/README.md index 6ba94ba..da87e3b 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.3.1`, and copy the runtime with +`fullbleed@0.3.2`, 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.3.1 / engine 2.5.10, Vue 3.5.43, TypeScript 6.0.3, +The lockfile pins Fullbleed 0.3.2 / engine 2.5.11, 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 7b21840..1a3bbeb 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.3.1", + "fullbleed": "0.3.2", "vue": "3.5.43" }, "devDependencies": { @@ -605,9 +605,9 @@ } }, "node_modules/fullbleed": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.3.1.tgz", - "integrity": "sha512-95CVTnYKduXD5KcEj9MLfzPsqofahZB2BsJ62Dfg0tOTlPFEHT8vx/T3f8xp4VQYkihOPQdMDiWQzYNHJkLpWA==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.3.2.tgz", + "integrity": "sha512-bzbTUn6Hcxu0YZoP38wkrJ/nZ33+rQfZq5itdpQUl7lIO9mR1m9LC5n/2KLxjj+7dAb8IhpYoVTSmbuhQyA5Vg==", "license": "MIT", "dependencies": { "@bjorn3/browser_wasi_shim": "0.4.2" diff --git a/examples/vue/package.json b/examples/vue/package.json index 45fbe3a..389f6ad 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.3.1", + "fullbleed": "0.3.2", "vue": "3.5.43" }, "devDependencies": { diff --git a/tools/check-browser-starter.py b/tools/check-browser-starter.py index 105f784..89f06dd 100644 --- a/tools/check-browser-starter.py +++ b/tools/check-browser-starter.py @@ -11,6 +11,7 @@ from playwright.sync_api import sync_playwright, expect from pypdf import PdfReader +from starter_standard_fonts import verify_edited_preview parser=argparse.ArgumentParser(description=__doc__) parser.add_argument('--dist',type=Path,required=True) @@ -88,11 +89,12 @@ def download(name,pages,text): check('customized bytes differ from the default invoice',documents[-1]['sha256']!=documents[0]['sha256']) page.locator('#template-editor summary').click() page.locator('#html-source').fill('

Edited template

{{customer}} / {{reference}}

') - page.locator('#css-source').fill('@page { size: A5; margin: 15mm } h1 { color: {{ink}}; font-size: 24pt }') + page.locator('#css-source').fill('@page { size: A5; margin: 15mm } body { font-family: Helvetica } h1 { color: {{ink}}; font-size: 24pt }') page.locator('#render-source').click();ready() path=download('edited',1,['Edited template','Birch & ','TEST-2042']) check('edited print CSS changes the PDF page size',abs(float(PdfReader(path).pages[0].mediabox.width)-419.52756)<1) check('document script does not run on the page',page.evaluate('window.documentScriptExecuted !== true')) + verify_edited_preview(page, path, out, check) page.screenshot(path=str(out/'edited-template.png'),full_page=True) page.locator('#kind').select_option('report');page.locator('#generate').click();ready() download('report',3,['COMMON','1,240','420,000.00']) diff --git a/tools/check-react-starter.py b/tools/check-react-starter.py index 54f47be..68a7cd1 100644 --- a/tools/check-react-starter.py +++ b/tools/check-react-starter.py @@ -12,6 +12,7 @@ from playwright.sync_api import sync_playwright, expect from pypdf import PdfReader +from starter_standard_fonts import verify_edited_preview parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--dist', type=Path, required=True) @@ -174,11 +175,12 @@ def download(name, count, text): page.locator('#template-editor summary').click() page.locator('#html-source').fill('

React template

{{customer}} / {{reference}}

') - page.locator('#css-source').fill('@page { size: A5; margin: 15mm } h1 { color: {{ink}}; font-size: 24pt }') + page.locator('#css-source').fill('@page { size: A5; margin: 15mm } body { font-family: Helvetica } h1 { color: {{ink}}; font-size: 24pt }') ready() edited = download('edited-template', 1, ['React template', 'Birch & ', 'REACT-2042']) check('edited print CSS changes the page size', abs(float(PdfReader(edited).pages[0].mediabox.width) - 419.52756) < 1) check('document scripts are not executed in the app', page.evaluate('window.documentScriptExecuted !== true')) + verify_edited_preview(page, edited, out, check) page.screenshot(path=str(out / 'edited-template.png'), full_page=True) page.locator('#kind').select_option('report') ready() diff --git a/tools/check-vue-starter.py b/tools/check-vue-starter.py index 07248e0..577ffe0 100644 --- a/tools/check-vue-starter.py +++ b/tools/check-vue-starter.py @@ -12,6 +12,7 @@ from playwright.sync_api import sync_playwright, expect from pypdf import PdfReader +from starter_standard_fonts import verify_edited_preview parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--dist', type=Path, required=True) @@ -184,11 +185,12 @@ def previews_match(name, count): page.locator('#template-editor summary').click() page.locator('#html-source').fill('

Vue template

{{customer}} / {{reference}}

') - page.locator('#css-source').fill('@page { size: A5; margin: 15mm } h1 { color: {{ink}}; font-size: 24pt }') + page.locator('#css-source').fill('@page { size: A5; margin: 15mm } body { font-family: Helvetica } h1 { color: {{ink}}; font-size: 24pt }') ready() edited = download('edited-template', 1, ['Vue template', 'Birch & ', 'VUE-2042']) check('edited print CSS changes the page size', abs(float(PdfReader(edited).pages[0].mediabox.width) - 419.52756) < 1) check('document scripts are not executed in the app', page.evaluate('window.documentScriptExecuted !== true')) + verify_edited_preview(page, edited, out, check) page.screenshot(path=str(out / 'edited-template.png'), full_page=True) page.locator('#kind').select_option('report') ready() diff --git a/tools/starter_standard_fonts.py b/tools/starter_standard_fonts.py new file mode 100644 index 0000000..b56e0dc --- /dev/null +++ b/tools/starter_standard_fonts.py @@ -0,0 +1,37 @@ +"""Check that a pasted Helvetica template has visible browser preview text.""" +import base64 +import hashlib +import json + +from playwright.sync_api import expect +from pypdf import PdfReader + + +def verify_edited_preview(page, pdf_path, out, check): + pdf = PdfReader(pdf_path) + fonts = [item.get_object() for item in pdf.pages[0]['/Resources']['/Font'].values()] + names = sorted(str(font.get('/BaseFont')) for font in fonts) + check('edited template uses unembedded standard Helvetica faces', + bool(fonts) and all(name in ['/Helvetica', '/Helvetica-Bold'] for name in names) + and all('/FontDescriptor' not in font for font in fonts)) + image = page.locator('#preview img') + expect(image).to_have_count(1) + result = image.evaluate('''async image => { + await image.decode(); + const canvas = document.createElement('canvas'); + canvas.width = image.naturalWidth; canvas.height = image.naturalHeight; + const context = canvas.getContext('2d'); context.drawImage(image, 0, 0); + const pixels = context.getImageData(0, 0, canvas.width, canvas.height).data; + let ink = 0; + for (let i = 0; i < pixels.length; i += 4) + if (pixels[i + 3] > 0 && Math.min(pixels[i], pixels[i + 1], pixels[i + 2]) < 245) ink++; + return {width: canvas.width, height: canvas.height, inkPixels: ink, + png: canvas.toDataURL('image/png').split(',')[1]}; + }''') + png = base64.b64decode(result.pop('png')) + (out / 'edited-preview-decoded.png').write_bytes(png) + result.update(fonts=names, pdfSha256=hashlib.sha256(pdf_path.read_bytes()).hexdigest(), + decodedPreviewSha256=hashlib.sha256(png).hexdigest()) + (out / 'edited-preview-verification.json').write_text( + json.dumps(result, indent=2) + '\n', encoding='utf-8', newline='\n') + check('edited standard-font preview contains visible text', result['inkPixels'] > 500) diff --git a/verification/starters-032.json b/verification/starters-032.json new file mode 100644 index 0000000..d8e25eb --- /dev/null +++ b/verification/starters-032.json @@ -0,0 +1,583 @@ +{ + "ok": true, + "checked_at": "2026-10-07T03:56:27.779456+00:00", + "package_version": "0.3.2", + "engine_version": "2.5.11", + "npm_integrity": "sha512-bzbTUn6Hcxu0YZoP38wkrJ/nZ33+rQfZq5itdpQUl7lIO9mR1m9LC5n/2KLxjj+7dAb8IhpYoVTSmbuhQyA5Vg==", + "source_files": { + "examples/browser/.gitignore": "c023fec2bf12476cb47709e859f1fbb785aeb1e03281e52141e0bee1ee7dad72", + "examples/browser/LICENSE": "36a2115938f6e68cf0d87eccff7a6a3e57c17017e909cbd640d51aff1fa0c5a9", + "examples/browser/README.md": "6b5bec007619b8d1776dc668b4f16d2204e4ad9f92dd9b7e38352e534f1bcd13", + "examples/browser/index.html": "79baecb0b24716e78799893fc0076d88b2c026354f9f31acdf799c8f305c954b", + "examples/browser/package-lock.json": "b1f81db73d867f963443fbc2415f352297ee4d9c962f0ba601d9cdc3457e4bd5", + "examples/browser/package.json": "6dbc4b39b8daa889493125b5d1c091941dd8f994a26274153ffc4298e532cd18", + "examples/browser/public/favicon.svg": "3eed6dcf1013e48bc64886fed4cf815c609ed06b404742bdacdc44c1df8d3118", + "examples/browser/src/invoice.css": "0b310ce0f2964fd6b9b68e4afafa9307b4ff8c6b2f1fc753367dc755620ce427", + "examples/browser/src/invoice.html": "1f065ad6694021649e61b3f666cba6dc01aacdcbd8e80e97425b383e60d7d2fe", + "examples/browser/src/main.js": "10312b7064b0b2c19d97194b3953f639cc1f6842c35acf85f5e9543fa9c1d27f", + "examples/browser/src/report.css": "519591ae75c239ec602324f008dd99108ebeae0b9306df7215fa5f0442905473", + "examples/browser/src/report.html": "46b0e975d43914c8b4492218feae09ea0ad684091a9f9ee3b18f66a87202e521", + "examples/browser/src/style.css": "5005045f66a84a038a0f7a0f7e15b8b5e9eac2489f9717d4a581fec5075bf6f4", + "examples/browser/vite.config.js": "ec725922705a3c5ba2df191a564c54c0cac6e8997ecd3d00b8a42c647bec8e74", + "examples/nextjs/.gitignore": "b4abc38d76926640e3f2cbe3bf25c8f5aec6cc43dd978c48d3ad5cc20d964f66", + "examples/nextjs/LICENSE": "36a2115938f6e68cf0d87eccff7a6a3e57c17017e909cbd640d51aff1fa0c5a9", + "examples/nextjs/README.md": "96de8ae1580d61c0c34274c3fafe70a81338aa2bd927d22900874c367ae9158c", + "examples/nextjs/app/api/invoices/[id]/route.js": "d6348ce64b44feac7010e21fb0f55723e0858c7867222ae831c45d3440a3b052", + "examples/nextjs/app/globals.css": "02e0b545a14eaf15e6d3d8fc8c0e8c0f1c5ce5c1b8dc461b4343d330f291233e", + "examples/nextjs/app/layout.js": "2df89bff0d2f002b432b3fdb73fd64edf59c57641d9ea6df3f0051cf2329cf27", + "examples/nextjs/app/page.js": "24c7996f8172992f925cdb1e47455d0095397d0832f747c90ecb409f2a1d460c", + "examples/nextjs/lib/invoice.mjs": "fd87dd082fa19659ec572d97255c1902e1e728e43ebbc3e0ade9891fa7736cf2", + "examples/nextjs/next.config.mjs": "e93613419f5aaa6264f0405e5afbb6f5eea4c956e42e7ebf30a6c85a4d9d89b3", + "examples/nextjs/package-lock.json": "f5d9b10c7546f8df1c08b0cdb489647289f91b1c89d065c3f1a133478d5fcfbf", + "examples/nextjs/package.json": "e31ff035bd4d340ca73a5ee95319ee85ec4ae1d3651a6193bf2a6f0174a39e9c", + "examples/nextjs/public/invoice.png": "72420749ccc3deaa8025daf7baffcdf79be716b6e8e93ed605effebed01e86c7", + "examples/nextjs/templates/invoice.css": "cda22b39ab1f7a1eeac7cd76d162b05cd3568c2430c099123ba30a2fbd58a296", + "examples/nextjs/templates/invoice.html": "d9a5b1950ddab61f08628b0fcc773cb36f05d9401bb3d87273bca02452b1835e", + "examples/nextjs/tools/copy-public.mjs": "25005e2612d90ed5127f268144c45bcd259ceb1e68c832723549e57a06b8d3dd", + "examples/nextjs/tools/render-preview.mjs": "0e5f1c0e49e2ec35a77eec460d735a1228ae82a30b86a7eb687af4056c23522b", + "examples/nextjs/tools/start.mjs": "4c185a0801ca4f89a30a0c1e75710f1c22c84463d29477900162b05a8bc52eab", + "examples/nextjs/tools/verify.mjs": "54623914ff28a47ea63a553d063ac50a206df0424270dacb3f3a99054982a888", + "examples/react/.gitignore": "25d896ffa2be7ed008ad87c3956a0a88f54167d04a7cef5cf774d6989d3b5a7b", + "examples/react/LICENSE": "36a2115938f6e68cf0d87eccff7a6a3e57c17017e909cbd640d51aff1fa0c5a9", + "examples/react/README.md": "9b20e8bd9a23d22e4733012b94cc8ab645fa08cb70327aa2ab1027e0b85a4081", + "examples/react/index.html": "cadf48a3476088b2efbe9012bb4b2d21fa24f1dd3e99f974f444c88fbf1ee165", + "examples/react/package-lock.json": "c28b7d70a675e27cfcf0c68c06ca1ecc7f640f43f35261f20274f02e48947ce3", + "examples/react/package.json": "ef11b5673e83c8c9222b7b7b6f2ea7c9ca7f0c99e809eb675d7c08cc6173dd14", + "examples/react/public/favicon.svg": "3eed6dcf1013e48bc64886fed4cf815c609ed06b404742bdacdc44c1df8d3118", + "examples/react/src/App.tsx": "5dbcd45d570a3e4d69f12237d252e30afe71f52cb8411c3ffe094dc72f559b9f", + "examples/react/src/invoice.css": "0b310ce0f2964fd6b9b68e4afafa9307b4ff8c6b2f1fc753367dc755620ce427", + "examples/react/src/invoice.html": "1f065ad6694021649e61b3f666cba6dc01aacdcbd8e80e97425b383e60d7d2fe", + "examples/react/src/main.tsx": "f8f523ef5fc0b1e4095830466e0caa648c01f7b42036694b800c25d43d5e2c84", + "examples/react/src/report.css": "519591ae75c239ec602324f008dd99108ebeae0b9306df7215fa5f0442905473", + "examples/react/src/report.html": "46b0e975d43914c8b4492218feae09ea0ad684091a9f9ee3b18f66a87202e521", + "examples/react/src/style.css": "940621b782b9e39fc48f5a257c119763e11cedd158fb570a82f483ea20aa9951", + "examples/react/src/usePdfPreview.ts": "587e8702bd32f42abe2720f9d29dcc68e6d75aba8cb3ceacbdfcf2b583f378cc", + "examples/react/tsconfig.json": "382c3c4a68901c016c3038c9653f1adaf145738c68de40a34821181ead2d7289", + "examples/react/vite.config.js": "4b74d0189e7f58a4c75d168367e9b8a53000555dd3de594c497f70fb5e02fff0", + "examples/vue/.gitignore": "c023fec2bf12476cb47709e859f1fbb785aeb1e03281e52141e0bee1ee7dad72", + "examples/vue/LICENSE": "36a2115938f6e68cf0d87eccff7a6a3e57c17017e909cbd640d51aff1fa0c5a9", + "examples/vue/README.md": "b157b91e6a93417c279b565f2b5ecb96c8299c3e0524b7531704348ba0bbe4e8", + "examples/vue/index.html": "30d46eb1b5147bd3d9c580e7adaed5b5202f9b97ab598e189efda7c3d2d405be", + "examples/vue/package-lock.json": "72269d20bb6fabb34779eac61bea9d2b9eaadf5afff09e5e968053c2418fe3c4", + "examples/vue/package.json": "262a2d42ec0067ff76d4c80398b01de7c2fc84c455f66fd7bb62517b30669c90", + "examples/vue/public/favicon.svg": "3eed6dcf1013e48bc64886fed4cf815c609ed06b404742bdacdc44c1df8d3118", + "examples/vue/src/App.vue": "0afa15196ccedd0b0f91293cc5e362799149a2752c59deaa377f4533825ea2af", + "examples/vue/src/DocumentEditor.vue": "a3f3f8096d194e1eed6cd0cf2fb0a0606a04d40823ff7b794b9b1a0dbfd8c28a", + "examples/vue/src/invoice.css": "0b310ce0f2964fd6b9b68e4afafa9307b4ff8c6b2f1fc753367dc755620ce427", + "examples/vue/src/invoice.html": "1f065ad6694021649e61b3f666cba6dc01aacdcbd8e80e97425b383e60d7d2fe", + "examples/vue/src/main.ts": "d6d44427a8c817da9a1a8f6809a0d6e1fff87f73ee695442f174fca32db8e6f6", + "examples/vue/src/report.css": "519591ae75c239ec602324f008dd99108ebeae0b9306df7215fa5f0442905473", + "examples/vue/src/report.html": "46b0e975d43914c8b4492218feae09ea0ad684091a9f9ee3b18f66a87202e521", + "examples/vue/src/style.css": "940621b782b9e39fc48f5a257c119763e11cedd158fb570a82f483ea20aa9951", + "examples/vue/src/usePdfPreview.ts": "cc0c57d0d8733ca4bfe257ce5fc08acfb78693ca63a4298c169dc6608a89c893", + "examples/vue/tsconfig.json": "0d0065f1a795408837fd69a8ec9e5c5e7c6eb23b304d9710c6dbfaa96573a453", + "examples/vue/vite.config.js": "dfcc6c065fb26ada03802def672108735f4c2c35e0f30d11965ddfa796b8a0c0" + }, + "browsers": [ + { + "starter": "browser", + "browser_engine": "chrome", + "browser": "154.0.8037.98", + "checks": [ + "runtime resolves beneath a nested deployment path", + "bundled UI font loads", + "invoice: download has the expected pages and text", + "1440px: no horizontal overflow", + "390px: no horizontal overflow", + "320px: no horizontal overflow", + "color-only: download has the expected pages and text", + "changing only ink color changes the rendered PDF", + "changed inputs clear the previous preview", + "customized: download has the expected pages and text", + "customized bytes differ from the default invoice", + "edited: download has the expected pages and text", + "edited print CSS changes the PDF page size", + "document script does not run on the page", + "edited template uses unembedded standard Helvetica faces", + "edited standard-font preview contains visible text", + "report: download has the expected pages and text", + "multi-page preview displays all report pages", + "each design preserves its template edits", + "reset restores only the selected template", + "invalid input displays an error without a stale download", + "cancellation restores controls without a stale download", + "recovered: download has the expected pages and text", + "no document uploads or cross-origin runtime requests", + "no uncaught page errors" + ], + "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": "f5c6c1927fc6c636cee592a3ebc7bb7b3e91dd645ab34a9370590571506d278b" + }, + { + "name": "report", + "filename": "community-report.pdf", + "pages": 3, + "sha256": "139df89824e6a73c13ff62f1237bb8eb9f71e790dcbd051b1bb713ed2bc47802" + }, + { + "name": "recovered", + "filename": "invoice-TEST-2042.pdf", + "pages": 1, + "sha256": "31ff314b44a6f13534cc383e0cf24929f0695c1dd3d84769dedf8828c6486a93" + } + ], + "edited_preview": { + "width": 559, + "height": 794, + "inkPixels": 5455, + "fonts": [ + "/Helvetica", + "/Helvetica-Bold" + ], + "pdfSha256": "f5c6c1927fc6c636cee592a3ebc7bb7b3e91dd645ab34a9370590571506d278b", + "decodedPreviewSha256": "d83c1c009ce8df726dd205d1efaf9f4f623192b8d7386adad0612901664924bd" + } + }, + { + "starter": "react", + "browser_engine": "chrome", + "browser": "154.0.8037.98", + "checks": [ + "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", + "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" + ], + "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-REACT-2042.pdf", + "sha256": "fccdad8d56abef7206e5efefc716af2c44c717c0e1c3619827c6d27931ed76be" + }, + { + "name": "edited-template", + "pages": 1, + "filename": "invoice-REACT-2042.pdf", + "sha256": "f3d3f491c3c38a71f873f2a46de9fd6434ade3d938eb1a3d15a40ddc9d414c52" + }, + { + "name": "report", + "pages": 3, + "filename": "community-report.pdf", + "sha256": "139df89824e6a73c13ff62f1237bb8eb9f71e790dcbd051b1bb713ed2bc47802" + }, + { + "name": "recovered", + "pages": 1, + "filename": "invoice-REACT-2042.pdf", + "sha256": "fccdad8d56abef7206e5efefc716af2c44c717c0e1c3619827c6d27931ed76be" + }, + { + "name": "remounted", + "pages": 1, + "filename": "invoice-NS-1042.pdf", + "sha256": "3078d155f30536f5ad721f7be3c241cd78661ae41249131fc0fd65f37f23e408" + } + ], + "edited_preview": { + "width": 559, + "height": 794, + "inkPixels": 5323, + "fonts": [ + "/Helvetica", + "/Helvetica-Bold" + ], + "pdfSha256": "f3d3f491c3c38a71f873f2a46de9fd6434ade3d938eb1a3d15a40ddc9d414c52", + "decodedPreviewSha256": "ddab1397eb54c55fdc300b140c91ceb169c18c2b2128f3648f50eeefd1156db3" + } + }, + { + "starter": "vue", + "browser_engine": "chrome", + "browser": "154.0.8037.98", + "checks": [ + "invoice: real PDF has expected pages and text", + "invoice matches the installed Node package", + "invoice: displayed previews match 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", + "report: real PDF has expected pages and text", + "report matches the installed Node package", + "all report pages have previews", + "report: displayed previews match the installed Node package", + "each design preserves its template edits", + "invalid input cannot leave an old download", + "cancelling a queued preview prevents its worker from starting", + "runtime load failure clears previews and releases the worker", + "outage-recovery: real PDF has expected pages and text", + "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" + ], + "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": "e23fdd55b186015b830a970af7842a5bd19e3bdc321505ff83fa11c4991fa63c" + }, + { + "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" + } + ], + "edited_preview": { + "width": 559, + "height": 794, + "inkPixels": 4790, + "fonts": [ + "/Helvetica", + "/Helvetica-Bold" + ], + "pdfSha256": "e23fdd55b186015b830a970af7842a5bd19e3bdc321505ff83fa11c4991fa63c", + "decodedPreviewSha256": "5e7c7d8cc22b240314f2ffa6860b866e9496ddbcdf951cd5a8d5569a626e09c9" + } + } + ], + "unchanged_reviewed_031_documents": [ + "browser/invoice", + "browser/color-only", + "browser/customized", + "browser/report", + "browser/recovered", + "react/invoice", + "react/manual", + "react/latest-input", + "react/report", + "react/recovered", + "react/remounted" + ], + "public_031_negative_control": { + "ok": true, + "checkedAt": "2026-10-07T03:50:38.760124+00:00", + "expectedBlank": true, + "records": [ + { + "flavor": "browser", + "url": "https://docs.fullbleed.dev/assets/browser-demo/", + "package": "0.3.1", + "engine": "2.5.10", + "pdfSha256": "1b8c17ab56c4dbd073b4b108528d617d272c735ffd14056cac2cd5394e988cbc", + "pngSha256": "2cc00648b3ae8e45f95e8a92a750972137f2a268329c734b2f26375435a536dc", + "fonts": [ + "/Courier", + "/Courier-Bold", + "/Courier-BoldOblique", + "/Courier-Oblique", + "/Helvetica", + "/Helvetica-Bold", + "/Helvetica-BoldOblique", + "/Helvetica-Oblique", + "/Times-Bold", + "/Times-BoldItalic", + "/Times-Italic", + "/Times-Roman" + ], + "allFontsUnembedded": true, + "textLabels": 12, + "width": 559, + "height": 794, + "inkPixels": 0 + }, + { + "flavor": "react", + "url": "https://docs.fullbleed.dev/assets/react-demo/", + "package": "0.3.1", + "engine": "2.5.10", + "pdfSha256": "1b8c17ab56c4dbd073b4b108528d617d272c735ffd14056cac2cd5394e988cbc", + "pngSha256": "2cc00648b3ae8e45f95e8a92a750972137f2a268329c734b2f26375435a536dc", + "fonts": [ + "/Courier", + "/Courier-Bold", + "/Courier-BoldOblique", + "/Courier-Oblique", + "/Helvetica", + "/Helvetica-Bold", + "/Helvetica-BoldOblique", + "/Helvetica-Oblique", + "/Times-Bold", + "/Times-BoldItalic", + "/Times-Italic", + "/Times-Roman" + ], + "allFontsUnembedded": true, + "textLabels": 12, + "width": 559, + "height": 794, + "inkPixels": 0 + }, + { + "flavor": "vue", + "url": "https://docs.fullbleed.dev/assets/vue-demo/", + "package": "0.3.1", + "engine": "2.5.10", + "pdfSha256": "1b8c17ab56c4dbd073b4b108528d617d272c735ffd14056cac2cd5394e988cbc", + "pngSha256": "2cc00648b3ae8e45f95e8a92a750972137f2a268329c734b2f26375435a536dc", + "fonts": [ + "/Courier", + "/Courier-Bold", + "/Courier-BoldOblique", + "/Courier-Oblique", + "/Helvetica", + "/Helvetica-Bold", + "/Helvetica-BoldOblique", + "/Helvetica-Oblique", + "/Times-Bold", + "/Times-BoldItalic", + "/Times-Italic", + "/Times-Roman" + ], + "allFontsUnembedded": true, + "textLabels": 12, + "width": 559, + "height": 794, + "inkPixels": 0 + } + ] + }, + "new_preview_gate_rejects_public_031": { + "ok": true, + "expectedFailure": true, + "package": "0.3.1", + "checks": [ + { + "name": "edited template uses unembedded standard Helvetica faces", + "passed": true + }, + { + "name": "edited standard-font preview contains visible text", + "passed": false + } + ] + }, + "nextjs": { + "checkedAt": "2026-10-07T03:54:37.205Z", + "node": "v22.23.3", + "platform": "win32", + "next": "16.3.8", + "nodePackage": "0.3.2", + "engineVersion": "2.5.11", + "isolation": "process", + "packageLockSha256": "f5d9b10c7546f8df1c08b0cdb489647289f91b1c89d065c3f1a133478d5fcfbf", + "base": "http://127.0.0.1:3427", + "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." + }, + "scope": "Local Windows / Node 22.23.3 and Chrome checks of locked public npm 0.3.2 consumers. Standard-font preview regression confirmed against live 0.3.1 editors; no new npm release, framework upgrade, native crash fix, or PDF standards claim." +}