diff --git a/plugin-designs/my-upg-100-list/poster-wall.vue b/plugin-designs/my-upg-100-list/poster-wall.vue index 66b565cd6..f9f73a1a7 100644 --- a/plugin-designs/my-upg-100-list/poster-wall.vue +++ b/plugin-designs/my-upg-100-list/poster-wall.vue @@ -274,38 +274,69 @@ const { scrimRef, scrimStyle } = useViewportScrim(exportState) // all stay — printPoster below still uses posterUrl(), and all three print paths still // share the exportState HUD for progress/errors. -// ─── SHARED PRINT-WINDOW MECHANISM ──────────────────────────────────────────── +// ─── SHARED PRINT MECHANISM ─────────────────────────────────────────────────── // ONE mechanism, reused by all three "Print …" actions (poster / list / card -// deck) rather than three ad-hoc window.opens. Extracted from the poster path, -// which already carries the load-bearing fix documented below. -// -// ★ NO `noopener` HERE, AND THAT IS THE WHOLE FIX (2026-09-23). `noopener` makes window.open() -// return null BY SPECIFICATION — severing the opener relationship is exactly what the flag does, -// so there is no handle to hand back. With it, `w` was always null, the document.write preview -// below was UNREACHABLE FOR EVERY USER, and every click fell through to a raw-content fallback. -// MEASURED in a default (non-headless-flagged) browser: window.open('','_blank','noopener') -> null, -// the identical call without the flag -> a usable handle. I first attributed the null to this -// headless box; it is not environmental, it is what the flag means. -// The window we write into carries same-origin markup we generate ourselves, so the opener -// reference it regains is our own — there is nothing hostile for `noopener` to protect against here. +// deck) rather than three ad-hoc implementations. // // `bodyHtml` is the FULL document string (caller owns
/' + + // A SQUARE on a PORTRAIT sheet must fit by its SHORTER axis. The old print rule + // (`max-height:none;width:100%`) scaled the square to the full page WIDTH, which on portrait + // letter is taller than the printable height -> the poster overflowed onto a second page. + // Constrain BOTH axes and let object-fit letterbox it inside the sheet instead. + '@media print{body{height:auto}' + + 'img{width:auto;max-width:100%;max-height:100%;object-fit:contain}}' + '