files = walk.filter(Files::isRegularFile).toList();
+ for (Path file : files) {
+ Files.delete(file);
+ }
+ }
+ }
+
private static String relativeUrl(Path showcaseRoot, Path target, Path docsRoot) {
Path rel = docsRoot.relativize(target);
return rel.toString().replace('\\', '/');
diff --git a/examples/src/test/java/com/demcha/examples/GenerateAllExamplesSmokeTest.java b/examples/src/test/java/com/demcha/examples/GenerateAllExamplesSmokeTest.java
index 3ed0cb428..2832f9588 100644
--- a/examples/src/test/java/com/demcha/examples/GenerateAllExamplesSmokeTest.java
+++ b/examples/src/test/java/com/demcha/examples/GenerateAllExamplesSmokeTest.java
@@ -2,38 +2,98 @@
import static org.assertj.core.api.Assertions.assertThat;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
/**
* CI-coverage smoke test: runs {@link GenerateAllExamples#main(String[])}
- * end-to-end and verifies it completes without exceptions.
+ * end-to-end and then asserts against what it actually wrote.
*
* Each individual example invokes the full canonical pipeline
* ({@code DocumentSession} -> {@code DocumentDsl} -> layout compile ->
- * PDF render) and writes a PDF to {@code examples/target/generated-pdfs}.
+ * render) and writes its output to {@code examples/target/generated-pdfs}.
* If any example breaks at runtime, this test fails — closing the
* audit's L4 gap that flagged {@code GenerateAllExamples} as never
* exercised by CI.
*
+ * Completing without throwing is necessary but not sufficient, so the
+ * assertions below read the output tree rather than the exit path.
+ *
* Lives under the sibling {@code examples/} Maven module so the
* smoke runner's JUnit dependency does not pollute the main project's
* test classpath.
*/
class GenerateAllExamplesSmokeTest {
- @Test
- void generateAllExamplesCompletesWithoutErrors() throws Exception {
- // The main method prints "Generated: " lines for each
- // example. We assert that it returns normally — any
- // RuntimeException, IOException, or template wiring break
- // surfaces here as a CI failure.
+ private static final Path GENERATED_ROOT = Path.of("target", "generated-pdfs");
+
+ @BeforeAll
+ static void generateEveryExample() throws Exception {
GenerateAllExamples.main(new String[0]);
+ }
+
+ @Test
+ void generateAllExamplesWritesPdfs() {
+ assertThat(GENERATED_ROOT)
+ .describedAs("GenerateAllExamples must write its output under %s", GENERATED_ROOT)
+ .isDirectoryRecursivelyContaining("glob:**/*.pdf");
+ }
+
+ /**
+ * Every generated deck needs a PDF of the same name beside it.
+ *
+ * {@code ShowcaseSync} builds one card per PDF and copies a {@code .pptx} only
+ * as the same-named companion of one. A deck generated without its PDF twin is
+ * therefore invisible to the published site: it lands in {@code target}, never
+ * reaches {@code web/showcase}, and its hand-written {@code ShowcaseMetadata} entry
+ * goes silently unused. That is exactly what happened to the Maven banner, whose
+ * example exposes both {@code generate()} and {@code generatePdf()} while only the
+ * first was wired into the runner.
+ */
+ @Test
+ void everyGeneratedDeckHasAPdfTwin() throws IOException {
+ Set orphanDecks = new TreeSet<>();
+ try (Stream walk = Files.walk(GENERATED_ROOT)) {
+ List decks = walk
+ .filter(path -> path.toString().endsWith(".pptx"))
+ .toList();
+ for (Path deck : decks) {
+ String fileName = deck.getFileName().toString();
+ String stem = fileName.substring(0, fileName.length() - ".pptx".length());
+ if (!Files.isRegularFile(deck.resolveSibling(stem + ".pdf"))) {
+ orphanDecks.add(GENERATED_ROOT.relativize(deck).toString().replace('\\', '/'));
+ }
+ }
+ }
+
+ assertThat(orphanDecks)
+ .describedAs("a .pptx with no same-named .pdf beside it never reaches the "
+ + "published showcase — ShowcaseSync keys every card on a PDF and "
+ + "copies a deck only as that PDF's companion")
+ .isEmpty();
+ }
+
+ /** Guards against a runner that silently narrows to one corner of the catalogue. */
+ @Test
+ void generatedCatalogueCoversEveryCategory() throws IOException {
+ try (Stream walk = Files.walk(GENERATED_ROOT)) {
+ Set categories = walk
+ .filter(path -> path.toString().endsWith(".pdf"))
+ .map(path -> GENERATED_ROOT.relativize(path).getName(0).toString())
+ .collect(Collectors.toCollection(TreeSet::new));
- // Sanity: at least one PDF should have been written. Each
- // example resolves its own output path inside generate(), so we
- // do not assert a fixed file list — that would couple this
- // smoke test to the example list. Reaching this assertion at
- // all means main() completed.
- assertThat(true).isTrue();
+ assertThat(categories)
+ .describedAs("the runner should cover the whole catalogue, not one corner")
+ .contains("templates", "features", "flagships");
+ }
}
}
diff --git a/scripts/cut-release.ps1 b/scripts/cut-release.ps1
index 60ab7953e..b1d6da09b 100644
--- a/scripts/cut-release.ps1
+++ b/scripts/cut-release.ps1
@@ -560,6 +560,7 @@ function Run-ShowcaseSync {
# lifecycle phase. Wrapping the whole token in quotes preserves it
# as a single literal argument.
$execProp = '"-Dexec.mainClass=com.demcha.examples.support.ShowcaseSync"'
+ $generateProp = '"-Dexec.mainClass=com.demcha.examples.GenerateAllExamples"'
# The examples module depends on these bumped SNAPSHOT siblings (not on
# Central); each must be installed before exec:java can resolve them. Keep
# this list in lockstep with examples/pom.xml — a module the examples depend
@@ -574,6 +575,7 @@ function Run-ShowcaseSync {
foreach ($modulePom in $exampleSnapshotSiblings) {
Write-Host " [DRY RUN] $mvnw -B -ntp -DskipTests install -f $modulePom" -ForegroundColor Yellow
}
+ Write-Host " [DRY RUN] $mvnw -B -ntp -f examples/pom.xml -DskipTests clean compile exec:java $generateProp" -ForegroundColor Yellow
Write-Host " [DRY RUN] $mvnw -B -ntp -f examples/pom.xml -DskipTests compile exec:java $execProp" -ForegroundColor Yellow
return
}
@@ -608,6 +610,23 @@ function Run-ShowcaseSync {
throw "Install $modulePom failed (exit $LASTEXITCODE)"
}
}
+ # Regenerate the catalogue before syncing it. ShowcaseSync only COPIES what is
+ # already in examples/target/generated-pdfs, so without this the published site
+ # is whatever happens to be sitting in the maintainer's target directory from
+ # earlier commands — and on a clean checkout there is nothing there at all.
+ # `clean` is the point: it drops artifacts left by tests or by examples that
+ # have since been renamed or deleted, so the published tree is a function of
+ # the tag rather than of shell history. This runs after Step 1 bumped the poms
+ # and Step 3 rewrote GH_BASE, so version-stamped renders carry the new version.
+ Write-Host " > $mvnw -B -ntp -f examples/pom.xml -DskipTests clean compile exec:java $generateProp" -ForegroundColor DarkGray
+ & $mvnw -B -ntp -f examples/pom.xml -DskipTests clean compile exec:java $generateProp 2>&1 | ForEach-Object {
+ if ($_ -match 'BUILD SUCCESS|BUILD FAILURE|ERROR') {
+ Write-Host " $_" -ForegroundColor DarkGray
+ }
+ }
+ if ($LASTEXITCODE -ne 0) {
+ throw "GenerateAllExamples failed (exit $LASTEXITCODE)"
+ }
# `compile` before exec:java is REQUIRED: Step 3 rewrote ShowcaseMetadata.GH_BASE
# to /blob/, and exec:java runs the COMPILED class. Without recompiling it here,
# ShowcaseSync would emit examples.json with the previous release's "View Code" links
diff --git a/web/examples.json b/web/examples.json
index 7f59d60ce..817a5e353 100644
--- a/web/examples.json
+++ b/web/examples.json
@@ -178,12 +178,12 @@
},
{
"id": "proposal-modern-v2",
- "title": "Proposal Modern V2",
- "description": "Generated showcase for templates / proposal.",
- "tags": ["templates", "proposal"],
+ "title": "Modern Proposal",
+ "description": "The ModernProposal preset composed straight from its document spec — cover, scope sections and pricing table themed through BrandTheme.",
+ "tags": ["proposal"],
"pdf": "showcase/pdf/templates/proposal/proposal-modern-v2.pdf",
"screenshot": "showcase/screenshots/templates/proposal/proposal-modern-v2.png",
- "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples"
+ "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/proposal/v2/ModernProposalV2Example.java"
}
]
},
@@ -202,12 +202,12 @@
},
{
"id": "invoice-modern-v2",
- "title": "Invoice Modern V2",
- "description": "Generated showcase for templates / invoice.",
- "tags": ["templates", "invoice"],
+ "title": "Modern Invoice",
+ "description": "The ModernInvoice preset composed straight from an InvoiceDocumentSpec — line items, totals and payment block driven by the BrandTheme rather than per-document styling.",
+ "tags": ["invoice"],
"pdf": "showcase/pdf/templates/invoice/invoice-modern-v2.pdf",
"screenshot": "showcase/screenshots/templates/invoice/invoice-modern-v2.png",
- "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples"
+ "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/templates/invoice/v2/ModernInvoiceV2Example.java"
}
]
},
@@ -403,12 +403,12 @@
},
{
"id": "inline-code-column-wrap",
- "title": "Inline Code Column Wrap",
- "description": "Generated showcase for features / tables.",
- "tags": ["features", "tables"],
+ "title": "Inline Code in Narrow Columns",
+ "description": "Inline-code chips inside a fixed-width column — how a token too wide for its cell wraps instead of overflowing the column edge.",
+ "tags": ["tables", "text"],
"pdf": "showcase/pdf/features/tables/inline-code-column-wrap.pdf",
"screenshot": "showcase/screenshots/features/tables/inline-code-column-wrap.png",
- "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples"
+ "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/tables/InlineCodeColumnWrapExample.java"
},
{
"id": "table-advanced",
@@ -859,12 +859,12 @@
},
{
"id": "poetry-title",
- "title": "Poetry Title",
- "description": "Generated showcase for features / title.",
- "tags": ["features", "title"],
+ "title": "Poetry Title Page",
+ "description": "A centred title page with generous vertical rhythm — margins, alignment and spacing carrying the composition instead of decoration.",
+ "tags": ["title"],
"pdf": "showcase/pdf/features/title/poetry-title.pdf",
"screenshot": "showcase/screenshots/features/title/poetry-title.png",
- "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples"
+ "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/features/title/PoetryTitlePageExample.java"
}
]
}
@@ -900,12 +900,12 @@
},
{
"id": "engine-deck-v2",
- "title": "Engine Deck V2",
- "description": "Generated showcase for flagships / default.",
- "tags": ["flagships", "default"],
+ "title": "Engine Deck — Module First",
+ "description": "The landscape deck the README banner is cut from: the 2.0 module graph, native vector charts, and comparative benchmark figures read from the committed snapshot at render time.",
+ "tags": ["flagship", "showcase"],
"pdf": "showcase/pdf/flagships/default/engine-deck-v2.pdf",
"screenshot": "showcase/screenshots/flagships/default/engine-deck-v2.png",
- "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples"
+ "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/flagships/EngineDeckV2Example.java"
},
{
"id": "feature-catalog",
@@ -926,6 +926,16 @@
"screenshot": "showcase/screenshots/flagships/default/financial-report.png",
"code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/flagships/FinancialReportExample.java"
},
+ {
+ "id": "linkedin-carousel",
+ "title": "LinkedIn Carousel",
+ "description": "A six-slide 4:5 carousel sized for a LinkedIn document post, typeset for a phone. Every figure is read at render time — the version from the filtered properties, the timings from the committed benchmark snapshot.",
+ "tags": ["flagship", "showcase"],
+ "pdf": "showcase/pdf/flagships/default/linkedin-carousel.pdf",
+ "pptx": "showcase/pptx/flagships/default/linkedin-carousel.pptx",
+ "screenshot": "showcase/screenshots/flagships/default/linkedin-carousel.png",
+ "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/flagships/LinkedInCarouselExample.java"
+ },
{
"id": "master-showcase",
"title": "Master Showcase",
@@ -936,6 +946,16 @@
"screenshot": "showcase/screenshots/flagships/default/master-showcase.png",
"code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java"
},
+ {
+ "id": "maven-banner",
+ "title": "Maven Central Banner",
+ "description": "A single-slide brand banner emitted through the PPTX backend — gradient, rounded panels, native paths and text frames arriving in PowerPoint as an editable copy of the rendered page.",
+ "tags": ["flagship", "showcase", "pptx"],
+ "pdf": "showcase/pdf/flagships/default/maven-banner.pdf",
+ "pptx": "showcase/pptx/flagships/default/maven-banner.pptx",
+ "screenshot": "showcase/screenshots/flagships/default/maven-banner.png",
+ "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/flagships/MavenBannerPptxExample.java"
+ },
{
"id": "module-first-profile",
"title": "Module-First Authoring",
@@ -945,6 +965,16 @@
"screenshot": "showcase/screenshots/flagships/default/module-first-profile.png",
"code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/flagships/ModuleFirstFileExample.java"
},
+ {
+ "id": "social-card",
+ "title": "Social Preview Card",
+ "description": "The repository's 1280x640 social preview, itself a GraphCompose document — one sheet resolving into a portrait page and a 16:9 slide from the same content, so the card cannot drift from the palette and wordmark it is drawn with.",
+ "tags": ["flagship", "showcase"],
+ "pdf": "showcase/pdf/flagships/default/social-card.pdf",
+ "pptx": "showcase/pptx/flagships/default/social-card.pptx",
+ "screenshot": "showcase/screenshots/flagships/default/social-card.png",
+ "code": "https://github.com/DemchaAV/GraphCompose/blob/develop/examples/src/main/java/com/demcha/examples/flagships/SocialCardExample.java"
+ },
{
"id": "twin-output",
"title": "Twin Output",
diff --git a/web/showcase/pdf/features/chrome/pdf-chrome.pdf b/web/showcase/pdf/features/chrome/pdf-chrome.pdf
index b0df47089..aa10c0999 100644
Binary files a/web/showcase/pdf/features/chrome/pdf-chrome.pdf and b/web/showcase/pdf/features/chrome/pdf-chrome.pdf differ
diff --git a/web/showcase/pdf/features/shapes/shape-container.pdf b/web/showcase/pdf/features/shapes/shape-container.pdf
index a8b3a74f7..a9b31e9bc 100644
Binary files a/web/showcase/pdf/features/shapes/shape-container.pdf and b/web/showcase/pdf/features/shapes/shape-container.pdf differ
diff --git a/web/showcase/pdf/features/tables/inline-code-column-wrap.pdf b/web/showcase/pdf/features/tables/inline-code-column-wrap.pdf
index e74a7aaa5..e23ad70a6 100644
Binary files a/web/showcase/pdf/features/tables/inline-code-column-wrap.pdf and b/web/showcase/pdf/features/tables/inline-code-column-wrap.pdf differ
diff --git a/web/showcase/pdf/features/text/section-presets.pdf b/web/showcase/pdf/features/text/section-presets.pdf
index 2978d4717..57c8d8e3a 100644
Binary files a/web/showcase/pdf/features/text/section-presets.pdf and b/web/showcase/pdf/features/text/section-presets.pdf differ
diff --git a/web/showcase/pdf/features/themes/invoice-custom-theme.pdf b/web/showcase/pdf/features/themes/invoice-custom-theme.pdf
deleted file mode 100644
index b8526be12..000000000
Binary files a/web/showcase/pdf/features/themes/invoice-custom-theme.pdf and /dev/null differ
diff --git a/web/showcase/pdf/features/title/poetry-title.pdf b/web/showcase/pdf/features/title/poetry-title.pdf
index c72b3e84b..87151967e 100644
Binary files a/web/showcase/pdf/features/title/poetry-title.pdf and b/web/showcase/pdf/features/title/poetry-title.pdf differ
diff --git a/web/showcase/pdf/flagships/default/engine-deck-v2.pdf b/web/showcase/pdf/flagships/default/engine-deck-v2.pdf
index ede78caf8..b0ba395e4 100644
Binary files a/web/showcase/pdf/flagships/default/engine-deck-v2.pdf and b/web/showcase/pdf/flagships/default/engine-deck-v2.pdf differ
diff --git a/web/showcase/pdf/flagships/default/linkedin-carousel.pdf b/web/showcase/pdf/flagships/default/linkedin-carousel.pdf
new file mode 100644
index 000000000..5dbeb7396
Binary files /dev/null and b/web/showcase/pdf/flagships/default/linkedin-carousel.pdf differ
diff --git a/web/showcase/pdf/flagships/default/maven-banner.pdf b/web/showcase/pdf/flagships/default/maven-banner.pdf
new file mode 100644
index 000000000..0a9213c48
Binary files /dev/null and b/web/showcase/pdf/flagships/default/maven-banner.pdf differ
diff --git a/web/showcase/pdf/flagships/default/social-card.pdf b/web/showcase/pdf/flagships/default/social-card.pdf
new file mode 100644
index 000000000..d0592c7c7
Binary files /dev/null and b/web/showcase/pdf/flagships/default/social-card.pdf differ
diff --git a/web/showcase/pdf/templates/invoice/invoice-modern-v2.pdf b/web/showcase/pdf/templates/invoice/invoice-modern-v2.pdf
index 49edc4016..c9e348018 100644
Binary files a/web/showcase/pdf/templates/invoice/invoice-modern-v2.pdf and b/web/showcase/pdf/templates/invoice/invoice-modern-v2.pdf differ
diff --git a/web/showcase/pdf/templates/invoice/invoice.pdf b/web/showcase/pdf/templates/invoice/invoice.pdf
deleted file mode 100644
index 91cb652c6..000000000
Binary files a/web/showcase/pdf/templates/invoice/invoice.pdf and /dev/null differ
diff --git a/web/showcase/pdf/templates/proposal/project-proposal-cinematic.pdf b/web/showcase/pdf/templates/proposal/project-proposal-cinematic.pdf
index 5bdd45d03..ea4a0a0f5 100644
Binary files a/web/showcase/pdf/templates/proposal/project-proposal-cinematic.pdf and b/web/showcase/pdf/templates/proposal/project-proposal-cinematic.pdf differ
diff --git a/web/showcase/pdf/templates/proposal/proposal-cinematic.pdf b/web/showcase/pdf/templates/proposal/proposal-cinematic.pdf
index db9cb6789..30ccf8ac9 100644
Binary files a/web/showcase/pdf/templates/proposal/proposal-cinematic.pdf and b/web/showcase/pdf/templates/proposal/proposal-cinematic.pdf differ
diff --git a/web/showcase/pdf/templates/proposal/proposal-modern-v2.pdf b/web/showcase/pdf/templates/proposal/proposal-modern-v2.pdf
index 4eabcf925..ada0d7970 100644
Binary files a/web/showcase/pdf/templates/proposal/proposal-modern-v2.pdf and b/web/showcase/pdf/templates/proposal/proposal-modern-v2.pdf differ
diff --git a/web/showcase/pdf/templates/proposal/proposal.pdf b/web/showcase/pdf/templates/proposal/proposal.pdf
deleted file mode 100644
index 3d18c951b..000000000
Binary files a/web/showcase/pdf/templates/proposal/proposal.pdf and /dev/null differ
diff --git a/web/showcase/pptx/flagships/default/linkedin-carousel.pptx b/web/showcase/pptx/flagships/default/linkedin-carousel.pptx
new file mode 100644
index 000000000..7e0602228
Binary files /dev/null and b/web/showcase/pptx/flagships/default/linkedin-carousel.pptx differ
diff --git a/web/showcase/pptx/flagships/default/maven-banner.pptx b/web/showcase/pptx/flagships/default/maven-banner.pptx
new file mode 100644
index 000000000..fd0a5375b
Binary files /dev/null and b/web/showcase/pptx/flagships/default/maven-banner.pptx differ
diff --git a/web/showcase/pptx/flagships/default/social-card.pptx b/web/showcase/pptx/flagships/default/social-card.pptx
new file mode 100644
index 000000000..b9f878cc5
Binary files /dev/null and b/web/showcase/pptx/flagships/default/social-card.pptx differ
diff --git a/web/showcase/screenshots/features/chrome/pdf-chrome.png b/web/showcase/screenshots/features/chrome/pdf-chrome.png
index 049c5b329..9fc770347 100644
Binary files a/web/showcase/screenshots/features/chrome/pdf-chrome.png and b/web/showcase/screenshots/features/chrome/pdf-chrome.png differ
diff --git a/web/showcase/screenshots/features/themes/invoice-custom-theme.png b/web/showcase/screenshots/features/themes/invoice-custom-theme.png
deleted file mode 100644
index 2d9ca7366..000000000
Binary files a/web/showcase/screenshots/features/themes/invoice-custom-theme.png and /dev/null differ
diff --git a/web/showcase/screenshots/flagships/default/engine-deck-v2.png b/web/showcase/screenshots/flagships/default/engine-deck-v2.png
index b7f2e8d07..d4fb6632b 100644
Binary files a/web/showcase/screenshots/flagships/default/engine-deck-v2.png and b/web/showcase/screenshots/flagships/default/engine-deck-v2.png differ
diff --git a/web/showcase/screenshots/flagships/default/linkedin-carousel.png b/web/showcase/screenshots/flagships/default/linkedin-carousel.png
new file mode 100644
index 000000000..2bab09202
Binary files /dev/null and b/web/showcase/screenshots/flagships/default/linkedin-carousel.png differ
diff --git a/web/showcase/screenshots/flagships/default/maven-banner.png b/web/showcase/screenshots/flagships/default/maven-banner.png
new file mode 100644
index 000000000..901995ab8
Binary files /dev/null and b/web/showcase/screenshots/flagships/default/maven-banner.png differ
diff --git a/web/showcase/screenshots/flagships/default/social-card.png b/web/showcase/screenshots/flagships/default/social-card.png
new file mode 100644
index 000000000..999ddd86f
Binary files /dev/null and b/web/showcase/screenshots/flagships/default/social-card.png differ
diff --git a/web/showcase/screenshots/templates/invoice/invoice.png b/web/showcase/screenshots/templates/invoice/invoice.png
deleted file mode 100644
index b4f3168c1..000000000
Binary files a/web/showcase/screenshots/templates/invoice/invoice.png and /dev/null differ
diff --git a/web/showcase/screenshots/templates/proposal/proposal.png b/web/showcase/screenshots/templates/proposal/proposal.png
deleted file mode 100644
index 2dca5f466..000000000
Binary files a/web/showcase/screenshots/templates/proposal/proposal.png and /dev/null differ