Skip to content

feat(examples): hold the committed previews to the code that renders them - #487

Merged
DemchaAV merged 5 commits into
developfrom
feat/asset-drift-gate
Aug 2, 2026
Merged

feat(examples): hold the committed previews to the code that renders them#487
DemchaAV merged 5 commits into
developfrom
feat/asset-drift-gate

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Second of the two changes for #461. The first made the catalogue render one way, from an empty tree; this one holds the committed previews to it.

Why

README and the showcase site read files under assets/readme/** rather than rendering anything, and nothing held those files to the catalogue. A change to an example, a theme or the engine moved the render while the committed file stayed where it was, and the first anybody knew was a release publishing it — which is how maven-banner.pptx went two releases without the bold weights its styles asked for, found by hand in #486 rather than by a gate.

Twenty-three of the sixty-seven previews were behind the code, and they are re-rendered here. All twenty-two PDFs among them rasterise to the same pixels as before at 100 DPI — the drift was in the operator stream, not in what a reader sees — so nothing visible was carrying it and nothing visible is buried by refreshing them. The one DOCX is the exception worth naming: it expressed bold by asking for a font called Helvetica-Bold, which Word honours only if that font is installed, and now marks <w:b/> instead. All 56 runs keep the weight they had.

What

The comparison is exact. The original sketch for this gate was rasterisation at a fixed DPI with a pixel budget; the measurement says that is not needed and would be worse. Rendering the catalogue on Windows and on the runner at one version puts 99 of 104 documents byte-identical once the machine is left out of it, and the five that are not are each accounted for:

document difference committed?
twin-output.pptx the box two freeforms are normalised against yes — those two shapes named
word-export-companion.docx the creation stamp in docProps yes — dropped
master-showcase.pptx a watermark's antialiasing yes — the one named exemption
engine-deck.pptx five raster parts no
emoji-svg-vs-png.pdf rasterised emoji no

So AssetContent drops a PDF's clock-seeded /ID, a package's zip timestamps, dcterms:created in docProps/core.xml, and the platform's line separator after an XML declaration — and compares everything else, images included, pixel for pixel.

Every exemption names one thing and the two renders it covers it between. The watermark is exempted by the pixel digest of each machine's version, not by its path: an image of the same size the fixtures do not account for keeps its own digest and fails. The same shape holds for geometry. Comparing all six decks shape by shape found the box difference in exactly two freeforms, both in twin-output.pptx, so those two are named by digest and every other shape is compared as written — a box is the centre a rot turns a shape around and the axis a flipH mirrors it in, and five of the six decks carry one or the other. A tolerance is what nobody can justify a year later; a named exemption with a measurement behind it is.

The version the previews carry is recorded. A committed preview is rendered at the released version while a working tree is already on the next one, so the two are different documents before anything else is weighed. graphcompose.examples.assetVersion in examples/pom.xml records it and surefire pins the display version to it, so the module's tests render what the previews are.

Nothing moves that property yet, and this gate cannot notice. It compares both sides at whatever the property says, so a release that re-renders web/showcase at the new version while leaving the previews behind passes here. That is the release step's job — and the reason this should land next to it rather than long before: merged alone and left, the next cut puts the previews a version behind with nothing going red.

What cannot be compared is written down. Eight rasterised figures sit beside the preview folder, the README hero among them. Each is a page rasterised to a PNG, and rasterising text is where the two machines disagree — the first run of this gate proved it, rejecting the hero and nothing else while all sixty-seven previews matched. Comparing them would take a pixel budget, so instead the eight are listed and asserted as a set: a ninth arrives as a decision rather than as a commit nobody read. assets/readme/v1.5 is left out entirely — those are a released line's figures and re-rendering them would be the bug.

assets/readme/** joins the code path filter, so hand-editing or hand-reverting a preview runs the job that checks it.

The PPTX comparison from #486 moves into AssetContent with its tests rather than being duplicated for PDF and DOCX; every test it had is still here, plus one for the /ID, one for the creation stamp, and one for line endings.

Out of scope

  • The eight rasterised figures, for the reason above; cut-release.ps1 re-renders the hero at every tag, so no release publishes one that is behind.
  • cut-release.ps1 — the release-side asset step, the -SkipShowcase split and the staging allow-list are the next change.
  • The showcase register's reverse invariant (a generated document with no entry). ShowcaseMetadataCoverageTest records a deliberate decision not to assert it, since the fallback card is the point of having one; overturning that needs its own justification and an explicit list of documents left to the fallback, not a line in a PR about drift.

Tests

examples 71 tests and qa 688, green locally. The reactor is green on JDK 17, 21 and 25; the asset-drift gate itself runs in Examples Generation Smoke Test, which is the baseline JDK 17 alone.

Check Result
every committed preview matches what the catalogue renders passes; failed on 23 before the refresh
the only assets this cannot compare are the ones written down passes
a PDF carrying a different identifier is the same document passes
the catalogue is exactly the published previews plus the unpublished ones passes; a deleted preview fails it by name
an unknown image of the same size is not absorbed passes
the allowlisted part in both decks is a known render passes
a custom date property is not absorbed passes
the same deck / the same PDF rendered twice is one document passes
rotated freeforms with different pivots are not equal passes
only the named shapes collapse passes
the named shapes are still what both decks carry passes
two images of one size are not one image passes
the named unstable part absorbs the measured difference passes
a part written with either line ending is one part passes
the creation stamp is not part of the document passes
every allowlisted part names a part that exists passes

The gate was also run against a deliberately swapped preview: it fails naming that file alone.

…them

README and the showcase site read files under assets/readme rather than
rendering anything, and nothing held those files to the catalogue. A change
to an example, a theme or the engine moved the render while the committed
file stayed where it was, and the first anybody knew was a release
publishing it — which is how a deck went two releases without the bold
weights its styles asked for. Twenty-three of the sixty-seven previews were
behind the code at the point this was written; they are re-rendered here.
All twenty-two of the PDFs among them rasterise to the same pixels as
before, so nothing visible was carrying the drift, and nothing visible is
buried by refreshing them.

Every preview is now compared against a fresh render on each build. The
comparison is exact and drops only what a machine writes rather than an
author: a PDF's clock-seeded identifier, a package's zip and creation
stamps, the platform's line separator after an XML declaration, the box a
freeform's path is normalised against, and one named watermark whose
antialiasing differs between machines. Each was measured by rendering the
catalogue on both platforms at one version rather than argued from — 99 of
104 documents came out byte-identical under it — so the gate needs no
tolerance, and every exemption is a line somebody had to write.

A committed preview is rendered at the released version while a working
tree is already on the next one, so the version the previews carry is
recorded next to them and the module's tests render at it. Without that the
coordinate pill alone would differ and the comparison would fail on every
preview that has one.

The README hero is checked too. It is not part of the catalogue — the
release script renders it alone — which is exactly why it drifts unnoticed
between releases.

Editing a preview by hand now runs the job that checks it.
The runner rejected the README hero and nothing else: all sixty-seven
previews matched across the two platforms, and the hero did not, because it
is not a document — it is a page rasterised to a PNG, and rasterising text
is exactly where two machines disagree. Comparing it would take a pixel
budget, which is a number nobody can defend a year later.

It is also not the only one: eight rasterised figures sit beside the
preview folder, all of them the same kind of file for the same reason. They
are written down and asserted as a set, so a ninth arrives as a decision
rather than as a commit nobody read. The figures of the 1.5 line are left
out of that entirely — re-rendering those would be the bug.
…what it did

The hero test took a raster comparison down with it when the runner showed
rasters cannot be compared, and two pieces of it stayed: a branch reading a
PNG by its pixels, which no caller reaches and which says the opposite of
the list beside it, and a comment justifying itself by a caller that is gone.

The guard over the assets it cannot compare listed files and passed over
folders, which are easier to add without noticing, not harder. Both are
asserted now.

The CHANGELOG said all twenty-three refreshed previews rasterise to the same
pixels. Twenty-two of them do; the twenty-third is a DOCX, which nothing
rasterised, and whose change was real — it asked for a font named
Helvetica-Bold, which Word honours only if that font is installed, and now
marks bold.
…wapped image

Comparing the files that are there means a deleted preview leaves its own
guard: README loses a figure, the folder shrinks, and every test stays
green. The catalogue is now pinned against the folder plus a list of the
documents deliberately left unpublished, so the three have to account for
each other exactly — a deletion, an example nobody decided to publish, and
a rename that lands as one of each all fail. The list is the shorter half of
the decision, and it replaces the deck-only lists, which were the same idea
applied to six files out of sixty-seven.

The exemption for the showcase watermark exempted the path rather than the
renders. Any image of the same size passed: a different word, a swapped
logo, a blank. It now names the pixel digest of each machine's version, and
only those two collapse onto one token; anything else keeps its own digest.
Two tests hold that shut — an unknown image of exactly the right size is not
absorbed, and the part actually inside both decks has to be one of the two
renders the fixtures account for, since naming a path protects nothing once
the deck stops carrying what was measured.

The creation stamp was dropped by matching any ISO instant anywhere under
docProps, which would have taken a date somebody meant along with it. It
names dcterms:created in docProps/core.xml, which is the one element that
was measured varying, and a custom date property now proves it survives.

The property recording the previews' version said the release script moves
it. No release step does yet, and until one exists a release re-renders the
showcase at the new version while the previews stay behind — with this gate
comparing both sides at the old one and staying green. Both notes say so.
Reading each freeform's path in slide coordinates dropped the box those
coordinates were measured against — and a box is not bookkeeping. It is the
centre a rot turns a shape around and the axis a flipH mirrors it in, and
five of the six committed decks carry one or the other. Two shapes with the
same path and the same angle in differently sized boxes turn about different
points and draw differently; the comparison called them one shape. The scale
it applied came from the first path in a shape and was used for every point
in it, which is wrong the moment a shape has two paths with different extents.

Comparing all six decks shape by shape against a render from the runner found
the difference in exactly two shapes, both in one deck. Those two are named
now, with the digest of each machine's version, the way the unstable
watermark already was — so the exemption covers a pair of renders rather than
a kind of shape, and every other freeform is compared as it was written.

The preview folder was listed for its files, with folders stepped over in
silence: a preview one level down was in none of the sets these tests
compare. The folder is asserted flat, which is how the site reads it.
@DemchaAV
DemchaAV merged commit 2492de3 into develop Aug 2, 2026
20 of 22 checks passed
@DemchaAV
DemchaAV deleted the feat/asset-drift-gate branch August 2, 2026 20:27
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