fix(web): gate PDF print-ready handshake on a usable content size (#4458)#4545
fix(web): gate PDF print-ready handshake on a usable content size (#4458)#4545YOMXXX wants to merge 2 commits into
Conversation
…xu-io#4458) One blank-PDF path in nexu-io#4458: the in-iframe print-ready handshake reported the artifact's content size after a fixed two animation frames, which can fire before a heavier artifact has finished layout (size still 0). With no usable __odPrintSize, the desktop bridge's inferPageSize falls back to measuring the wrapper viewport — which, per its own docs, blanks artifacts whose visible content sits below the fold. - Add reportPrintSizeWhenStable(): poll animation frames until the measured size is usable (positive, finite), then report it; bounded so a genuinely empty artifact reports best-effort instead of hanging the handshake. - Add isUsablePrintSize() and use it in the parent readiness cache's size validation. Both are injected via toString() and unit-tested directly (real-logic behavior assertions, not just string-presence). Scoped to this one root-cause path. A separate blank-page mode where the reported size is valid but printToPDF captures before the iframe composites needs a real-Electron repro to confirm and is left for follow-up.
|
Hi @YOMXXX! 👋 Thanks for tightening the PDF export readiness path here — the write-up around the zero-size fallback path is clear, and it helps that you called out the remaining end-to-end limitation explicitly. I've routed this PR into review and will keep the PR metadata / follow-up in sync while it moves through the queue. |
|
🧪 This PR has changes that need a manual QA pass before merge — please hold off self-merging for now; we'll loop QA in once it's merge-ready. |
…eady cache (nexu-io#4458) nexu-io#4458 notes the existing coverage only proves the print-ready scripts are injected, never that they behave. Extract the real parent-cache <script> from a live export, run it against a minimal fake window, and drive it with postMessage events to assert the runtime size gate: a usable size is cached for inferPageSize(), while a zero/non-finite size or a wrong-nonce message is rejected so it cannot blank or poison the PDF page.
mrcfps
left a comment
There was a problem hiding this comment.
@YOMXXX I reviewed the updated print-ready handshake and the added runtime coverage in apps/web/src/runtime/exports.ts / apps/web/tests/runtime/exports.test.ts. The bounded polling plus parent-cache size gate line up with inferPageSize()'s existing contract, and I did not find any changed-range issues that look merge-blocking. Thanks for tightening this PDF-export path and for documenting the remaining limitation clearly. 🙏
Refs #4458
Why
#4458 reports PDF export still producing a one-page, all-white PDF even though the artifact visibly rendered in Open Design. @lefarcen's triage pointed at the desktop
printToPDFreadiness path, and reading it confirmed one concrete, non-speculative blank path:For the sandboxed-preview export, the desktop bridge prints a wrapper document whose
<body>isoverflow:hiddenaround asandbox="allow-scripts"iframe it cannot read. It therefore depends on the in-iframe print-ready handshake reporting the artifact's content size (__odPrintSize). That handshake measured the size after a fixed two animation frames — which can fire before a heavier artifact has finished layout, so it reportswidth/height = 0. The parent cache only stores a positive-finite size, so__odPrintSizestays null, andinferPageSizethen falls back to measuring the wrapper viewport — which, per that function's own docstring, "blanks … artifacts whose visible content sits below the fold".What users will see
A heavier or slower-laying-out artifact that previously exported as a blank page is more likely to export with its real content, because the desktop PDF bridge now receives the artifact's true content size instead of falling back to the wrapper viewport.
Surface area
apps/web/src/runtime/exports.ts. No UI/CLI/API/i18n surface.Screenshots
N/A — no UI. (Visual confirmation requires a real desktop PDF export; see Bug fix verification.)
Bug fix verification
apps/web/tests/runtime/exports.test.tsreportPrintSizeWhenStable (#4458)— reports only once content has a usable (non-zero) size by polling animation frames; reports best-effort when the frame budget is exhausted (genuinely empty content).isUsablePrintSize (#4458)— rejects zero / negative / non-finite / non-number dimensions.reportPrintSizeWhenStableand the cache gates onisUsablePrintSize.mainand green on this branch? yes — both new functions are missing onmain(import isundefined, tests throw); green after the fix. These are real-logic behavior assertions on the exact functions injected into the handshake (viatoString()), not string-presence checks.__odPrintSizeis valid but Electron'sprintToPDFcaptures before the iframe composites a paint; distinguishing/validating that needs a real-Electron repro with an artifact that reproduces the blank (the reporter noted it was hard to recreate), which this PR does not attempt. HenceRefsrather thanFixes— leaving the issue open for end-to-end confirmation.Validation
pnpm --filter @open-design/web exec vitest run tests/runtime/exports.test.ts→ 55 passed (4 new/updated specs RED→GREEN).tsc --noEmitclean forexports.ts.pnpm guard→ no new violations.