Skip to content

build(release): publish the showcase the cut produced, not the one lying in target - #477

Merged
DemchaAV merged 3 commits into
developfrom
release/showcase-truth
Jul 31, 2026
Merged

build(release): publish the showcase the cut produced, not the one lying in target#477
DemchaAV merged 3 commits into
developfrom
release/showcase-truth

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Why

cut-release.ps1 never runs GenerateAllExamples.

Run-ShowcaseSync installs the train siblings, recompiles the examples so ShowcaseMetadata.GH_BASE points at the tag, and then runs ShowcaseSync — which only copies what is already in examples/target/generated-pdfs. So the published site is whatever happened to be in the maintainer's target directory from earlier commands. On a clean checkout there is nothing there, and the cut aborts at Step 4 with the tree already version-bumped.

ShowcaseSync also copies but never deletes. An example that was renamed or removed keeps its artifact live on GitHub Pages indefinitely — reachable by URL, absent from the manifest, rendered from source that no longer exists. Three such documents are published today: invoice-custom-theme (whose example was removed), and invoice.pdf / proposal.pdf, which no example has written since June — verified by grepping for the exact output names and by their absence from a fresh generation.

And eight generated artifacts have no ShowcaseMetadata entry, so lookup (ShowcaseMetadata.java:145) falls back to a filename-derived title, the description "Generated showcase for flagships / default.", and a link to the examples root instead of the example. Two of them are social-card and linkedin-carousel — the flagships the 2.1 line exists to show. They would have shipped on the 2.1.1 site as placeholder cards.

What changed

Regeneration before sync. Run-ShowcaseSync gains a clean compile exec:java GenerateAllExamples ahead of the sync. clean is the point: it drops artifacts left by tests or by examples since renamed, so the published tree is a function of the tag rather than of shell history. It sits after the pom bump and the GH_BASE rewrite, so version-stamped renders carry the new version — the ordering rule Render-ReadmeBanner already documents.

ShowcaseSync clears before it copies — but collects first. The directory existing is not evidence anything was produced, and clearing before listing meant a generation run that succeeded while writing nothing would delete the whole published site and write an empty manifest, with the failure visible only once the site was live. The list is gathered first and an empty one aborts before anything is removed. The clear itself removes only regular files, not directories: git tracks no empty directory, the copy recreates what it needs, and deleting directories is what makes this fail on Windows — the first version died with AccessDeniedException on web/showcase/pdf/templates/schedule.

The smoke test asserts. GenerateAllExamplesSmokeTest ended in assertThat(true).isTrue() under a comment claiming it checked that at least one PDF was written. It now reads the output tree: PDFs exist, every deck has its twin, and the catalogue spans templates, features and flagships.

A PDF for the Maven banner. GenerateAllExamples called only MavenBannerPptxExample.generate(), which writes flagships/maven-banner.pptx, while the class also exposes generatePdf(). ShowcaseSync walks .pdf, keys one card per PDF, and copies a .pptx only as that PDF's same-named companion — so a deck without a PDF twin lands in target and stops there, and the metadata entry written for it is never read. The banner appeared in web/examples.json zero times. everyGeneratedDeckHasAPdfTwin now states that invariant, so the next deck cannot repeat it.

Nine metadata entries, including engine-deck-v2. EngineDeckV2Example renders the deck the README banner is cut from and has its own snapshot test, but was never wired into GenerateAllExamples — registering it made it the eighth artifact needing an entry. invoice() and proposal() hardcoded their producing example, so the two *-modern-v2 entries could not name theirs; both helpers now take the class, like cv(), letter(), feature() and flagship() already do.

Five committed previews re-rendered, and eleven unreferenced binaries removed (9.01 MiB; assets/Overview.png alone is 5.8 MiB). The .gitignore allowlist still whitelisted docs/assets, docs/showcase and site/ — three trees with no tracked file between them.

Verification

Reactor gate BUILD SUCCESS, 13/13 modules, 2:04, zero errors. Examples module 49 tests green. cut-release.ps1 parses clean (PowerShell AST, 4549 tokens); the cut-release.ps1 dry-run CI check covers the dry-run branch.

GenerateAllExamples then ShowcaseSync were run in the same order the script now uses: Synced 99 documents (91 PDFs, 8 PPTX twins) and 91 preview PNGs. The manifest check is bidirectional, because counting placeholder cards is not enough — a missing card does not look like a placeholder, which is how the Maven banner went unnoticed in the first pass: 91 produced, 91 cards, 0 placeholders, 0 produced without a card.

The deck-twin guard was proved in both directions. With the fix: 3 tests green. With generatePdf() removed: Expecting empty but was: ["flagships/maven-banner.pptx"]. The first attempt at that negative run passed spuriously — Surefire does not clean, so the previous run's maven-banner.pdf was still in target and the guard saw a twin; re-run with clean test it fails as it should.

Staleness was measured, not guessed, and the measurement corrected two earlier numbers:

Signal Result
Asset older than its source (commit dates) 26 flagged — an upper bound; a source touched for an import reorder counts
File-size delta ≥ 5% 4 flagged — and it misses pdf-chrome.pdf, which differs by 2.6%
Rendering both copies and comparing pixels 5, at 11.9–36.4 mean channel delta, then a cliff to 0.15

That cliff is the date stamp in the page corner and nothing else, so 21 previews were left alone. pdf-chrome.pdf is the one worth looking at: the committed copy draws the entire body at low alpha with the DRAFT watermark bleeding through every panel, where a fresh render is solid — a 2.6% size difference and an 18.3/255 pixel difference.

The same rule was applied to web/showcase: a full regeneration touched 111 files, of which 86 differed only by that date stamp. Those were reverted, leaving 25 — six additions, six orphan removals, thirteen material re-renders — instead of a PR carrying ninety binary diffs nobody can review.

The eleven deleted assets were each checked twice: a content scan across every tracked text file, then git grep. GraphComposeLogo_final.svg is not the referenced GraphComposeLogo.png. All remain recoverable from history.

Not in scope

Nothing asserts that a produced artifact has a metadata entry — lookup falls back rather than failing, which is why nine accumulated silently. Today's nine are fixed by hand and the deck-twin invariant is guarded, but a new PDF without an entry still publishes as a placeholder. That check needs ShowcaseMetadata's id set, and the class is package-private; it belongs with the other guards in the next change, along with a guard against the orphan leak returning.

The regeneration step itself only executes during a real cut; the dry-run check covers syntax and the dry-run branch. Running both mains manually in the script's order is the closest available proof short of cutting a release.

DemchaAV added 3 commits July 31, 2026 18:39
…ing in target

cut-release.ps1 never ran GenerateAllExamples. Run-ShowcaseSync installed the
train siblings, recompiled the examples so ShowcaseMetadata.GH_BASE pointed at
the tag, and then synced whatever happened to be sitting in
examples/target/generated-pdfs from earlier commands — on a clean checkout,
nothing, and the cut aborts mid-flight. Step 4 now regenerates the catalogue
first, with clean, so the published site is a function of the tag rather than of
the maintainer's shell history. It runs after the pom bump and the GH_BASE
rewrite, so version-stamped renders carry the new version.

ShowcaseSync copied but never removed, so an example that was renamed or deleted
kept its artifact live on GitHub Pages indefinitely — reachable by URL, absent
from the manifest, rendered from source that no longer exists. It now clears the
three output trees first. Three documents went with it: invoice-custom-theme,
whose example was removed, and invoice.pdf / proposal.pdf, which no example has
written since June. Only files are deleted, not directories: git tracks no empty
directory, the copy recreates what it needs, and removing them is what makes
this fail on Windows with AccessDeniedException.

Eight generated artifacts had no ShowcaseMetadata entry, so lookup fell back to
a filename-derived title, a generic description and a link to the examples root
— a placeholder card. Two of them were the flagships 2.1.x exists to show,
social-card and linkedin-carousel. The manifest now carries 90 cards and no
placeholders. EngineDeckV2Example, which renders the deck the README banner is
cut from, was never in GenerateAllExamples despite being linked from the README
and covered by a snapshot test; registering it made it the eighth.

invoice() and proposal() hardcoded their producing example, so the two v2
entries could not name theirs. Both now take the class like cv(), letter(),
feature() and flagship() already do.

Five committed previews were materially stale. pdf-chrome.pdf is the visible
one: the whole body is drawn at low alpha with the DRAFT watermark bleeding
through every panel, where a fresh render is solid. Staleness was decided by
rendering both copies and comparing pixels — the five differ by 11.9 to 36.4
mean channel delta, the other twenty-one by less than 0.16, which is the date
stamp in the corner and nothing else.

Eleven committed binaries totalling 9.01 MiB are referenced by no file in the
repository — assets/Overview.png alone is 5.8 MiB. The .gitignore allowlist
still whitelisted docs/assets, docs/showcase and site/, three trees with no
tracked file between them.
…assert

The banner deck never reached the site. GenerateAllExamples called only
MavenBannerPptxExample.generate(), which writes flagships/maven-banner.pptx,
while the class also exposes generatePdf(). ShowcaseSync walks .pdf files, keys
one card per PDF, and copies a .pptx only as that PDF's same-named companion —
so a deck with no PDF twin lands in target and stops there, and the metadata
entry written for it goes unused. The banner appeared in web/examples.json zero
times. Generating the PDF first puts it on the site: the sync now reports 91
PDFs and 8 PPTX twins where it reported 90 and 7.

everyGeneratedDeckHasAPdfTwin states that invariant so the next deck cannot
repeat it. The smoke test around it asserted nothing — its own comment said "at
least one PDF should have been written" above assertThat(true).isTrue(). It now
reads the output tree: PDFs exist, every deck has its twin, and the catalogue
spans templates, features and flagships rather than one corner of it.

ShowcaseSync collected the PDF list after clearing the published trees, so a
generation run that succeeded while writing nothing would have deleted the whole
site and written an empty manifest, with the failure visible only once the site
was live. The list is now collected first and an empty one aborts before
anything is removed.
@DemchaAV
DemchaAV merged commit d615214 into develop Jul 31, 2026
11 checks passed
@DemchaAV
DemchaAV deleted the release/showcase-truth branch July 31, 2026 18: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