Skip to content

feat(examples): render the same document from any branch - #485

Merged
DemchaAV merged 3 commits into
developfrom
feat/deterministic-example-renders
Aug 2, 2026
Merged

feat(examples): render the same document from any branch#485
DemchaAV merged 3 commits into
developfrom
feat/deterministic-example-renders

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Aug 1, 2026

Copy link
Copy Markdown
Owner

First of three for #461: this one makes a fresh render comparable to the committed one. The CI gate comes next, the release-script step after that. Nothing here touches cut-release.ps1, CI or the root README.

Why

Two things made a committed preview incomparable with a fresh render, and neither was about the document.

The version. A render takes it from the reactor, so develop bakes the next patch — 2.1.1 while 2.1.0 is what anyone can depend on. withoutQualifier() already strips -SNAPSHOT; the number itself has moved, so stripping was never going to be enough.

The date. Two headers carried {date}, which the engine resolves against the wall clock. The same document differed by the day it was rendered.

The assets also disagreed with each other: the README hero PNG said v2.1.0 while engine-deck-v2.pdf — cut from the same scene — said v2.1.1.

What

A display version can be passed in. -Dgraphcompose.examples.displayVersion=2.1.0 overrides the value everything already reads. banner.properties keeps sourcing @project.version@, which a guard requires, so the reactor remains the default answer; the override exists for the two callers that need to reproduce a published document.

The date leaves two headers. The master showcase names its own quarter — it is a Q2 fiction anyway — and the feature catalogue drops the field, including from the code sample that claims to show that document's own header. PdfChromeExample keeps {date}, because demonstrating the token is what that example is for; its preview is the one that cannot be compared, which is a property of the example, not an oversight.

The number leaves the places it decorated. The deck's hero kicker (GRAPHCOMPOSE 2.1 / MODULE-FIRSTGRAPHCOMPOSE / MODULE-FIRST), its module-graph title, a hardcoded 2.0 in the lockstep kicker, and two footers. It stays in the version pill and in the Maven banner's coordinate card — which now reads the same input as everything else instead of the string literal v2.1.0 it carried.

Six assets re-rendered at 2.1.0 — four PDFs, one deck and the README hero — so no committed asset still says 2.1.1. The two maven-banner files are deliberately left at their committed bytes; see the regression note below.

Tests

examples 52, qa 688, core guard job 37 — all green.

The layout snapshot moves, and only where it should: three widths shrink by exactly 21.12 each — four characters of a version at that size — and no other geometry in the deck changes.

-    "placementWidth" : 163.68,   +    142.56    HeroKicker
-    "placementWidth" : 105.6,    +     84.48    GraphLabel
-    "placementWidth" : 116.16,   +     95.04    MatrixKicker

The override is confirmed end to end: rendered on 2.1.1-SNAPSHOT, page one of the deck extracts as v2.1.0 GRAPHCOMPOSE / MODULE-FIRST. The remaining 2.0 strings in the deck and catalogue are body prose — "not a shipped 2.0 claim", "Apache 2.0" — not stamps, and stay.

A regression this uncovered, deliberately left for its own PR

Comparing the committed decks against a fresh render — ignoring zip timestamps, so only content counts — shows three of them losing bold today:

deck committed fresh render
maven-banner.pptx 28 bold runs 8
twin-output.pptx 4 0
business-report.pptx 25 0
master-showcase.pptx 75 75

The cause is the defect #479 fixed in the library and never applied to the examples: a style naming FontName.HELVETICA_BOLD with no decoration(BOLD). FontLibrary rewrites the alias to its family before the lookup, and the PPTX backend takes boldness from the resolved font. There are 115 such sites in examples/src/main, none of them paired with a decoration. master-showcase is unaffected because its styles come through BusinessTheme.

This is live on develop and predates this PR — business-report and twin-output are not touched here. It matters because web/showcase is regenerated on every cut, so the next release would publish the de-bolded decks.

maven-banner.pdf and .pptx are therefore left at their committed bytes rather than re-rendered: they carry the intended weights, and a fresh render today would commit the regression. Fixing the 115 sites and re-rendering is its own change, with the canonical-content comparator that found this.

What this does not make byte-identical

Two renders of the same example still differ: PDFBox seeds the trailer /ID from the clock, and the PPTX zip entries carry wall-clock timestamps. The engine ships deterministic(...) on both backends, but the convenience path document.buildPdf() never reaches it, so no example opts in. The comparison the next PR builds therefore has to be raster and text, not bytes — that is a property of the output format here, not a gap in the gate.

pdf-chrome.pdf stays non-reproducible in content too: it keeps {date}, because demonstrating that token is what the example is for.

web/showcase/** holds a second committed copy of these documents and is regenerated by the release cut, not here — so between this PR and the next cut the two copies differ on the five documents that changed. Re-syncing by hand would rewrite all 190 files for the sake of five, since every copy picks up a fresh /ID.

The committed set is consistent about the release version: no asset now says 2.1.1. Several feature previews still name 1.5 / 1.8 / 1.9.0 in their body copy — those are the subject matter of the demos, as issue #461 itself notes.

Not in this PR

The dates that survive in the previews (2026-07-26) are literals, not clock reads — verified by rendering on 2026-08-01 and getting the same value. pdf-chrome.pdf remains non-reproducible by design and will be named as out of scope when the gate lands.

A committed preview could not be compared against a fresh one, for two
reasons that had nothing to do with the document.

The version. Renders take it from the reactor, so develop bakes the next
patch — 2.1.1 while 2.1.0 is what anyone can depend on. Stripping the
qualifier was never enough: the number itself has moved. A display
version can now be passed in, and banner.properties keeps sourcing
@project.version@ as the default, so the reactor stays the answer when
nobody says otherwise.

The date. Two headers carried the {date} token, which resolves against
the wall clock inside the engine, so the same document differed by the
day it was rendered. The master showcase names its own quarter and the
feature catalogue drops the field; PdfChromeExample keeps the token,
because demonstrating it is what that example is for — its preview is
the one that cannot be compared, and that is a property of the example
rather than an oversight.

The number also left the places it decorated: the deck's hero kicker,
its module-graph title, a hardcoded "2.0" in the lockstep kicker, the
v1 deck's footer, and the master showcase's. It stays in the version
pill and in the Maven banner's coordinate card, which now reads the same
input as everything else instead of a string literal.

The layout snapshot moves with them: three widths shrink by 21.12 each,
which is the four characters of a version at that size, and nothing else
in the deck geometry changes.

The eight committed assets are re-rendered at 2.1.0. They disagreed with
each other before this — the hero PNG said 2.1.0 while the deck it is cut
from said 2.1.1 — so the set is internally consistent for the first time
since the release.
Both maven-banner assets were committed as modified with no content change:
the version literal and the token produce the same string at 2.1.0, so the
render is identical and only the time-seeded PDF /ID and the zip stamps
moved. They go back to what they were. A binary diff that is not a diff
hides whether the file was re-rendered at all.

The override arrived undocumented and untested. The decision now sits in a
pure function, because the answer is cached in a static field and a test
that sets the property proves only that nothing had touched the class yet.
It accepts a leading "v" — one render site prepends its own, so both
spellings reaching the page as "vv2.1.0" was one typo away. The examples
README carries the incantation, and the class says the reactor is the
default rather than the only answer.

Three leftovers: the deleted VERSION_LINE left its javadoc bound to the
constructor; the master showcase still called itself deliberately undated
and named a version source it no longer reads; and its currency was
formatted against the default locale, so the committed preview was only
reproducible on an English-locale machine. Output is unchanged there —
verified by re-rendering and comparing extracted text.
@DemchaAV DemchaAV changed the title feat(examples): make a committed render reproducible from any branch feat(examples): render the same document from any branch Aug 1, 2026
…ing it

Every site that prints the value runs it through withoutQualifier(), so
-Dgraphcompose.examples.displayVersion=2.2.0-rc.1 would have reached the
page as 2.2.0 — the final version of that line, which is not published
and may never be. The release tooling does cut -rc versions, so the path
was reachable rather than theoretical.

The override now takes a released version and refuses anything else,
naming what was passed and why. The reactor's own -SNAPSHOT is
unaffected: it arrives through banner.properties, not through the
override, and withoutQualifier() has always been what strips it there.
@DemchaAV
DemchaAV merged commit 236ed39 into develop Aug 2, 2026
10 checks passed
@DemchaAV
DemchaAV deleted the feat/deterministic-example-renders branch August 2, 2026 14:33
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