Render a media=print capture under emulated print media - #6139
Conversation
lukemelia
left a comment
There was a problem hiding this comment.
[Claude Code 🤖] This review traced the media-emulation lifecycle (settle → capture → restore) across every capture path — raster single and batch, pdf, envelope re-renders, the declared-screenshots path, and error exits — plus the parse/engine twin guards on mixed-media batches, and what the new tests actually pin.
Bottom line: no blocking issues. The mechanism is correct on every path traced; the one real ask is coverage — both new render tests stay green without the captureScreenshot half of this change, and the raster media=print shape (the only media=print form the GET DSL can request) is untested.
- Add a raster
media=printtest — the pdf leg's own media pin masks the engine change from both new render tests; see the comment on the print-pdf test inprerendering-test.ts. - The engine's mixed-media guard message can interpolate
undefined— mechanical one-liner; see the comment inprerender/utils.ts. - Two new comments use change-log wording ("now honored" / "no longer gated") — see the comment in
capture-spec.ts.
CI was still in flight at review time; nothing red.
| ); | ||
| }); | ||
|
|
||
| test('a media=print pdf paginates to the CSS paper size', async function (assert) { |
There was a problem hiding this comment.
[Claude Code 🤖] Neither of the new render tests would fail if the engine half of this change were lost, because both capture via type=pdf and captureOneEntry's pdf leg independently emulates the spec's media immediately before page.pdf() and clears it in its own finally. So this test's A4/pagination assertions hold even when the settle ran under screen media (the print job re-lays-out under print media with preferCSSPageSize either way), and the bleed test below finds no leak to catch because the pdf leg's inner restore already cleaned up.
The two behaviors captureScreenshot gains here — settling under print, and the outer finally restore — are only load-bearing for a raster media=print capture, which no test exercises. That is also the only media=print shape the GET DSL can request (it refuses type=pdf), so it's the axis's primary production form.
A raster pair pins both: a media=print png of a card with a print-only style (say a @media print background color, asserted via decodePngRGBA), followed by a default capture asserting the screen layout is back. The raster path has no inner pin, so that pair fails without both the settle-time emulation and the outer restore.
Test-coverage gap; non-blocking.
There was a problem hiding this comment.
[Claude Code 🤖] Fixed in 4713261. print-probe gains a print-only background color, and a new raster test asserts the color is present in a media=print png (the settle ran under print media) and absent from the next default capture — the render-level restore, which the raster path alone depends on. Ran the prerender suite locally: the new raster test and both print-pdf tests pass.
| if ((entry.media ?? 'screen') !== firstMedia) { | ||
| return buildInvalidRenderResponseError( | ||
| page, | ||
| `capture batch mixes media values (${firstMedia} and ${entry.media}); a batch renders under one media`, |
There was a problem hiding this comment.
[Claude Code 🤖] When the mismatching entry carries no explicit media (a defaulted-screen entry against a print batch), this interpolates undefined into the error message. Cosmetic; non-blocking.
| `capture batch mixes media values (${firstMedia} and ${entry.media}); a batch renders under one media`, | |
| `capture batch mixes media values (${firstMedia} and ${entry.media ?? 'screen'}); a batch renders under one media`, |
There was a problem hiding this comment.
[Claude Code 🤖] Fixed in 4713261 — a defaulted entry now interpolates as screen.
| // corresponding leg: a jpeg/webp encode on the on-demand path. (The `print` | ||
| // media value is now honored — the engine emulates print media across the | ||
| // settle — so both `CaptureMedia` values are supported and none is gated.) |
There was a problem hiding this comment.
[Claude Code 🤖] "is now honored" reads as a change-log entry rather than the current contract — "now" has no anchor for a later reader. The new parse test's comment in media-cache-dsl-test.ts has the same wording ("The engine now emulates… no longer gated"). State both timelessly, e.g. here: "Both CaptureMedia values are supported — the engine emulates the spec's media across the settle — so only output types are gated." Non-blocking.
There was a problem hiding this comment.
[Claude Code 🤖] Fixed in 4713261 — both comments now state the contract timelessly.
9b3d602 to
c1a8fe7
Compare
The `media` axis gains its behavior: a `media=print` capture now settles under print media, so the card's print CSS applies during the render — `@page` paper size (via preferCSSPageSize), `@media print` rules, and `break-*` pagination. The emulation is lifted to wrap the whole render in captureScreenshot, before the settle and the image-paint wait, so a print-only layout's resources load and are waited on, not just painted at capture time. It is restored in the render-level `finally`, the same save/restore discipline the viewport already gets, so a print capture leaves no media override on the pooled page for the indexing HTML path or the next screenshot to inherit. `media=print` is no longer refused at parse. Because emulation is page-level and a batch shares one settled render, a batch must render under one media — a mixed-media batch is refused at the shared parse and guarded again at the engine, rather than settling later entries under the wrong media. The pdf leg keeps its own `page.pdf` media pin: page.pdf forces print for the print job whatever the page emulates, so a media=screen pdf must emulate screen to override that default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014GsYGsuCqHJz9GAti4jheG
The pdf leg's own media pin masks the render-level emulation from pdf-based tests, so pin it with a raster: print-probe gains a print-only background color, and a new test asserts the color is present in a media=print png (the settle ran under print media) and absent from the next default capture (the render-level restore, which the raster path alone depends on). Also interpolate the defaulted media value into the engine's mixed-media guard message instead of 'undefined', and restate two change-log-style comments timelessly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ce0e4e8 to
4713261
Compare
Stacked on #6121 (pdf persist/serve) in the Cards to PDF chain — base is that branch, not
main.What
Gives the
mediaaxis its behavior. Amedia=printcapture now settles under print media, so the card's print CSS applies during the render:@page { size: … }sets the paper (viapreferCSSPageSize), Chrome's default otherwise@media printrules andbreak-after: page/break-inside: avoidpaginationThe emulation is lifted to wrap the whole render in
captureScreenshot— before the settle and the image-paint wait — so a print-only layout's fonts and background images load and are waited on, not just painted at capture time. It is cleared in the render-levelfinally, the same save/restore discipline the viewport already gets, so a print capture leaves no media override on the pooled page for the indexing HTML path or the next screenshot to inherit.Details
media=printis no longer refused at parse (theUNSUPPORTED_CAPTURE_MEDIAgate is gone). It parses and round-trips on both the POST body and the GET URL DSL, and is servable (a print-media raster is still an image).page.pdfmedia pin:page.pdfforces print for the print job whatever the page emulates, so amedia=screenpdf still has to emulate screen to override that default. The two restores are idempotent.Tests
media=printparses/round-trips on both surfaces; invalid media still refused; mixed-media batch refused; uniform-media batch allowed.prerendering-test.ts): a rastermedia=printcapture shows a print-only background color and the next default capture does not — the pdf leg pins its own media at capture time, so the raster path is what proves the settle-level emulation and the render-level restore; a newpagedfixture with@page { size: A4 }+break-after: pageproduces a multi-page A4 PDF viamedia=print(asserted against the PDF's/MediaBox); a regression test confirms the next pooled capture renders under screen media again (no print-media bleed-through).🤖 Generated with Claude Code
https://claude.ai/code/session_014GsYGsuCqHJz9GAti4jheG