Skip to content

Commit 99f693d

Browse files
committed
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.
1 parent e3ecaa8 commit 99f693d

10 files changed

Lines changed: 226 additions & 36 deletions

File tree

-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 & 22 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
}
@@ -919,23 +916,7 @@ private static String snapshotDate(EngineDeckData.BenchRun bench) {
919916
* @return {@code "2.1"} for {@code "2.1.0"} / {@code "2.1.0-SNAPSHOT"}; the input
920917
* unchanged when it carries no dotted numeric prefix
921918
*/
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-
}
926919

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-
}
939920

940921
private static SvgIcon logo() {
941922
return icon("logo");

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

Lines changed: 42 additions & 14 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 —
@@ -38,6 +41,13 @@
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+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.demcha.examples.support;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
6+
import java.io.InputStream;
7+
import java.io.UncheckedIOException;
8+
import java.util.HashMap;
9+
import java.util.Map;
10+
import java.util.Optional;
11+
12+
/**
13+
* The committed current-speed baseline, for examples that quote engine speed.
14+
*
15+
* <p>Reads {@code baselines/current-speed-full.json} — the same file the local
16+
* perf gate scores against, put on the classpath by the examples module rather
17+
* than copied, so there is one number and not two that drift. An example that
18+
* quotes a figure therefore restates measured data instead of asserting a
19+
* literal somebody typed once.</p>
20+
*
21+
* <p>The figures are one machine's median over several runs. Absolute
22+
* milliseconds do not travel between machines, so anything rendering them
23+
* should show {@link #capturedOn()} beside them and let the reader judge.</p>
24+
*/
25+
public final class PerfBaseline {
26+
27+
private static final String RESOURCE = "/baselines/current-speed-full.json";
28+
29+
private static final PerfBaseline INSTANCE = load();
30+
31+
private final String capturedOn;
32+
private final Map<String, Scenario> scenarios;
33+
34+
private PerfBaseline(String capturedOn, Map<String, Scenario> scenarios) {
35+
this.capturedOn = capturedOn;
36+
this.scenarios = scenarios;
37+
}
38+
39+
/**
40+
* One scenario's measured speed.
41+
*
42+
* @param avgMillis average render latency in milliseconds
43+
* @param docsPerSecond documents rendered per second
44+
*/
45+
public record Scenario(double avgMillis, double docsPerSecond) {
46+
}
47+
48+
/**
49+
* The bundled baseline.
50+
*
51+
* @return the loaded baseline
52+
*/
53+
public static PerfBaseline get() {
54+
return INSTANCE;
55+
}
56+
57+
/**
58+
* The date the baseline was measured, as {@code yyyy-MM-dd}.
59+
*
60+
* @return capture date, or {@code "undated"} when the file carries no timestamp
61+
*/
62+
public String capturedOn() {
63+
return capturedOn;
64+
}
65+
66+
/**
67+
* Looks up one scenario by its benchmark name.
68+
*
69+
* @param scenario scenario name, e.g. {@code invoice-template}
70+
* @return the scenario's figures, empty when the baseline does not carry it
71+
*/
72+
public Optional<Scenario> scenario(String scenario) {
73+
return Optional.ofNullable(scenarios.get(scenario));
74+
}
75+
76+
private static PerfBaseline load() {
77+
try (InputStream in = PerfBaseline.class.getResourceAsStream(RESOURCE)) {
78+
if (in == null) {
79+
return new PerfBaseline("undated", Map.of());
80+
}
81+
JsonNode root = new ObjectMapper().readTree(in);
82+
Map<String, Scenario> scenarios = new HashMap<>();
83+
for (JsonNode row : root.path("latency")) {
84+
scenarios.put(row.path("scenario").asText(),
85+
new Scenario(row.path("avgMillis").asDouble(),
86+
row.path("docsPerSecond").asDouble()));
87+
}
88+
String timestamp = root.path("timestamp").asText("");
89+
return new PerfBaseline(
90+
timestamp.length() >= 10 ? timestamp.substring(0, 10) : "undated",
91+
Map.copyOf(scenarios));
92+
} catch (java.io.IOException e) {
93+
throw new UncheckedIOException("Cannot read " + RESOURCE, e);
94+
}
95+
}
96+
}
-4 Bytes
Binary file not shown.
-759 Bytes
Binary file not shown.
3.55 KB
Loading

0 commit comments

Comments
 (0)