Skip to content

Commit e73aa05

Browse files
authored
chore(examples): stop the master showcase from dating itself (#470)
* chore(examples): stop the master showcase from dating itself The showcase was a v1.5 release-status report, re-committed at every release since. It announced what "v1.5 lands", carried an action item to tag v1.5.0 once develop merged, confirmed a release window for June 2026, and quoted two benchmark scenarios by name at figures typed in during v1.5. Its metadata subject and footer named v1.5 as well. The class javadoc calls the document fictional, but a reader of the PDF sees the real repository name, real benchmark scenario names and real version numbers, so the props and the claims were indistinguishable. Substituting fresh values would have been wrong twice over: it rots again on the same schedule, and "v1.5 lands the cinematic features" does not become true by writing 2.1 - those features did land in 1.5. So the narrative no longer names a version or a date, and the two facts worth stating are read at render time instead. The version comes from the filtered banner.properties, the speed figures from the committed perf baseline shown with its capture date. The examples module now puts that baseline on its own classpath from where the gate keeps it, rather than holding a second copy that would drift from the first. The approval seal reads SAMPLE where it read a quarter and a year, which also makes the document's fictional half visible in the render rather than only in the javadoc. The version lookup moves to a shared helper. It existed only in the deck, and the deck's own comment explains why it exists - a literal keeps announcing whichever line it was typed on - which is the same failure this commit repairs in a second file. * fix(examples): guard the baseline resource and repair what the extraction left behind Removing majorMinor from the deck deleted the method but not its javadoc, so the block was left heading logo() and documenting an @param version that method does not take. The showcase javadoc promised "three pages of designed content"; the document renders two. The examples job gates on the `code` path filter, which did not list baselines/. Now that the module renders figures from the committed baseline, a baseline-only refresh would have changed generated output while skipping the job that checks it. PerfBaseline degraded inconsistently: a missing resource was swallowed while a malformed one threw from a static initializer and would have taken down every example in the module, not just the one quoting a figure. Loading now never throws, and a scenario whose figures are absent or non-numeric is treated as absent rather than read as 0.0 - which would have rendered a plausible "0.0 ms avg, 0 docs/sec" instead of admitting nothing was measured. That fallback is honest at render time and wrong to discover in a published PDF, so PerfBaselineTest asserts the resource really is on the classpath with the scenarios the showcase quotes. Verified by pointing the pom entry at a filename that does not exist: on a clean build two of its three tests fail with the intended message.
1 parent fbc23cf commit e73aa05

12 files changed

Lines changed: 299 additions & 46 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ jobs:
7979
- '**/pom.xml'
8080
- '**/src/main/resources/**'
8181
- '**/src/test/resources/**'
82+
# The examples module renders figures from the committed perf
83+
# baseline, so a baseline-only refresh changes generated output.
84+
- 'baselines/**'
8285
- '.mvn/**'
8386
- 'mvnw'
8487
- 'mvnw.cmd'
-122 Bytes
Binary file not shown.
-776 Bytes
Binary file not shown.

examples/pom.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,20 @@
146146
<exclude>banner.properties</exclude>
147147
</excludes>
148148
</resource>
149+
<!--
150+
The committed perf baseline, read straight from where the gate
151+
keeps it. Examples that quote engine speed restate this file
152+
rather than carrying literals, and pointing at the original
153+
instead of copying it is what keeps the two from drifting.
154+
-->
155+
<resource>
156+
<directory>${project.basedir}/../baselines</directory>
157+
<targetPath>baselines</targetPath>
158+
<filtering>false</filtering>
159+
<includes>
160+
<include>current-speed-full.json</include>
161+
</includes>
162+
</resource>
149163
</resources>
150164
<plugins>
151165
<plugin>

examples/src/main/java/com/demcha/examples/flagships/EngineDeckV2Example.java

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,16 @@
3737
import com.demcha.compose.document.svg.SvgIcon;
3838
import com.demcha.compose.font.FontName;
3939
import com.demcha.examples.support.ExampleOutputPaths;
40+
import com.demcha.examples.support.ExampleVersion;
4041

4142
import java.awt.image.BufferedImage;
42-
import java.io.IOException;
4343
import java.io.InputStream;
4444
import java.nio.charset.StandardCharsets;
4545
import java.nio.file.Path;
4646
import java.util.ArrayList;
4747
import java.util.List;
4848
import java.util.Locale;
4949
import java.util.Objects;
50-
import java.util.Properties;
51-
import java.util.regex.Matcher;
52-
import java.util.regex.Pattern;
5350

5451
/**
5552
* Parallel 2.0 release concept for the GraphCompose README hero and engine
@@ -116,7 +113,7 @@ public final class EngineDeckV2Example {
116113
private static final DocumentColor PALE_BLUE = DocumentColor.rgb(232, 243, 255);
117114
private static final DocumentColor PALE_MINT = DocumentColor.rgb(230, 249, 242);
118115

119-
private static final String VERSION = loadVersion();
116+
private static final String VERSION = ExampleVersion.current();
120117

121118
/**
122119
* The {@code major.minor} of {@link #VERSION}, for the banner's prose labels.
@@ -125,7 +122,7 @@ public final class EngineDeckV2Example {
125122
* so a literal would keep announcing whichever line it was typed on — the hero
126123
* still read "2.0" while the pill beside it read v2.1.0.</p>
127124
*/
128-
private static final String VERSION_LINE = majorMinor(VERSION);
125+
private static final String VERSION_LINE = ExampleVersion.majorMinor(VERSION);
129126

130127
private EngineDeckV2Example() {
131128
}
@@ -912,31 +909,6 @@ private static String snapshotDate(EngineDeckData.BenchRun bench) {
912909
return timestamp.substring(0, Math.min(10, timestamp.length()));
913910
}
914911

915-
/**
916-
* Reduces a version to its {@code major.minor} line for display.
917-
*
918-
* @param version the resolved build version, possibly a qualifier or {@code "dev"}
919-
* @return {@code "2.1"} for {@code "2.1.0"} / {@code "2.1.0-SNAPSHOT"}; the input
920-
* unchanged when it carries no dotted numeric prefix
921-
*/
922-
private static String majorMinor(String version) {
923-
Matcher matcher = Pattern.compile("^(\\d+)\\.(\\d+)").matcher(version);
924-
return matcher.find() ? matcher.group(1) + "." + matcher.group(2) : version;
925-
}
926-
927-
private static String loadVersion() {
928-
Properties banner = new Properties();
929-
try (InputStream in = EngineDeckV2Example.class.getResourceAsStream("/banner.properties")) {
930-
if (in != null) {
931-
banner.load(in);
932-
}
933-
} catch (IOException ignored) {
934-
// Fall through to the development label below.
935-
}
936-
String value = banner.getProperty("version");
937-
return value == null || value.isBlank() || value.startsWith("@") ? "dev" : value.trim();
938-
}
939-
940912
private static SvgIcon logo() {
941913
return icon("logo");
942914
}

examples/src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@
2323
import com.demcha.examples.support.theme.BusinessTheme;
2424
import com.demcha.compose.font.FontName;
2525
import com.demcha.examples.support.ExampleOutputPaths;
26+
import com.demcha.examples.support.ExampleVersion;
27+
import com.demcha.examples.support.PerfBaseline;
2628

2729
import java.nio.file.Path;
30+
import java.util.Locale;
2831

2932
/**
3033
* Kitchen-sink showcase combining the canonical surface end-to-end —
@@ -34,10 +37,17 @@
3437
* QR + Code 128 barcodes, document metadata, and header / footer chrome.
3538
*
3639
* <p>The rendered PDF reads like a fictional "GraphCompose Q2 Sample
37-
* Report" — three pages of designed content meant to look at a glance
40+
* Report" — designed content meant to look at a glance
3841
* like the kind of business document GraphCompose was built to
3942
* generate, not a feature checklist. Use it as a reference when
4043
* composing your own multi-page documents.</p>
44+
*
45+
* <p>The narrative is deliberately undated. This file is regenerated and
46+
* re-committed on every release, so anything that names a version, a date or a
47+
* measurement would go stale between one release and the next — as it did. What
48+
* survives is either sample copy that cannot age or a figure read at render time
49+
* from {@link com.demcha.examples.support.PerfBaseline} and
50+
* {@link com.demcha.examples.support.ExampleVersion}.</p>
4151
*/
4252
public final class MasterShowcaseExample {
4353
private static final BusinessTheme THEME = BusinessTheme.modern();
@@ -90,7 +100,7 @@ static void compose(DocumentSession document) {
90100
document.metadata(DocumentMetadata.builder()
91101
.title("GraphCompose master showcase")
92102
.author("Jordan Rivera")
93-
.subject("Comprehensive end-to-end demo of the v1.5 canonical surface")
103+
.subject("Comprehensive end-to-end demo of the canonical document surface")
94104
.keywords("graphcompose, showcase, business, theme, rich-text, table, shape, transform, barcode")
95105
.creator("GraphCompose Examples")
96106
.producer("GraphCompose")
@@ -109,7 +119,7 @@ static void compose(DocumentSession document) {
109119

110120
document.footer(DocumentHeaderFooter.builder()
111121
.zone(DocumentHeaderFooterZone.FOOTER)
112-
.leftText("v1.5 — \"intuitive\" release")
122+
.leftText("GraphCompose " + ExampleVersion.currentLine() + " — sample report")
113123
.rightText("Page {page} of {pages}")
114124
.fontSize(9f)
115125
.textColor(MUTED)
@@ -165,16 +175,17 @@ static void compose(DocumentSession document) {
165175
.plain("Status: ")
166176
.bold("On track")
167177
.plain(" — ")
168-
.accent("675 / 675 tests green", BRAND)
169-
.plain(" — Q2 release window confirmed for ")
170-
.underline("June 2026")
178+
.accent("every gate green", BRAND)
179+
.plain(" — release window confirmed for the ")
180+
.underline("current quarter")
171181
.plain("."))
172182
.addRich(rich -> rich
173183
.plain("Performance: ")
174184
.color("invoice-template", BRAND_DEEP)
175-
.plain(" 13.4 ms avg, 75 docs/sec; ")
185+
.plain(" " + speed("invoice-template") + "; ")
176186
.color("feature-rich", BRAND_DEEP)
177-
.plain(" 36.8 ms avg, 27 docs/sec.")))
187+
.plain(" " + speed("feature-rich") + ". One machine, "
188+
+ PerfBaseline.get().capturedOn() + ".")))
178189
.addSection("Seal", section -> section
179190
.padding(DocumentInsets.of(2))
180191
.addCircle(118, BRAND, circle -> circle
@@ -187,7 +198,7 @@ static void compose(DocumentSession document) {
187198
style(FontName.HELVETICA_BOLD, 13,
188199
DocumentTextDecoration.BOLD,
189200
DocumentColor.WHITE)))
190-
.position(label("Q2 / 2026",
201+
.position(label("SAMPLE",
191202
style(FontName.HELVETICA_BOLD, 7.5,
192203
DocumentTextDecoration.BOLD,
193204
SOFT_GOLD)),
@@ -222,7 +233,7 @@ static void compose(DocumentSession document) {
222233
.textStyle(THEME.text().h2())
223234
.margin(DocumentInsets.zero()))
224235
.addRich(rich -> rich
225-
.plain("v1.5 lands the cinematic features that turn GraphCompose from \"tidy PDF layouter\" into a designed-document engine: ")
236+
.plain("The features below are what turn GraphCompose from a \"tidy PDF layouter\" into a designed-document engine: ")
226237
.bold("shape-as-container with clip path")
227238
.plain(", ")
228239
.bold("rotate / scale + per-layer z-index")
@@ -314,7 +325,7 @@ static void compose(DocumentSession document) {
314325
.addSection("Card3", section -> highlightCard(section,
315326
"Transformable mixin",
316327
"rotate / scale on every shape",
317-
"v1.5 extends Transformable<T> to every leaf builder so any shape rotates around its placement centre.")))
328+
"Transformable<T> reaches every leaf builder, so any shape rotates around its placement centre.")))
318329

319330
// ───── Action items + status legend ─────
320331
.addSection("ActionItems", section -> section
@@ -328,9 +339,9 @@ static void compose(DocumentSession document) {
328339
.textStyle(THEME.text().h2())
329340
.margin(DocumentInsets.zero()))
330341
.addRich(rich -> rich
331-
.plain("• Tag ")
332-
.bold("v1.5.0")
333-
.plain(" on main once develop is merged through PR. ")
342+
.plain("• Publish the release notes once the branch ")
343+
.bold("merges through review")
344+
.plain(". ")
334345
.accent("Owner: maintainer", BRAND)
335346
.plain("."))
336347
.addRich(rich -> rich
@@ -360,7 +371,7 @@ static void compose(DocumentSession document) {
360371
.padding(DocumentInsets.zero())
361372
.addBarcode(barcode -> barcode
362373
.code128()
363-
.data("GC-MASTER-Q2-2026")
374+
.data("GC-MASTER-SAMPLE")
364375
.foreground(BRAND)
365376
.size(180, 36))))
366377
.build();
@@ -403,6 +414,23 @@ private static DocumentNode label(String text, DocumentTextStyle style) {
403414
.build();
404415
}
405416

417+
/**
418+
* One scenario's measured speed, phrased for the report.
419+
*
420+
* <p>Restates the committed baseline rather than a literal, so the figure
421+
* moves when the measurement does instead of when somebody remembers.</p>
422+
*
423+
* @param scenario benchmark scenario name
424+
* @return e.g. {@code "6.6 ms avg, 152 docs/sec"}, or a plain note when the
425+
* baseline does not carry the scenario
426+
*/
427+
private static String speed(String scenario) {
428+
return PerfBaseline.get().scenario(scenario)
429+
.map(measured -> String.format(Locale.ROOT, "%.1f ms avg, %.0f docs/sec",
430+
measured.avgMillis(), measured.docsPerSecond()))
431+
.orElse("not measured");
432+
}
433+
406434
private static DocumentTextStyle label() {
407435
return DocumentTextStyle.builder()
408436
.fontName(FontName.HELVETICA_BOLD)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.demcha.examples.support;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.util.Properties;
6+
import java.util.regex.Matcher;
7+
import java.util.regex.Pattern;
8+
9+
/**
10+
* The reactor version, for examples that print it.
11+
*
12+
* <p>Read from the filtered {@code banner.properties} rather than written out,
13+
* because example documents are regenerated on every release: a literal keeps
14+
* announcing whichever line it was typed on. Shared so the value cannot drift
15+
* between the documents that show it.</p>
16+
*/
17+
public final class ExampleVersion {
18+
19+
private static final Pattern MAJOR_MINOR = Pattern.compile("^(\\d+)\\.(\\d+)");
20+
21+
private static final String CURRENT = load();
22+
23+
private ExampleVersion() {
24+
}
25+
26+
/**
27+
* The full reactor version, or {@code "dev"} when the resource is absent or
28+
* unfiltered (running straight from sources).
29+
*
30+
* @return version string
31+
*/
32+
public static String current() {
33+
return CURRENT;
34+
}
35+
36+
/**
37+
* The {@code major.minor} of {@link #current()}, for prose that names a line
38+
* rather than a patch.
39+
*
40+
* @return the leading {@code major.minor}, or the version unchanged when it
41+
* carries no dotted numeric prefix
42+
*/
43+
public static String currentLine() {
44+
return majorMinor(CURRENT);
45+
}
46+
47+
/**
48+
* Reduces a version to its {@code major.minor} prefix.
49+
*
50+
* @param version version string to reduce
51+
* @return the leading {@code major.minor}, or {@code version} unchanged when
52+
* it carries no dotted numeric prefix
53+
*/
54+
public static String majorMinor(String version) {
55+
Matcher matcher = MAJOR_MINOR.matcher(version);
56+
return matcher.find() ? matcher.group(1) + "." + matcher.group(2) : version;
57+
}
58+
59+
private static String load() {
60+
Properties banner = new Properties();
61+
try (InputStream in = ExampleVersion.class.getResourceAsStream("/banner.properties")) {
62+
if (in != null) {
63+
banner.load(in);
64+
}
65+
} catch (IOException ignored) {
66+
// Fall through to the development label below.
67+
}
68+
String value = banner.getProperty("version");
69+
return value == null || value.isBlank() || value.startsWith("@") ? "dev" : value.trim();
70+
}
71+
}

0 commit comments

Comments
 (0)