From 8daa356750cba45d1d033953938813cf647ae932 Mon Sep 17 00:00:00 2001 From: transitrix Date: Thu, 1 Oct 2026 13:25:10 +0400 Subject: [PATCH] fix: retain issued page identity and format dates in UTC Signed-off-by: transitrix --- .github/workflows/document-renderer-test.yml | 6 ++ packages/document-renderer/README.md | 21 ++++++- .../src/render-vivliostyle.mjs | 12 ++-- .../tests/test_paged_media_css.mjs | 42 +++++++++++++- .../tests/test_render_vivliostyle.mjs | 57 +++++++++++++------ 5 files changed, 114 insertions(+), 24 deletions(-) diff --git a/.github/workflows/document-renderer-test.yml b/.github/workflows/document-renderer-test.yml index 392649cb..79e6cd0a 100644 --- a/.github/workflows/document-renderer-test.yml +++ b/.github/workflows/document-renderer-test.yml @@ -67,3 +67,9 @@ jobs: - name: Run full-run end-to-end test run: node packages/document-renderer/tests/test_full_run.mjs + + - name: Run paged-media CSS tests + run: node packages/document-renderer/tests/test_paged_media_css.mjs + + - name: Run snapshot caption tests + run: node packages/document-renderer/tests/test_render_vivliostyle.mjs diff --git a/packages/document-renderer/README.md b/packages/document-renderer/README.md index d1b95a80..a1a9c66a 100644 --- a/packages/document-renderer/README.md +++ b/packages/document-renderer/README.md @@ -497,7 +497,8 @@ The package has two PDF rendering paths: - Running footers (issuer, date, identity, page number) - Landscape pages for wide diagrams - Page breaks and typography rules -- First-page suppression (no footer on title page) +- Every-page identity for issued documents, including the first portrait or landscape page +- First-page suppression retained for non-issued content (no `issued_at`) **Usage:** @@ -514,6 +515,24 @@ const html = wrapHtmlForPrintRendering('

Title

Content...

', { // Pass html to Vivliostyle or another CSS Paged Media renderer ``` +Supplying `issued_at` selects the issued-document footer behavior; a one-page +issued document receives the same issuer, issue date, identity, optional commit +and page counters as subsequent pages. Callers supply the existing identity +fields; this helper does not create or validate an issue identity. + +Issue dates (`issued_at`) and snapshot caption dates (`generated_at`) use the +`en-US` locale and **UTC**, independent of the host timezone. Supply ISO 8601 +timestamps with `Z` or an explicit numeric offset (or a date-only `YYYY-MM-DD`); +timezone-less date-time strings are not stable inputs across hosts. +Snapshot captions use only captured-at `generated_at`, never valid-at +`renderDate`, the issue date, or the wall clock. `renderDate` remains the separate +pass-1 input that selects model validity. Missing snapshot dates leave captions +unchanged; invalid dates produce `Unknown date`. Callers remain responsible for +snapshot binding before issuing a document. + +These helpers prepare HTML/CSS; their tests do not establish footer visibility, +landscape geometry, diagram embedding or reproducibility in a final PDF. + **Specification:** [`PDF_RENDERING_ENGINE.md`](./PDF_RENDERING_ENGINE.md) **Engine reference:** Vivliostyle (option 2), caller-supplied supported (option 3) **Feature matrix:** Named pages, landscape pages, break rules, running elements, counters diff --git a/packages/document-renderer/src/render-vivliostyle.mjs b/packages/document-renderer/src/render-vivliostyle.mjs index faa0fc56..29282036 100644 --- a/packages/document-renderer/src/render-vivliostyle.mjs +++ b/packages/document-renderer/src/render-vivliostyle.mjs @@ -31,7 +31,8 @@ function escapeHtml(text) { * * @param {object} metadata - Document metadata * @param {string} metadata.issuer - Who created the document - * @param {string} metadata.issued_at - ISO 8601 timestamp + * @param {string} metadata.issued_at - Issue timestamp with explicit offset; formatted in UTC. + * When present, retains the footer on every page, including the first. * @param {string} metadata.document_identity - Recipe ID or document identifier * @param {string} [metadata.repository_commit] - Git commit hash * @param {array} [metadata.views] - View specifications with width/height @@ -52,6 +53,7 @@ export function generatePagedMediaCss(metadata = {}) { year: 'numeric', month: 'short', day: 'numeric', + timeZone: 'UTC', }); } catch { // ignore invalid date @@ -98,11 +100,11 @@ export function generatePagedMediaCss(metadata = {}) { } } -@page :first { +${metadata.issued_at ? '' : `@page :first { @bottom-center { content: ""; } -} +}`} @page :left { margin-right: 20mm; @@ -238,7 +240,8 @@ ${wideViewsCss} /** * Format figure captions to include snapshot build dates. * Finds all
elements with data-snapshot-id attributes and appends - * the snapshot's generated_at timestamp to the caption. + * the snapshot's captured-at generated_at timestamp to the caption, formatted in UTC. + * Supply timestamps with explicit offsets; renderDate (valid-at) is not a fallback. * * @param {string} html - HTML content with figure elements * @param {object} snapshots - Map of snapshot IDs to snapshot objects with generated_at @@ -265,6 +268,7 @@ export function formatFigureCaptions(html, snapshots = {}) { year: 'numeric', month: 'short', day: 'numeric', + timeZone: 'UTC', }); } } catch { diff --git a/packages/document-renderer/tests/test_paged_media_css.mjs b/packages/document-renderer/tests/test_paged_media_css.mjs index 8854075e..0c0b572d 100644 --- a/packages/document-renderer/tests/test_paged_media_css.mjs +++ b/packages/document-renderer/tests/test_paged_media_css.mjs @@ -61,11 +61,47 @@ function check(condition, message) { check(!hasUnescapedAmpersand, 'No unescaped ampersands in content string'); } -// Test 5: First page omits footer +// Test 5: Issued identity survives on every page, including a one-page document. { + const metadata = { + issuer: 'Test Corp', + issued_at: '2026-09-02T00:30:00Z', + document_identity: 'SPEC-v2.0', + repository_commit: 'a1b2c3d4e5f6', + }; + for (const content of ['

One page

', '
Wide view
']) { + const html = wrapHtmlForPrintRendering(content, metadata); + const css = html.match(/