Skip to content

Declared PDFs: author-supplied render components via the render slot - #6186

Draft
lukemelia wants to merge 1 commit into
cs-12979-declared-pdfs-typepdf-entries-in-the-screenshots-roster-withfrom
cs-12980-author-supplied-pdf-render-components-via-the-declared
Draft

lukemelia wants to merge 1 commit into
cs-12979-declared-pdfs-typepdf-entries-in-the-screenshots-roster-withfrom
cs-12980-author-supplied-pdf-render-components-via-the-declared

Conversation

@lukemelia

Copy link
Copy Markdown
Contributor

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-only render component instead of a display format — the same render-slot pattern posters (PdfPosterCapture) already use — for cards whose document shape isn't any display format:

static screenshots = {
  statement: { format: 'isolated', type: 'pdf' }, // isolated-template path (CS-12979)
  invoice:   { render: InvoiceDocument, type: 'pdf' }, // custom-component path (this PR)
};

The component renders the full document flow itself and paginates via print CSS (@page, break-*); there is no capture box.

Changes

  • base/card-api.gtsassertValidScreenshotSpec stops refusing render on a PDF entry. A PDF entry now sources render or an isolated/embedded format; it still refuses raster geometry, box formats, and useAsThumbnail.
  • realm-server/prerender/utils.ts — render-based PDF entries drive the render.screenshot route through captureRenderBasedEntry under 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.
  • host render.screenshot route + template — a PDF render entry carries no width/height and renders the component in the natural document flow, not the fixed-size overflow: hidden box the raster path uses (which would clip a multi-page PDF to one page). Readiness stays the component's data-screenshot-pending signal.
  • experiments-realm — a StatementPdfDemo pattern 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 loader PdfPosterCapture already uses for its pdfjs-loader import.

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)
  • a slot-level component error follows the broken-links model without failing the row

Tests

  • Unit — validation now accepts render + type: 'pdf'; refusal cases updated.
  • End-to-end indexing — a render component paginates its own three-page document (distinct page count from the one-page isolated template, proving provenance) and serves as application/pdf; a failing PDF component records a screenshotErrors entry and persists no ledger row while the row still indexes.

Verification

realm-server passes lint:types + lint:js; host passes lint:js and lint:types for all files I touched (two pre-existing origin/main errors remain in app/lib/pdfjs.ts and app/lib/signed-capture.gts — unrelated to this change, from local build state). ⚠️ The realm-server browser suites (Docker-seeded Postgres + prerender stack) were not run locally — Docker wouldn't start in my environment. They need a CI run.

🤖 Generated with Claude Code

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
@github-actions

github-actions Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Preview deployments

Host Test Results

    1 files      1 suites   2h 15m 48s ⏱️
4 887 tests 4 873 ✅ 14 💤 0 ❌
4 902 runs  4 888 ✅ 14 💤 0 ❌

Results for commit 160a18c.

Realm Server Test Results

    1 files    241 suites   1h 17m 51s ⏱️
3 513 tests 3 513 ✅ 0 💤 0 ❌
3 561 runs  3 561 ✅ 0 💤 0 ❌

Results for commit 160a18c.

@lukemelia
lukemelia added this pull request to stack #6187 September 17, 2026 21:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant