Declared PDFs: author-supplied render components via the render slot - #6186
Draft
Conversation
A declared `type: 'pdf'` entry can now source a capture-only `render` component instead of a display format — the same render-slot pattern posters use — for cards whose document shape isn't any display format. - card-api: assertValidScreenshotSpec stops refusing render for pdf; a pdf entry sources render OR an isolated/embedded format, still with no raster geometry, no box formats, and no useAsThumbnail. - prerender: render-based pdf entries drive the render.screenshot route through captureRenderBasedEntry under emulated print media (no capture box); page.pdf() paginates the component's document flow. The pdf loop dispatches render- vs format-based sources; both stay under the pdf sub-cap and follow the broken-links model on failure. - host render.screenshot route/template: a pdf render entry carries no width/height and renders the component in the natural document flow (no fixed, overflow-hidden box that would clip it to one page); readiness stays the component's data-screenshot-pending signal. - experiments-realm: a StatementPdfDemo pattern card showing both paths (isolated-template pdf and custom-component pdf) plus an instance. The @cardstack/boxel-host/lib/* doorway needs no new registration — author pdf components load through the same route + component loader PdfPosterCapture already uses. Tests: unit validation (render+pdf accepted), and end-to-end indexing — a render component paginates its own three-page flow (distinct from the one-page isolated template), and a failing pdf component fails its slot without failing the row. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014GsYGsuCqHJz9GAti4jheG
Contributor
lukemelia
added this pull request to stack #6187
September 17, 2026 21:45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes CS-12980. Stacked on #6184 (CS-12979) — review/merge that first; this PR's base is the CS-12979 branch, so its diff shows only the render-slot work.
What this does
A declared
type: 'pdf'entry can now source a capture-onlyrendercomponent instead of a display format — the same render-slot pattern posters (PdfPosterCapture) already use — for cards whose document shape isn't any display format:The component renders the full document flow itself and paginates via print CSS (
@page,break-*); there is no capture box.Changes
base/card-api.gts—assertValidScreenshotSpecstops refusingrenderon a PDF entry. A PDF entry now sourcesrenderor anisolated/embeddedformat; it still refuses raster geometry, box formats, anduseAsThumbnail.realm-server/prerender/utils.ts— render-based PDF entries drive therender.screenshotroute throughcaptureRenderBasedEntryunder emulated print media (no box);page.pdf()paginates the component's flow. The PDF loop dispatches render- vs format-based sources; both stay under the PDF sub-cap and follow the broken-links model on failure.hostrender.screenshot route + template — a PDF render entry carries nowidth/heightand renders the component in the natural document flow, not the fixed-sizeoverflow: hiddenbox the raster path uses (which would clip a multi-page PDF to one page). Readiness stays the component'sdata-screenshot-pendingsignal.experiments-realm— aStatementPdfDemopattern card demonstrating both paths (isolated-template + custom-component) plus an instance.Doorway check:
@cardstack/boxel-host/lib/*needs no new registration — an author PDF component loads through the same route + component loaderPdfPosterCapturealready uses for itspdfjs-loaderimport.Acceptance (CS-12980)
invoice: { type: 'pdf', render: InvoiceDocument }produces a multi-page PDF at index time whose content comes from the component, not the isolated template (the end-to-end test asserts the component's 3-page flow vs the template's 1 page)Tests
render+type: 'pdf'; refusal cases updated.application/pdf; a failing PDF component records ascreenshotErrorsentry and persists no ledger row while the row still indexes.Verification
realm-serverpasseslint:types+lint:js;hostpasseslint:jsandlint:typesfor all files I touched (two pre-existingorigin/mainerrors remain inapp/lib/pdfjs.tsandapp/lib/signed-capture.gts— unrelated to this change, from local build state).🤖 Generated with Claude Code