From 8b5ee5dfc2a8a08c13a48b485fded7f91b6d01f5 Mon Sep 17 00:00:00 2001
From: DemchaAV
Date: Fri, 31 Jul 2026 08:36:13 +0100
Subject: [PATCH 1/2] docs: put the contributor surfaces on the 2.x vocabulary
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Several documents still described an architecture that shipped out of the
repository in 2.0, and a contributor following them wrote code that does not
compile.
CONTRIBUTING routed template authors to BusinessTheme, InvoiceTemplateV2 and
ProposalTemplateV2, and its engine-primitive section described entities,
container-growth markers and Breakable. None of those seven types exist in
src/main. Both sections now describe what is there: the layered preset stack
with create(BrandTheme), and the NodeDefinition pipeline where pagination is
declared by the definition and a splittable node compiles through
SplittableLeafCompiler. A dead link to ComputedPositionTest points at a test
that exists.
CONTRIBUTING, SECURITY and the architecture overview called the pptx module a
semantic exporter. Its primary backend is fixed-layout, in
document.backend.fixed.pptx, alongside the older semantic manifest — and a new
fragment kind has to register a handler with both fixed-layout backends, or it
renders in one output and silently vanishes from the other. The semantic
wording is left in place everywhere it is still accurate.
ROADMAP described 2.1 as in development and 2.0 as current stable, four days
after 2.1.0 shipped. The README release-status block described 2.1.1 with
2.1.0's headline. docs/README listed five superseded 1.x upgrade guides on the
main path and omitted the 2.0 migration guide entirely.
The bug-report template asked for a v1.6-era version and offered a reproduction
with no try-with-resources and no render call, so a repro pasted from it
produced nothing. It now asks for the backend, the classpath, the font source
and the generated artifact or layout snapshot. The feature-request template
pointed at BusinessTheme; the pull-request template offered a lane that does not
exist and required updating a count by hand. A new issue-template config routes
usage questions to Discussions.
Four places stated four different example totals — 63, 63, 54 and "26+" — where
the runner invokes 94. The numbers are removed rather than synchronised; they
have drifted every release since June.
The release runbook grepped the README for a test-count claim the README does
not make, so that gate always passed; it compared the gallery row count against
the example file count, two numbers long diverged; and three items pointed at a
migration path that moved. The gallery check is now a ShowcaseMetadata coverage
check, and a new item covers ROADMAP.
canonical-legacy-parity.md is refreshed rather than archived: eight live
documents link it, and its three "Planned for v1.6" rows had all shipped.
In the README, the comparison block moves below Architecture so it stops
separating the first example from the next step, What's new in 2.0 compresses to
its links, and the documentation catalogue collapses to four routes.
---
.github/ISSUE_TEMPLATE/bug_report.md | 30 ++--
.github/ISSUE_TEMPLATE/config.yml | 14 ++
.github/ISSUE_TEMPLATE/feature_request.md | 13 +-
.github/pull_request_template.md | 4 +-
CHANGELOG.md | 30 ++++
CONTRIBUTING.md | 97 ++++++-------
README.md | 143 +++++++++----------
ROADMAP.md | 18 ++-
SECURITY.md | 6 +-
SUPPORT.md | 16 ++-
docs/README.md | 23 +--
docs/architecture/canonical-legacy-parity.md | 14 +-
docs/architecture/overview.md | 4 +-
docs/contributing/release-process.md | 15 +-
examples/README.md | 4 +-
web/index.html | 6 +-
16 files changed, 251 insertions(+), 186 deletions(-)
create mode 100644 .github/ISSUE_TEMPLATE/config.yml
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index aeba6d4d3..b62e59355 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -11,25 +11,39 @@ labels: bug
## What actually happened
-
+
## How to reproduce
```java
-// Minimal, runnable Java code that triggers the issue.
-// Prefer the canonical `GraphCompose.document(...)` API.
-DocumentSession document = GraphCompose.document(Path.of("repro.pdf"))
+// Minimal, runnable Java that triggers the issue. Close the session and produce
+// the output — a repro that never renders shows nothing.
+try (DocumentSession document = GraphCompose.document(Path.of("repro.pdf"))
.pageSize(DocumentPageSize.A4)
- .create();
-// ...
+ .margin(24, 24, 24, 24)
+ .create()) {
+
+ document.pageFlow(page -> page
+ .module("Repro", module -> module.paragraph("...")));
+
+ document.buildPdf(); // or buildPptx(Path) / toPdfBytes()
+}
```
+## Generated artifact
+
+
+
## Environment
-- GraphCompose version:
+- GraphCompose version:
+- Output backend:
+- Modules on the classpath:
- Java:
- OS:
-- PDFBox:
+- Font source:
## Additional context
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..25637456e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,14 @@
+# Routes everything that is not a bug or a feature request away from the
+# issue tracker, so Issues stay a list of actionable defects and requests.
+blank_issues_enabled: false
+
+contact_links:
+ - name: Usage question
+ url: https://github.com/DemchaAV/GraphCompose/discussions
+ about: How do I render X, which artifact do I need, why does my layout do this — ask in Discussions.
+ - name: Where to go for what
+ url: https://github.com/DemchaAV/GraphCompose/blob/main/SUPPORT.md
+ about: Channels, what to read first, and what response to expect.
+ - name: Security vulnerability
+ url: https://github.com/DemchaAV/GraphCompose/security/advisories/new
+ about: Report privately through a GitHub security advisory. Never in a public issue.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 199d73cb6..558ec0646 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -16,9 +16,13 @@ labels: enhancement
## Proposed API
```java
-// Sketch the public-API shape you'd like to use.
-// Stick to the canonical surface: GraphCompose.document(...), DocumentSession,
-// document.pageFlow(...), DocumentNode + NodeDefinition, BusinessTheme.
+// Sketch the public-API shape you'd like to use. Stick to the canonical surface:
+// authoring GraphCompose.document(...) -> DocumentSession, document.pageFlow(...)
+// new visual node DocumentNode (com.demcha.compose.document.node)
+// + NodeDefinition (com.demcha.compose.document.layout)
+// theming BrandTheme (com.demcha.compose.document.templates.core.theme)
+// new output kind FixedLayoutBackendProvider (…document.backend.fixed)
+// or SemanticBackend (…document.backend.semantic)
document.pageFlow().add???(...);
```
@@ -28,6 +32,9 @@ document.pageFlow().add???(...);
## Would this be a breaking change?
+Classify the surface it touches against [docs/api-stability.md](../../docs/api-stability.md)
+(§1 defines the tiers, §4 maps each package onto one).
+
- [ ] No — additive only (new node, new builder method, new template preset).
- [ ] Possibly — affects public-record shape, deprecates an existing API, or changes default rendering behaviour.
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 436731e19..8b946ee85 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -25,7 +25,7 @@ Delete any heading you have nothing real to put under — never ship an empty pl
- command run + result, e.g. `./mvnw -B -ntp clean verify` → BUILD SUCCESS, tests, 0 failures
- the new tests and what each asserts; regenerate renders if the change is visual -->
-**Lane:** —
+**Lane:** —
Closes #
@@ -38,6 +38,6 @@ Closes #
- [ ] `./mvnw -B -ntp clean verify` passes locally — this is the **Verification** proof above.
- [ ] **Java 17 compatible** — no `getFirst()`/`getLast()`, `Thread.threadId()`, type/deconstruction `switch`, `case null, default`. (CI runs Temurin 17 / 21 / 25.)
- [ ] **Public API changed** → `CHANGELOG.md` entry under the next `## v — Planned` heading.
-- [ ] **README / examples touched** → `DocumentationCoverageTest` stays green; a new example is wired into `GenerateAllExamples` + the `examples/README.md` gallery count.
+- [ ] **README / examples touched** → `DocumentationCoverageTest` stays green; a new example is wired into `GenerateAllExamples` and given a gallery row in `examples/README.md`.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c558b8433..e3602facd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -69,6 +69,36 @@ follow semantic versioning; release dates are ISO 8601.
snippet produces, where it fails.
- **Every published module README is reachable** from the module list in the root
README; eight of the ten had no inbound link from anywhere.
+- **The release surface names the release that shipped.** `ROADMAP.md` described
+ 2.1 as in development and 2.0 as the current stable line; 2.1 is now current,
+ 2.0 is history, and `## Now` points at what follows. The README release-status
+ block described 2.1.1 with 2.1.0's headline.
+- **The documentation index carries the migration guide that applies.**
+ `docs/README.md` listed five superseded 1.x minor-to-minor upgrade guides on the
+ main path and omitted `migration/v2.0.0-modules.md` entirely. The current
+ guides are listed; the historical ones moved behind a fold.
+- **The PPTX backend is documented where it lives.** `CONTRIBUTING.md`,
+ `SECURITY.md` and the architecture overview described the pptx module as a
+ semantic exporter. Its primary backend is fixed-layout
+ (`document.backend.fixed.pptx`), alongside the older semantic manifest; a new
+ fragment kind must register a handler with both fixed-layout backends or it
+ renders in one output and vanishes from the other.
+- **Contributor instructions compile.** The "new built-in template" section
+ routed authors to `BusinessTheme`, `InvoiceTemplateV2` and `ProposalTemplateV2`
+ — none of which exist since 2.0 — and the engine-primitive section described
+ the entity/marker execution layer that 2.0 removed. Both now describe the
+ layered preset stack and the `NodeDefinition` pipeline. The bug-report template
+ asked for a 1.6-era version and carried a reproduction that never rendered; the
+ feature-request template pointed at a removed theme type; the pull-request
+ template offered a lane that no longer exists.
+- **Usage questions route to Discussions** through a new issue-template
+ `config.yml`, instead of being filed as labelled issues.
+- **Hand-maintained example counts are gone.** Four places stated four different
+ totals, none matching the catalogue.
+- **The release runbook checks things that exist.** One gate grepped the README
+ for a test-count claim the README does not make, so it always passed; another
+ compared the gallery row count against the example file count, two numbers that
+ had long diverged; three references pointed at a migration path that moved.
- **A gallery entry for a deleted example is gone.** `examples/README.md`
documented rethemeing through `InvoiceTemplateV2`, a type removed in 2.0, linked
a source file that no longer exists, and carried a committed preview for it. The
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8968910b9..5839fa84b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,9 +8,14 @@ Read these files first:
- [README.md](./README.md)
- [docs/architecture/overview.md](./docs/architecture/overview.md)
-- [docs/contributing/implementation-guide.md](./docs/contributing/implementation-guide.md)
+- [docs/architecture/package-map.md](./docs/architecture/package-map.md) — what lives in which package, and which of them are internal
- [docs/operations/benchmarks.md](./docs/operations/benchmarks.md) when you touch benchmark tooling, render hot paths, layout hot paths, or performance-facing docs
+[docs/contributing/implementation-guide.md](./docs/contributing/implementation-guide.md)
+goes deeper on the engine, but parts of it still describe the execution layer that
+2.0 removed — read `overview.md` and `package-map.md` first and treat the guide as
+background until it is rewritten.
+
They explain the current public surface, the engine/template split, and the recommended extension points.
## Java 17 baseline
@@ -29,8 +34,8 @@ When writing new code, avoid Java 21+ APIs and language constructs that don't ex
## Build and test
- The blocking validation gate for repository work is `./mvnw -B -ntp clean verify` at the repository root — the root pom is the reactor aggregator, so this builds and verifies **every module**. For a fast inner loop while iterating on the engine, scope it to the core module: `./mvnw -B -ntp verify -pl :graph-compose-core`.
-- Run the guard-focused suite with `./mvnw -B -ntp "-Dtest=EnginePdfBoundaryTest,PdfRenderInterfaceGuardTest,DocumentationCoverageTest,DocumentationExamplesTest,CanonicalSurfaceGuardTest" test`.
-- Run a focused documentation sanity check with `./mvnw -B -ntp "-Dtest=DocumentationExamplesTest" test`.
+- Run the engine-resident guard suite with `./mvnw -B -ntp "-Dtest=EnginePdfBoundaryTest,DocumentationCoverageTest,CanonicalSurfaceGuardTest,PackageMapGuardTest,VersionConsistencyGuardTest,CiGuardListGuardTest" test -pl :graph-compose-core` — the same list CI runs. Every name must live in `graph-compose-core`: Surefire drops a name that matches nothing as long as a sibling matches, so a guard that lives elsewhere would silently not run (`CiGuardListGuardTest` fails the build if one creeps in).
+- The cross-module documentation guards — `DocumentationExamplesTest` and `DocumentationSnippetCompileTest`, which compiles the literal java fences published in `docs/` — live in `graph-compose-qa`: `./mvnw -B -ntp "-Dtest=DocumentationExamplesTest,DocumentationSnippetCompileTest" test -f qa/pom.xml`. A standalone `-f qa/pom.xml` run resolves its `graph-compose-*` dependencies from `~/.m2`, not from the reactor, so run `./mvnw -B -ntp -DskipTests install` once first — otherwise it quietly tests the artifacts you last installed instead of your working tree.
- Run the local benchmark wrapper when you change performance-sensitive code or benchmark tooling: `powershell -ExecutionPolicy Bypass -File .\scripts\run-benchmarks.ps1` (Windows). To compare two branches fairly, use `scripts/ab-bench.ps1` (Windows) or the cross-platform `scripts/ab-bench.sh` (Linux/macOS/Git Bash). See [docs/operations/benchmarks.md](./docs/operations/benchmarks.md).
## How to propose changes
@@ -65,12 +70,12 @@ Almost all work targets **`develop`**, the ongoing 2.x line. The `1.x` branch ta
6. **CI runs automatically.** Active jobs:
- `Architecture and Documentation Guards` — fast canonical / engine-boundary guard tests, fail-first gate (always runs)
- `Build and run tests (JDK 17)`, `(JDK 21)`, `(JDK 25)` — full `mvnw verify` in parallel matrix across the supported JVMs
- - `Examples Generation Smoke Test` — regenerates all 54 runnable examples and uploads the PDFs as a CI artifact
+ - `Examples Generation Smoke Test` — regenerates every runnable example and uploads the PDFs as a CI artifact
- `Binary Compatibility` — PR-only japicmp diff of the `graph-compose-core` surface
- `Performance Smoke Check` — PR-only coarse benchmark to catch performance regressions
- `CI Gate` — single aggregate status check that is green when every job that ran passed
- **Selective on pull requests:** a `dorny/paths-filter` step skips the heavy jobs when a PR touches nothing that affects the build — a **docs-only PR runs the guards only**; `Binary Compatibility` runs only when the core module changed, and the `Performance Smoke Check` only when core / render-pdf / templates changed. Pushes to `develop` / `main` (and manual dispatch) always run the full gate. Point branch protection at **`CI Gate`** + **`Architecture and Documentation Guards`** rather than the individual matrix legs, so a docs-only PR is not left waiting on a skipped check.
+ **Selective on pull requests:** a `dorny/paths-filter` step skips the heavy jobs when a PR touches nothing that affects the build. Markdown counts as a build input, so a **docs-only PR still runs the reactor** — on the baseline JDK alone, and without example generation — because that is where the guards compiling the published snippets live. `Binary Compatibility` runs only when the core module changed, and the `Performance Smoke Check` only when core / render-pdf / templates changed. Pushes to `develop` / `main` (and manual dispatch) always run the full gate. Point branch protection at **`CI Gate`** + **`Architecture and Documentation Guards`** rather than the individual matrix legs, so a docs-only PR is not left waiting on a skipped check.
The PR cannot merge into a protected branch until all required checks are green.
7. **Address review comments**, then squash any fixup commits before merge. The maintainer merges through GitHub once review is complete.
@@ -111,8 +116,10 @@ The 2.0 GA shipped, so the branches now hold their long-term roles:
Canonical functional layout pipeline: `LayoutCompiler`, `BuiltInNodeDefinitions`, `TableLayoutSupport`, `PreparedNode`, `PlacedFragment`
- `render-pdf/src/main/java/com/demcha/compose/document/backend/fixed/pdf` — module **graph-compose-render-pdf**
PDF backend: `PdfFixedLayoutBackend`, fragment handlers, and the option translators that bridge canonical types to PDFBox
-- `render-docx/` and `render-pptx/` — modules **graph-compose-render-docx** / **graph-compose-render-pptx**
- Semantic exporters `DocxSemanticBackend` (Apache POI based) and `PptxSemanticBackend` (manifest skeleton), under `com.demcha.compose.document.backend.semantic.{docx,pptx}`
+- `render-pptx/src/main/java/com/demcha/compose/document/backend/fixed/pptx` — module **graph-compose-render-pptx**
+ PPTX fixed-layout backend: `PptxFixedLayoutBackend` and its handlers, consuming the same resolved `LayoutGraph` as the PDF backend. The module also carries the older `PptxSemanticBackend` manifest skeleton under `com.demcha.compose.document.backend.semantic.pptx`
+- `render-docx/` — module **graph-compose-render-docx**
+ Semantic exporter `DocxSemanticBackend` (Apache POI based), under `com.demcha.compose.document.backend.semantic.docx`
- `templates/src/main/java/com/demcha/compose/document/templates/*` — module **graph-compose-templates**
Built-in templates (CV, cover letter, invoice, proposal, weekly schedule), DTOs, themes, registries, and scene composition helpers
- `core/src/main/java/com/demcha/compose/document/showcase`
@@ -202,20 +209,10 @@ not need any of them.
- Keep `core/src/main/java/com/demcha/compose/engine/components/*` free of
`org.apache.pdfbox` and `com.demcha.compose.engine.render.pdf`
imports.
-- When you add a new fragment kind, register its handler with
- `PdfFixedLayoutBackend` and add or update dispatch coverage.
-
-Keep the entity core thin:
-
-- `Entity` stays an identity-plus-components object with compatibility
- delegates. It is not a home for new layout math or pagination
- mutation rules.
-- Geometry reads live in `EntityBounds`. Parent-container size and
- page-shift propagation live in `ParentContainerUpdater`.
-- `Entity.bounding*` and `Entity.updateParentContainer*` are
- deprecated compatibility wrappers; do not copy them into new code.
-- Render-order optimizations live in the render layer (the PDF
- fragment handlers), not in `Entity`.
+- When you add a new fragment kind, register its handler with **both**
+ fixed-layout backends — `PdfFixedLayoutBackend` and `PptxFixedLayoutBackend` —
+ and add or update dispatch coverage. A kind registered with only one renders
+ in one output and silently vanishes from the other.
### Guard rails
@@ -275,31 +272,25 @@ Reference templates to copy:
### New built-in template
-GraphCompose supports two template authoring patterns. Pick based
-on whether you're extending an existing template family or building
-a new one.
-
-**For a NEW template family from scratch** (invoice-v2,
-cover-letter-v2, report-v2, anything not yet in `cv/v2/`) — follow
-the canonical layered architecture documented in
+There is one template authoring pattern, whether you are adding a
+new family or a new preset inside an existing one: the layered
+architecture documented in
[**docs/templates/v2-layered/contributor-guide.md**](./docs/templates/v2-layered/contributor-guide.md).
Five sub-packages (`data/` / `theme/` / `components/` / `widgets/`
-/ `presets/`), each with a clear contract. CV v2
-(`com.demcha.compose.document.templates.cv`) is the reference
-implementation; read it before starting yours.
-
-**For a new preset inside an existing v1-classic family** (a new CV
-variant alongside `ModernProfessional`, a new invoice preset
-alongside `InvoiceTemplateV2`):
+/ `presets/`), each with a clear contract, over the shared
+`templates.core` layer.
-- Constructor takes a `BusinessTheme` (or `BrandTheme` for CV
- templates). Provide a no-arg overload that picks a default theme.
+- Every preset is a `public final class` — no inheritance — with a
+ `create(BrandTheme)` factory returning `DocumentTemplate`, plus a
+ no-arg overload that picks a default theme.
- Compose against `DocumentDsl` — no PDF-specific imports.
- Route every visible token through `theme.palette()` /
`theme.text()` / `theme.spacing()` / `theme.table()`.
-- Reference: `InvoiceTemplateV2`, `ProposalTemplateV2`. Read
- [docs/templates/v1-classic/authoring.md](./docs/templates/v1-classic/authoring.md) before
- starting.
+- Reference implementations:
+ `templates.cv.presets.ModernProfessional`,
+ `templates.invoice.presets.ModernInvoice`,
+ `templates.proposal.presets.ModernProposal`. Read one before
+ starting yours.
> 📚 **Map of template docs**:
> [docs/README.md](./docs/README.md#templates) lists every template
@@ -313,16 +304,17 @@ marker, a new layout system, a new render-pass session):
- Decide first whether the feature belongs on the public surface as
a `DocumentNode` instead. If yes, see "New public node" above and
- treat the engine work as plumbing, not as new public ECS surface.
-- For genuine engine primitives, add the engine content / style /
- layout component plus a backend-neutral renderable marker plus a
- backend-owned render handler.
-- Marker rule of thumb:
- - add the container-growth marker only to parent-like boxes that
- should grow because of child content
- - add `Breakable` only to entities whose own content may continue
- across pages
- - do not treat the container-growth marker as a pagination flag
+ treat the engine work as plumbing, not as new public surface.
+- For genuine engine primitives, add the `NodeDefinition` that
+ prepares and measures the node, plus a backend-owned render handler
+ per fixed-layout backend.
+- Pagination is declared by the definition, not by a marker: state the
+ split behaviour on the `NodeDefinition` itself, and compile a node
+ that can continue across pages through `SplittableLeafCompiler` so
+ the continuation indices stay monotonic.
+- A container grows from the sizes its children report during
+ `prepare`; do not add growth as a separate signal, and do not read
+ it as a pagination flag.
For text-heavy primitives, also read:
@@ -346,7 +338,7 @@ Choose the smallest tests that match the change:
- For PDF fragment-handler dispatch changes:
[PdfRenderInterfaceGuardTest.java](render-pdf/src/test/java/com/demcha/compose/engine/render/pdf/PdfRenderInterfaceGuardTest.java)
- For layout/positioning behavior:
- [ComputedPositionTest.java](./core/src/test/java/com/demcha/compose/engine/components/layout/ComputedPositionTest.java)
+ [LayoutInsetsTest.java](./core/src/test/java/com/demcha/compose/document/layout/LayoutInsetsTest.java)
- For pagination and multi-page behavior:
[PaginationEdgeCaseTest.java](qa/src/test/java/com/demcha/compose/document/api/PaginationEdgeCaseTest.java)
- For Templates v2 CV / cover-letter presets:
@@ -387,7 +379,8 @@ The repository uses these normalized package roots:
- `com.demcha.compose.document.style`, `document.table`, `document.image`, `document.output` — public value types
- `com.demcha.compose.document.layout` — canonical functional layout pipeline
- `com.demcha.compose.document.backend.fixed.pdf` — PDF fixed-layout backend
-- `com.demcha.compose.document.backend.semantic` — DOCX / PPTX semantic backends
+- `com.demcha.compose.document.backend.fixed.pptx` — PPTX fixed-layout backend (`@Beta`)
+- `com.demcha.compose.document.backend.semantic` — semantic export SPI, the DOCX exporter, and the legacy PPTX manifest
- `com.demcha.compose.document.templates` — built-in templates and data
- `com.demcha.compose.engine` — internal shared engine foundation under the canonical surface; not part of the recommended public API
- `com.demcha.compose.font` — public font registry
diff --git a/README.md b/README.md
index 17a8f0280..ff96c4455 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@
> **Release status** —
-> 🟢 **Latest stable**: [v2.1.1](https://github.com/DemchaAV/GraphCompose/releases/tag/v2.1.1) — the **PowerPoint** release: `graph-compose-render-pptx` turns the same resolved layout into an editable deck — one page per slide, geometry-identical to the PDF, text and panels as native shapes. Ships as `@Beta`. **[What each backend supports ↓](docs/architecture/backend-capability-matrix.md)**
+> 🟢 **Latest stable**: [v2.1.1](https://github.com/DemchaAV/GraphCompose/releases/tag/v2.1.1) — a patch over the **PowerPoint** release: a heading no longer strands above a block that asked to stay whole, and the documented snippets were corrected against the shipped API. `graph-compose-render-pptx` (new in 2.1.0) turns the same resolved layout into an editable deck — one page per slide, geometry-identical to the PDF, text and panels as native shapes, `@Beta`. **[What each backend supports ↓](docs/architecture/backend-capability-matrix.md)**
> · ⬆️ **Upgrading from 1.x?** `graph-compose` stays a drop-in for PDF with no code change; see the [2.0 modules migration guide](./docs/migration/v2.0.0-modules.md)
> · See [API stability policy](./docs/api-stability.md) for tier definitions.
@@ -212,71 +212,9 @@ For a Spring Boot `@RestController` streaming the PDF straight to the response,
## What's new in 2.0
-The **module-first** release — the single jar becomes a family of per-concern artifacts, so you install exactly what you render.
+The **module-first** release: the single jar became a family of per-concern artifacts, so you install exactly what you render, and `graph-compose` stayed a drop-in for PDF callers. Everything the 1.9 line added ships unchanged.
-- **Lean engine** — `graph-compose-core` is the document model, DSL, themes, and deterministic layout with **no PDFBox, POI, or template code** on its dependency tree. Backends plug in through a `ServiceLoader` seam; a core-only classpath asked to render throws `MissingBackendException` naming the artifact to add.
-- **Opt-in render backends** — `graph-compose-render-pdf` (PDFBox 3.0, full DSL coverage), `graph-compose-render-pptx` (Apache POI, geometry-identical PowerPoint decks from the same resolved layout — one page per editable slide; clipped regions land as pixel-exact pictures; ships as **beta** in its first release), `graph-compose-render-docx` (Apache POI, semantic export).
-- **`graph-compose` stays a drop-in** — the 1.x coordinate is now a thin wrapper over core + the PDF backend, so existing callers upgrade with **no code and no dependency change**. Reached the built-in templates through the single 1.x jar? Add `graph-compose-templates` (imports are unchanged) — the [migration guide](./docs/migration/v2.0.0-modules.md) walks every case, including the removed deprecated APIs and their replacements.
-- **Templates are their own artifact** — the CV / cover-letter / invoice / proposal preset families moved to `graph-compose-templates` (imports unchanged). This is the [one dependency-level break](./docs/migration/v2.0.0-modules.md#the-one-break-templates) of the split.
-- **`graph-compose-bundle`** — one batteries-included coordinate: PDF stack + templates + fonts + colour emoji.
-- **Retired surface** — the APIs deprecated across 1.6–1.9 are removed, the layered template packages dropped their `.v2` suffix, and `BusinessTheme` plus the classic pre-layered presets are gone — each removal has a named replacement in the [migration guide](./docs/migration/v2.0.0-modules.md).
-
-Everything the 1.9 line added — in-document navigation, native TOC and page references, bookmarks, multi-section documents, inline chips / SVG icons / colour emoji, render-to-image — ships unchanged in 2.0. Full history in [`CHANGELOG.md`](./CHANGELOG.md).
-
-## Scope and comparison
-
-### Output support
-
-| Format | Status | Notes |
-|---|---|---|
-| PDF | Production | Fixed-layout backend on PDFBox 3.0. Full DSL coverage. |
-| DOCX | Partial | Semantic export via Apache POI — paragraphs, lists, block images, tables and metadata. Word owns the flow, so drawing nodes (`shape`, `line`, `ellipse`, `barcode`) are dropped, one logged warning per kind. **Hyperlinks, bookmarks and headers/footers are not implemented**, table `colSpan`/`rowSpan` is not applied, and image fit modes are ignored — see [render-docx](./render-docx/README.md#what-it-maps-and-what-it-does-not). |
-| PPTX | Beta | Fixed-layout export via Apache POI from the same resolved layout — one page per editable slide with native shapes and text frames; clipped regions land as pixel-exact pictures. First shipped in 2.1, marked `@Beta` while the API shape settles. |
-
-### Text & internationalization
-
-- Text is laid out **left-to-right**. Bidirectional (RTL) reordering and complex-script shaping — Arabic contextual joining, Indic reordering — are **not** performed, so Arabic / Hebrew text renders in logical order without correct visual ordering. Full RTL / bidi support is tracked in [#140](https://github.com/DemchaAV/GraphCompose/issues/140).
-- A glyph the active font does not cover renders as `?` (with a warning logged); load a font that covers the script you need.
-
-### When to use GraphCompose
-
-- **Server-side PDF generation in Java** — invoices, CVs, reports, proposals, statements, schedules.
-- **Templated documents from data** — themed presets (`ModernProfessional`, `ModernInvoice`, …) you parameterise instead of re-styling every time.
-- **Regression-tested layouts** — `DocumentSession#layoutSnapshot()` makes layout changes visible in PRs before any byte ships; `PdfVisualRegression` adds a pixel-level gate for font and colour fidelity.
-- **Streaming PDFs from web backends** — Spring Boot `@RestController` writing straight to the response ([`HttpStreamingExample`](./examples/src/main/java/com/demcha/examples/features/streaming/HttpStreamingExample.java)).
-- **Higher-level than PDFBox, lighter than JasperReports** — Java DSL describes semantics; no XML templates, no manual coordinates.
-
-### What GraphCompose is not
-
-- Not a hosted PDF rendering service — it is a library you embed.
-- Not a WYSIWYG editor — the DSL is code, not drag-and-drop.
-- Not a reporting engine like JasperReports — no datasource bindings, no XML templates, no compiled `.jasper` files.
-- Not a browser / HTML-to-PDF renderer — the engine has its own layout pipeline; HTML/CSS input is not supported.
-
-### Compared with similar Java libraries
-
-| Library | API style | Layout | License | Best for |
-|---|---|---|---|---|
-| **GraphCompose** | Java DSL, semantic nodes | Two-pass, deterministic, snapshot-testable | MIT | Code-first business documents with layout regression tests |
-| **PDFBox** | Low-level text / path primitives | Manual coordinates | Apache 2.0 | Direct PDF manipulation, parsing, extraction |
-| **iText 7** | Object/layout API + low-level canvas | Automatic layout with direct-positioning options | AGPL / commercial | When AGPL is acceptable or you have a commercial licence |
-| **OpenPDF** | iText 4 fork | Manual + helpers | LGPL / MPL | Legacy iText 4 codebases |
-| **JasperReports** | XML templates compiled to `.jasper` | Template-driven | LGPL | Tabular reports with datasource bindings |
-
-GraphCompose uses PDFBox under the hood as the rendering backend — the comparison is about authoring surface, not the renderer.
-
-### Which API should I use?
-
-| You want to… | Surface | Entry point |
-|---|---|---|
-| Generate a one-off PDF programmatically | DSL | `GraphCompose.document(...).pageFlow(...)` — see [Hello world](#hello-world) above |
-| Generate a CV / cover letter from data | Layered templates | `ModernProfessional.create().compose(session, cvDocument)` — see [layered templates](./docs/templates/v2-layered/README.md) |
-| Add a custom visual primitive | Engine extension | `NodeDefinition` + `PdfFragmentRenderHandler` — see [extension guide](./docs/contributing/extension-guide.md) |
-| Regression-test generated layouts | Layout snapshots | `DocumentSession#layoutSnapshot()` — quickstart at [Testing your document](./docs/operations/test-your-document.md); full reference at [snapshot testing](./docs/operations/layout-snapshot-testing.md) |
-| Pixel-test the rendered PDF (fonts, colours, anti-aliasing) | Visual regression | `PdfVisualRegression.standard()…assertMatchesBaseline(...)` — see [visual regression testing](./docs/operations/visual-regression-testing.md) |
-| See the live gallery | Static showcase site | [Showcase](https://DemchaAV.github.io/GraphCompose/) — source under [`web/`](./web), deployed to GitHub Pages via the [Pages workflow](./.github/workflows/deploy-web.yml) |
-
-> **Templates in 2.0** — there is one template surface: the layered preset families in `graph-compose-templates`, themed through `BrandTheme`. Arriving from a pre-2.0 surface (classic presets, the built-in `*Template` classes)? **[Which template system should I use?](./docs/templates/which-template-system.md)** maps every retired name to its layered replacement.
+Full detail in [`CHANGELOG.md`](./CHANGELOG.md); every removed API and its replacement in the [2.0 modules migration guide](./docs/migration/v2.0.0-modules.md).
## Vector primitives in 30 lines
@@ -345,24 +283,73 @@ The repository is a Maven multi-module reactor: the root `pom.xml` is the build
See [CONTRIBUTING](./CONTRIBUTING.md) for the branch-routing table and the full build / verify flow.
-## Documentation
+## Scope and comparison
-📚 **[Full docs index](./docs/README.md)** — categorised map of every doc, ADR, and recipe. Start there to navigate the documentation.
+### Output support
+
+| Format | Status | Notes |
+|---|---|---|
+| PDF | Production | Fixed-layout backend on PDFBox 3.0. Full DSL coverage. |
+| DOCX | Partial | Semantic export via Apache POI — paragraphs, lists, block images, tables and metadata. Word owns the flow, so drawing nodes (`shape`, `line`, `ellipse`, `barcode`) are dropped, one logged warning per kind. **Hyperlinks, bookmarks and headers/footers are not implemented**, table `colSpan`/`rowSpan` is not applied, and image fit modes are ignored — see [render-docx](./render-docx/README.md#what-it-maps-and-what-it-does-not). |
+| PPTX | Beta | Fixed-layout export via Apache POI from the same resolved layout — one page per editable slide with native shapes and text frames; clipped regions land as pixel-exact pictures. First shipped in 2.1, marked `@Beta` while the API shape settles. |
+
+### Text & internationalization
+
+- Text is laid out **left-to-right**. Bidirectional (RTL) reordering and complex-script shaping — Arabic contextual joining, Indic reordering — are **not** performed, so Arabic / Hebrew text renders in logical order without correct visual ordering. Full RTL / bidi support is tracked in [#140](https://github.com/DemchaAV/GraphCompose/issues/140).
+- A glyph the active font does not cover renders as `?` (with a warning logged); load a font that covers the script you need.
+
+### When to use GraphCompose
+
+- **Server-side PDF generation in Java** — invoices, CVs, reports, proposals, statements, schedules.
+- **Templated documents from data** — themed presets (`ModernProfessional`, `ModernInvoice`, …) you parameterise instead of re-styling every time.
+- **Regression-tested layouts** — `DocumentSession#layoutSnapshot()` makes layout changes visible in PRs before any byte ships; `PdfVisualRegression` adds a pixel-level gate for font and colour fidelity.
+- **Streaming PDFs from web backends** — Spring Boot `@RestController` writing straight to the response ([`HttpStreamingExample`](./examples/src/main/java/com/demcha/examples/features/streaming/HttpStreamingExample.java)).
+- **Higher-level than PDFBox, lighter than JasperReports** — Java DSL describes semantics; no XML templates, no manual coordinates.
+
+### What GraphCompose is not
+
+- Not a hosted PDF rendering service — it is a library you embed.
+- Not a WYSIWYG editor — the DSL is code, not drag-and-drop.
+- Not a reporting engine like JasperReports — no datasource bindings, no XML templates, no compiled `.jasper` files.
+- Not a browser / HTML-to-PDF renderer — the engine has its own layout pipeline; HTML/CSS input is not supported.
+
+### Compared with similar Java libraries
+
+| Library | API style | Layout | License | Best for |
+|---|---|---|---|---|
+| **GraphCompose** | Java DSL, semantic nodes | Two-pass, deterministic, snapshot-testable | MIT | Code-first business documents with layout regression tests |
+| **PDFBox** | Low-level text / path primitives | Manual coordinates | Apache 2.0 | Direct PDF manipulation, parsing, extraction |
+| **iText 7** | Object/layout API + low-level canvas | Automatic layout with direct-positioning options | AGPL / commercial | When AGPL is acceptable or you have a commercial licence |
+| **OpenPDF** | iText 4 fork | Manual + helpers | LGPL / MPL | Legacy iText 4 codebases |
+| **JasperReports** | XML templates compiled to `.jasper` | Template-driven | LGPL | Tabular reports with datasource bindings |
+
+GraphCompose uses PDFBox under the hood as the rendering backend — the comparison is about authoring surface, not the renderer.
+
+### Which API should I use?
+
+| You want to… | Surface | Entry point |
+|---|---|---|
+| Generate a one-off PDF programmatically | DSL | `GraphCompose.document(...).pageFlow(...)` — see [Hello world](#hello-world) above |
+| Generate a CV / cover letter from data | Layered templates | `ModernProfessional.create().compose(session, cvDocument)` — see [layered templates](./docs/templates/v2-layered/README.md) |
+| Add a custom visual primitive | Engine extension | `NodeDefinition` + `PdfFragmentRenderHandler` — see [extension guide](./docs/contributing/extension-guide.md) |
+| Regression-test generated layouts | Layout snapshots | `DocumentSession#layoutSnapshot()` — quickstart at [Testing your document](./docs/operations/test-your-document.md); full reference at [snapshot testing](./docs/operations/layout-snapshot-testing.md) |
+| Pixel-test the rendered PDF (fonts, colours, anti-aliasing) | Visual regression | `PdfVisualRegression.standard()…assertMatchesBaseline(...)` — see [visual regression testing](./docs/operations/visual-regression-testing.md) |
+| See the live gallery | Static showcase site | [Showcase](https://DemchaAV.github.io/GraphCompose/) — source under [`web/`](./web), deployed to GitHub Pages via the [Pages workflow](./.github/workflows/deploy-web.yml) |
-### Templates
-- [**Templates — layered architecture**](./docs/templates/v2-layered/README.md) — the template surface: CV, cover-letter, invoice, and proposal preset families on `BrandTheme`. Personas: [quickstart](./docs/templates/v2-layered/quickstart.md) · [using templates](./docs/templates/v2-layered/using-templates.md) · [authoring presets](./docs/templates/v2-layered/authoring-presets.md) · [contributing a new family](./docs/templates/v2-layered/contributor-guide.md).
-- [Which template system?](./docs/templates/which-template-system.md) — the template naming history and the migration map for callers arriving from a pre-2.0 surface (classic presets, built-in `*Template` classes, the legacy PDF API). The retired classic docs are archived at [v1-classic](./docs/templates/v1-classic/README.md).
+> **Templates in 2.0** — there is one template surface: the layered preset families in `graph-compose-templates`, themed through `BrandTheme`. Arriving from a pre-2.0 surface (classic presets, the built-in `*Template` classes)? **[Which template system should I use?](./docs/templates/which-template-system.md)** maps every retired name to its layered replacement.
-### Architecture & operations
-- [Architecture overview](./docs/architecture/overview.md) · [Lifecycle](./docs/architecture/lifecycle.md) · [Production rendering](./docs/operations/production-rendering.md) · [Benchmarks](./docs/operations/benchmarks.md) · [Layout snapshot testing](./docs/operations/layout-snapshot-testing.md) · [Troubleshooting](./docs/troubleshooting.md)
+## Documentation
+
+📚 **[Full docs index](./docs/README.md)** — categorised map of every doc, ADR, and recipe. Start there to navigate the documentation.
-### Recipes & examples
-- [Recipes index](./docs/recipes.md) — [shape-as-container](./docs/recipes/shape-as-container.md) · [shapes](./docs/recipes/shapes.md) · [transforms](./docs/recipes/transforms.md) · [page-backgrounds](./docs/recipes/page-backgrounds.md) · [layered-page-design](./docs/recipes/layered-page-design.md) · [absolute-placement](./docs/recipes/absolute-placement.md) · [tables](./docs/recipes/tables.md) · [themes](./docs/recipes/themes.md) · [streaming](./docs/recipes/streaming.md) · [extending](./docs/recipes/extending.md) · [font-coverage](./docs/font-coverage.md)
-- [Examples gallery](./examples/README.md) — every runnable example with PDF preview
+The index routes by what you are doing — first document, using or authoring a
+template, extending the engine, running in production. The entry points most
+people want directly:
-### Contributing & releases
-- [Contributing](./CONTRIBUTING.md) · [Code of conduct](./CODE_OF_CONDUCT.md) · [Security policy](./SECURITY.md) · [Release process](./docs/contributing/release-process.md)
-- [API stability policy](./docs/api-stability.md) · [Which template system?](./docs/templates/which-template-system.md) · [**Migration to 2.0 (modules)**](./docs/migration/v2.0.0-modules.md) · [older migration notes](./docs/README.md)
+- **Templates** — [layered architecture](./docs/templates/v2-layered/README.md) (CV, cover letter, invoice, proposal on `BrandTheme`) · [which template system?](./docs/templates/which-template-system.md) for callers arriving from a pre-2.0 surface
+- **Recipes** — [the cookbook](./docs/recipes.md): tables, themes, shapes, transforms, page backgrounds, streaming, extending
+- **Operations** — [production rendering](./docs/operations/production-rendering.md) · [layout snapshot testing](./docs/operations/layout-snapshot-testing.md) · [troubleshooting](./docs/troubleshooting.md)
+- **Project** — [Contributing](./CONTRIBUTING.md) · [Roadmap](./ROADMAP.md) · [Support](./SUPPORT.md) · [Security policy](./SECURITY.md) · [API stability](./docs/api-stability.md) · [Migration to 2.0](./docs/migration/v2.0.0-modules.md)
## Companion projects
diff --git a/ROADMAP.md b/ROADMAP.md
index b596bbe32..98fa0d4d3 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,18 +1,22 @@
# Roadmap
-GraphCompose is solo-maintained. This roadmap is a direction, not a contract. Dates are intentionally omitted. Concrete work is tracked in [issues](https://github.com/DemchaAV/GraphCompose/issues) and shipped work is recorded in [CHANGELOG.md](CHANGELOG.md). For v1.6 phase-level detail, see [docs/roadmaps/v1.6-roadmap.md](docs/roadmaps/v1.6-roadmap.md).
+GraphCompose is solo-maintained. This roadmap is a direction, not a contract. Dates are intentionally omitted. Concrete work is tracked in [issues](https://github.com/DemchaAV/GraphCompose/issues) and shipped work is recorded in [CHANGELOG.md](CHANGELOG.md).
-## Now — 2.1 line
+## Now — after 2.1
-In development on `develop`, building toward the **2.1.0** minor. The headline is the **fixed-layout PPTX render backend**: the same `DocumentSession` that prints a PDF now also emits an editable PowerPoint deck — one page per slide, identical geometry by construction, native shapes. It ships as `@Beta` (Experimental) while its API shape settles; the geometry identity with the PDF backend is a design invariant, not subject to change. See the [API stability policy](docs/api-stability.md) and the [backend capability matrix](docs/architecture/backend-capability-matrix.md).
+Consolidation ahead of the next patch: the documentation and contributor surfaces are being brought onto the 2.x vocabulary, the CI guards onto what they actually run, and the committed example assets onto a regeneration path. Open engineering threads are tracked in [issues](https://github.com/DemchaAV/GraphCompose/issues) — vector clipping for the PPTX backend, backend-neutral font measurement, and font-face selection are the live ones.
-Alongside it, 2.1 hardens pagination: `keepWithNext()` for headings, orphaned-heading fixes ahead of paragraphs, tables and lists, and the layered CV presets wired to the keep-with-next policy.
+## Current stable — 2.1
-Full detail lands in [CHANGELOG.md](CHANGELOG.md) under `v2.1.0`.
+The **2.1.0** GA is the current stable line. Its headline is the **fixed-layout PPTX render backend**: the same `DocumentSession` that prints a PDF also emits an editable PowerPoint deck — one page per slide, identical geometry by construction, native shapes. It ships as `@Beta` (Experimental) while its API shape settles; the geometry identity with the PDF backend is a design invariant, not subject to change. See the [API stability policy](docs/api-stability.md) and the [backend capability matrix](docs/architecture/backend-capability-matrix.md).
-## Current stable — 2.0
+2.1 also hardened pagination: `keepWithNext()` for headings, orphaned-heading fixes ahead of paragraphs, tables and lists, and the layered CV presets wired to the keep-with-next policy.
-The **2.0.0** GA is the current stable line. 2.0 was about **packaging and internal hygiene**, not new authoring API — binary-breaking by design.
+Full detail in [CHANGELOG.md](CHANGELOG.md) under `v2.1.0`.
+
+## Previously — 2.0
+
+The **2.0.0** GA opened the 2.x line. 2.0 was about **packaging and internal hygiene**, not new authoring API — binary-breaking by design.
- **Modular split** — the single jar is split into `graph-compose-core` plus `graph-compose-render-pdf` / `graph-compose-render-docx` / `graph-compose-render-pptx` / `graph-compose-templates` / `graph-compose-testing`, with render backends discovered through a `ServiceLoader` SPI. `graph-compose` stays a drop-in wrapper (core + render-pdf) so existing PDF callers upgrade unchanged. See the [2.0 modules migration guide](docs/migration/v2.0.0-modules.md) and [ADR 0016](docs/adr/0016-multi-module-packaging.md).
- **Legacy removal** — the dead Entity-Component-System execution layer and the deprecated (`forRemoval`) public API are gone; the classic template presets are replaced by the layered `templates.*` stack on `BrandTheme`.
diff --git a/SECURITY.md b/SECURITY.md
index b195b1680..2d23feaa0 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -9,7 +9,8 @@ Security fixes are issued for the latest minor release. Older minors do not rece
| Version | Supported |
|---------|-----------|
| 2.1.x | Yes — actively patched |
-| < 2.1 | No — upgrade required (see [CHANGELOG.md](CHANGELOG.md) for per-version migration notes, and the [2.0 modules migration guide](docs/migration/v2.0.0-modules.md) for the 1.x → 2.x step) |
+| 1.9.x | Critical fixes and security backports only, on the `1.x` branch — no features |
+| < 1.9 | No — upgrade required (see [CHANGELOG.md](CHANGELOG.md) for per-version migration notes, and the [2.0 modules migration guide](docs/migration/v2.0.0-modules.md) for the 1.x → 2.x step) |
## Reporting a vulnerability
@@ -44,7 +45,8 @@ In scope:
- Layout engine (`com.demcha.compose.document.layout`).
- PDF backend (`com.demcha.compose.document.backend.fixed.pdf`).
-- DOCX / PPTX semantic backends (`com.demcha.compose.document.backend.semantic`).
+- PPTX backend (`com.demcha.compose.document.backend.fixed.pptx`).
+- Semantic export backends (`com.demcha.compose.document.backend.semantic`) — DOCX, and the legacy PPTX manifest.
- Templates shipped in `com.demcha.compose.document.templates`.
- Public authoring API (`GraphCompose`, `DocumentSession`, DSL).
- Build and release artifacts on Maven Central (`io.github.demchaav:graph-compose`). The legacy JitPack URL remains available for consumers pinned to v1.6.5 and earlier but is no longer the documented install channel.
diff --git a/SUPPORT.md b/SUPPORT.md
index 46512f4ef..2ac0e6757 100644
--- a/SUPPORT.md
+++ b/SUPPORT.md
@@ -6,7 +6,7 @@ GraphCompose is a solo-maintained open-source library. This file describes where
| Need | Where |
|------|-------|
-| Usage question | [GitHub Issues](https://github.com/DemchaAV/GraphCompose/issues/new?labels=question&title=Question%3A+) — apply the `question` label |
+| Usage question | [GitHub Discussions](https://github.com/DemchaAV/GraphCompose/discussions) — not the issue tracker |
| Bug report | [Bug report template](https://github.com/DemchaAV/GraphCompose/issues/new?template=bug_report.md) |
| Feature request | [Feature request template](https://github.com/DemchaAV/GraphCompose/issues/new?template=feature_request.md) |
| Security vulnerability | [SECURITY.md](SECURITY.md) — do **not** use public issues |
@@ -16,12 +16,14 @@ GraphCompose is a solo-maintained open-source library. This file describes where
Self-serve, ordered by likelihood of containing the answer:
-1. [README.md](README.md) — installation, hello-world, v1.6 primitives.
-2. [docs/getting-started.md](docs/getting-started.md) — first runnable example.
-3. [examples/README.md](examples/README.md) — runnable examples with PDF previews.
-4. [docs/recipes.md](docs/recipes.md) — patterns for common layouts.
-5. [docs/architecture/overview.md](docs/architecture/overview.md) and [docs/architecture/package-map.md](docs/architecture/package-map.md) — when extending the engine.
-6. [docs/roadmaps/migration-v1-5-to-v1-6.md](docs/roadmaps/migration-v1-5-to-v1-6.md) — upgrades from previous minor.
+1. [README.md](README.md) — installation, hello-world, which artifact to depend on.
+2. [docs/first-document.md](docs/first-document.md) — the five-minute path to a rendered PDF.
+3. [docs/getting-started.md](docs/getting-started.md) — DSL or templates, and the first-render walk-through.
+4. [examples/README.md](examples/README.md) — runnable examples with PDF previews.
+5. [docs/recipes.md](docs/recipes.md) — patterns for common layouts.
+6. [docs/troubleshooting.md](docs/troubleshooting.md) — symptom-first fixes for the common gotchas.
+7. [docs/architecture/overview.md](docs/architecture/overview.md) and [docs/architecture/package-map.md](docs/architecture/package-map.md) — when extending the engine.
+8. [docs/migration/v2.0.0-modules.md](docs/migration/v2.0.0-modules.md) — upgrading from the 1.x line; [CHANGELOG.md](CHANGELOG.md) for minor-to-minor steps.
## Response expectations
diff --git a/docs/README.md b/docs/README.md
index 3bce26e42..03acd26a8 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -62,14 +62,21 @@ back here.
- **[contributing/implementation-guide.md](contributing/implementation-guide.md)** — internal engine notes for contributors hacking on layout / measurement / pagination.
- **[contributing/release-process.md](contributing/release-process.md)** — versioning, tag procedure, Maven Central publication.
-### Roadmaps & migrations
-- **[roadmaps/post-2.0-engineering.md](roadmaps/post-2.0-engineering.md)** — forward-looking engineering roadmap: internal refactors, scale, and tooling deferred past the 2.0 line.
-- **[roadmaps/v1.6-roadmap.md](roadmaps/v1.6-roadmap.md)** — the v1.6 "expressive" roadmap (shipped).
-- **[roadmaps/migration-v1-8-to-v1-9.md](roadmaps/migration-v1-8-to-v1-9.md)** — upgrade guide for v1.8 → v1.9.
-- **[roadmaps/migration-v1-7-to-v1-8.md](roadmaps/migration-v1-7-to-v1-8.md)** — upgrade guide for v1.7 → v1.8.
-- **[roadmaps/migration-v1-6-to-v1-7.md](roadmaps/migration-v1-6-to-v1-7.md)** — upgrade guide for v1.6 → v1.7.
-- **[roadmaps/migration-v1-5-to-v1-6.md](roadmaps/migration-v1-5-to-v1-6.md)** — upgrade guide for v1.5 → v1.6.
-- **[roadmaps/migration-v1-4-to-v1-5.md](roadmaps/migration-v1-4-to-v1-5.md)** — upgrade guide for v1.4 → v1.5.
+### Migrations & roadmap
+- **[migration/v2.0.0-modules.md](migration/v2.0.0-modules.md)** — the current upgrade guide: the 1.x → 2.x module split, the one dependency-level break, and every removed API with its replacement.
+- **[migration/v1.8.0-fonts.md](migration/v1.8.0-fonts.md)** — the fonts artifact split, still relevant to anyone adding `graph-compose-fonts`.
+- **[../ROADMAP.md](../ROADMAP.md)** — direction for the line after 2.1.
+- **[roadmaps/post-2.0-engineering.md](roadmaps/post-2.0-engineering.md)** — internal refactors, scale, and tooling deferred past the 2.0 line.
+
+
+Historical documentation — shipped roadmaps and superseded minor-to-minor upgrade guides
+
+Kept for anyone stepping through the 1.x line one minor at a time. Nothing here describes the current API.
+
+- [roadmaps/v1.6-roadmap.md](roadmaps/v1.6-roadmap.md) — the v1.6 "expressive" roadmap (shipped).
+- [roadmaps/migration-v1-8-to-v1-9.md](roadmaps/migration-v1-8-to-v1-9.md) · [v1-7-to-v1-8](roadmaps/migration-v1-7-to-v1-8.md) · [v1-6-to-v1-7](roadmaps/migration-v1-6-to-v1-7.md) · [v1-5-to-v1-6](roadmaps/migration-v1-5-to-v1-6.md) · [v1-4-to-v1-5](roadmaps/migration-v1-4-to-v1-5.md)
+
+
### Recipes (cookbook-style howtos)
- **[recipes.md](recipes.md)** — index of all recipes (coverage map in [recipes/README.md](recipes/README.md)).
diff --git a/docs/architecture/canonical-legacy-parity.md b/docs/architecture/canonical-legacy-parity.md
index 297be5243..64dcc47c3 100644
--- a/docs/architecture/canonical-legacy-parity.md
+++ b/docs/architecture/canonical-legacy-parity.md
@@ -1,8 +1,10 @@
# Canonical Legacy-Parity Matrix
-This matrix tracks practical authoring parity for the canonical session-first API.
-It is a living planning document (last refreshed for v1.5), not a request to
-expose the old low-level authoring model.
+This matrix tracks practical authoring coverage of the canonical session-first
+API, feature by feature. It is a living document, last refreshed for the 2.1
+line. The low-level authoring model it was originally measured against was
+removed in 2.0; the matrix is kept because the per-feature coverage it records
+is what the recipes, capabilities catalogue and troubleshooting guide link to.
Public application code should start with:
@@ -33,7 +35,7 @@ reaching into engine internals.
| Child horizontal alignment | Done via `LayerStack` / `ShapeContainer` | Use `LayerStackBuilder.topLeft(...)` … `bottomRight(...)` (nine alignment shortcuts) or `ShapeContainerBuilder` with the same vocabulary; v1.5 also exposes `position(node, offsetX, offsetY, anchor)` for screen-space nudges from an anchor. |
| Child vertical alignment | Done via `LayerStack` / `ShapeContainer` | Same nine alignment anchors cover top, centre, bottom edges. |
| Absolute placement | Rejected for normal authoring | Keep coordinates inside layout and backend internals. Use `LayerStack.position(...)` if you need anchor-plus-offset placement. |
-| Controlled free canvas (`CanvasLayerNode`) | Planned for v1.6 (stretch) | New atomic semantic node accepting children at explicit `(x, y)` coordinates. Separate from `LayerStackNode` and `ShapeContainerNode` so absolute placement stays an opt-in primitive, not a global policy. See [v1.6 roadmap, Phase C](../roadmaps/v1.6-roadmap.md). |
+| Controlled free canvas (`CanvasLayerNode`) | Done | New atomic semantic node accepting children at explicit `(x, y)` coordinates. Separate from `LayerStackNode` and `ShapeContainerNode` so absolute placement stays an opt-in primitive, not a global policy. |
## Visual Primitives
@@ -56,7 +58,7 @@ reaching into engine internals.
| Paragraph wrapping | Done | Keep wrapping in paragraph layout preparation. |
| Inline text/link runs | Done | Keep as paragraph-level semantic content. |
| Lists | Done | Existing marker and indentation APIs remain canonical. |
-| Nested list ergonomics | Planned for v1.6 (committed) | `ListBuilder.addItem(label, Consumer)` plus a new `ListItem` value type and a `ListNode` record extension with back-compat constructor. See [v1.6 roadmap, Phase A](../roadmaps/v1.6-roadmap.md). |
+| Nested list ergonomics | Done | `ListBuilder.addItem(label, Consumer)` plus a new `ListItem` value type and a `ListNode` record extension with back-compat constructor. |
| Auto-size text | Done | Use `ParagraphBuilder.autoSize(maxSize, minSize)` to fit single-line headlines into the resolved inner width. |
## Images
@@ -80,7 +82,7 @@ reaching into engine internals.
| Zebra rows | Done | `TableBuilder.zebra(odd, even)` (style or colour overload) alternates row fills. Applied lazily at `build()`; explicit `rowStyle(idx, ...)` always wins. |
| Totals row | Done | `TableBuilder.totalRow(values)` appends a row with a default bold + subtle-fill style. `totalRow(style, values)` for a custom look. |
| Repeated header on page break | Done | `TableBuilder.repeatHeader()` repeats the first row at the top of every continuation page; `repeatHeader(int n)` for multi-row headers. |
-| Complex cell composition | Planned for v1.6 (committed) | New `TableCellContent.NodeContent(DocumentNode child)` variant + `DocumentTableCell.node(DocumentNode)` factory. Two-pass cell measurement preserves row pagination behaviour. See [v1.6 roadmap, Phase B](../roadmaps/v1.6-roadmap.md). |
+| Complex cell composition | Done | New `TableCellContent.NodeContent(DocumentNode child)` variant + `DocumentTableCell.node(DocumentNode)` factory. Two-pass cell measurement preserves row pagination behaviour. |
## PDF Output
diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md
index 67a8b5e9e..699b16fe9 100644
--- a/docs/architecture/overview.md
+++ b/docs/architecture/overview.md
@@ -245,7 +245,9 @@ last:
- `com.demcha.compose.document.*` — **public canonical surface**.
Authoring API, layout graph, exceptions, snapshots. The render backends
(`document.backend.fixed.pdf` in **graph-compose-render-pdf**,
- `document.backend.semantic.*` in the docx / pptx modules) and the built-in
+ `document.backend.fixed.pptx` plus the legacy `document.backend.semantic.pptx`
+ in **graph-compose-render-pptx**, `document.backend.semantic.docx` in
+ **graph-compose-render-docx**) and the built-in
templates (`document.templates.*` in **graph-compose-templates**) share this
namespace but ship as separate, opt-in artifacts over `graph-compose-core`.
- `com.demcha.compose.font.*` — public font names, backend-neutral
diff --git a/docs/contributing/release-process.md b/docs/contributing/release-process.md
index cd2ca5be0..2b009ed93 100644
--- a/docs/contributing/release-process.md
+++ b/docs/contributing/release-process.md
@@ -32,20 +32,21 @@ The shell setup and exact PowerShell commands live in the `graphcompose-release-
- [ ] `./mvnw -B -ntp clean verify` exits 0 over the whole reactor. Every test must pass — no skips, no flake retries. Confirm `Tests run: , Failures: 0, Errors: 0, Skipped: 0` per module from `*/target/surefire-reports/*.txt`. **Read Maven's own exit code** — never end the command in a pipe, or the shell reports the last stage's status and a `BUILD FAILURE` slips through as `0`.
- [ ] Examples module compiles cleanly: `./mvnw -B -ntp -q -f examples/pom.xml clean compile` exits 0. Catches `double → float` lossy narrowing and similar bugs that don't surface in the engine module.
-- [ ] All examples regenerate: `./mvnw -B -ntp -q -f examples/pom.xml exec:java -Dexec.mainClass=com.demcha.examples.GenerateAllExamples` produces 26+ `Generated:` lines, exits 0, and emits no `Fixed column ... is smaller than required natural width` or `Spanned cell ... requires extra width` errors. (Requires `./mvnw install -DskipTests` once first so the local `~/.m2` resolves the current SNAPSHOT version — any standalone goal that resolves train modules from `~/.m2`, including the `qa` suite and `javadoc:javadoc`, needs this after a version bump.)
+- [ ] All examples regenerate: `./mvnw -B -ntp -q -f examples/pom.xml exec:java -Dexec.mainClass=com.demcha.examples.GenerateAllExamples` exits 0, prints one `Generated:` line per example, and emits no `Fixed column ... is smaller than required natural width` or `Spanned cell ... requires extra width` errors. (Requires `./mvnw install -DskipTests` once first so the local `~/.m2` resolves the current SNAPSHOT version — any standalone goal that resolves train modules from `~/.m2`, including the `qa` suite and `javadoc:javadoc`, needs this after a version bump.)
- [ ] Architecture-guard suite explicitly green: `./mvnw -B -ntp test -pl :graph-compose-core -Dtest='CanonicalSurfaceGuardTest,DocumentationCoverageTest,InternalAnnotationCoverageTest,PublicApiNoEngineLeakTest,PackageMapGuardTest,VersionConsistencyGuardTest'` exits 0. These guard against legacy-API leakage in docs and engine internals leaking into the public surface, and — via `VersionConsistencyGuardTest` — against version drift between the train poms and the README install snippets. Note `PackageMapGuardTest` also scans the gitignored `docs/private/`, so a stale local note there fails a local run that CI cannot reproduce.
- [ ] Javadoc gate green on the published modules: `./mvnw -B -ntp javadoc:javadoc -pl :graph-compose-core,:graph-compose-render-pdf,:graph-compose-render-docx,:graph-compose-render-pptx,:graph-compose-templates,:graph-compose-testing` exits 0. The release profile publishes a javadoc jar per module with `failOnError=false`, so a broken link only surfaces here.
### C. Documentation freeze (matches target version)
- [ ] `CHANGELOG.md` has a `## v — Planned` header at the top. The script flips `Planned` → today's date during release execution; if the header is missing or already dated, the script silently skips and the release ships with the wrong header.
-- [ ] CHANGELOG `v` section: every linked file resolves on disk. Common offenders: new `docs/adr/00XX-*.md`, `docs/migration-v1-N-to-v1-M.md`, recipe pages.
-- [ ] `README.md` test-count claim matches the actual surefire total (`grep -E '[0-9]+ green tests' README.md` vs the surefire aggregate).
+- [ ] CHANGELOG `v` section: every linked file resolves on disk. Common offenders: new `docs/adr/00XX-*.md`, `docs/migration/*.md`, recipe pages.
+- [ ] `README.md` carries no hand-maintained count (test totals, example totals) that the release would falsify. Numbers in prose have no owner and no guard — prefer prose that stays true.
- [ ] `README.md` install snippets match the **current** `pom.xml` version (on `develop` between releases that is the last published version). `VersionConsistencyGuardTest` enforces README == pom, so the two move together: `cut-release.ps1` rewrites the README Maven + Gradle install snippets to the new version in the *same* release commit it bumps the POMs (section 1, Step 2/6). The README therefore flips to the new version at release-execution time, never on `develop` ahead of the tag — a snippet pointing at a version that has not been published yet would 404 for any user who copies it. Do **not** hand-flip the README ahead of the script: that desyncs README from the still-unbumped pom and fails the guard at the verify gate.
+- [ ] `ROADMAP.md` names the version being released as the current stable line, and `## Now` points at what comes after it. Not guarded by a test.
- [ ] **`README.md` "Release status" prose block** (the `> 🟢 Latest stable: vX … 🟡 In develop: vY` blockquote near the top) names the **NEW target** as "Latest stable" and the **next cycle** as "In develop", with the new release's one-line highlights. ⚠️ `cut-release.ps1` does **not** touch this block (it only rewrites the install snippets), and the post-release cycle-open commit that would fix it is **develop-only** and never reaches `main`. So it MUST be updated **on develop before the cut** (in the `docs: pre-release fixes` commit, section 2.A) — otherwise `main` (the README GitHub renders) keeps advertising the *previous* version as "latest stable". Not guarded by a test; this checklist item is the only gate.
- [ ] `README.md` and `examples/README.md` link audits resolve: every `(./...)` and `(../...)` link must exist on disk. Use `grep -oE '\(\.?\.?/[^)]+\.(md|java|png|pdf|jpg)\)' README.md examples/README.md | sed 's/^(//;s/)$//' | sort -u | xargs -I{} test -e {} || echo MISSING: {}`.
-- [ ] `examples/README.md` gallery row count matches the file count: `find examples/src/main/java -name '*Example.java' | wc -l` equals `grep -c '^| \[' examples/README.md`.
-- [ ] For minor releases (`vX.Y.0`): `docs/migration-v1--to-v1-.md` exists. Patch releases skip this.
+- [ ] Every example the runner produces has a `ShowcaseMetadata` entry, so the published site carries no placeholder cards: compare `examples/target/generated-pdfs` against the entries in `examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java`.
+- [ ] For minor releases (`vX.Y.0`): a migration note exists under `docs/migration/`. Patch releases skip this.
### D. Version artifacts (script-handled, agent verifies state)
@@ -92,7 +93,7 @@ The script does **not** handle these. They are either pre-release or post-releas
- **Stale documentation claims** — examples count, gallery descriptors, version-anchored prose. Fix in a `docs: pre-release fixes — ` commit on develop, then commit, then push (or stage and let the user push).
- **CHANGELOG `## v — Planned` header** — must exist before the script runs. If you bumped scope mid-cycle, ensure the planned header is still on the right version line.
-- **Missing migration guide** for minor releases — write `docs/migration-v1--to-v1-.md` if absent.
+- **Missing migration guide** for minor releases — write the note under `docs/migration/` if absent.
- **`InternalAnnotationCoverageTest` and other guard tests** — fix any failures by adjusting the source (annotation propagation, doc rewording), never by suppressing the test or extending the allowlist. Allowlist edits are reviewable evidence of an architecture decision; write or update an ADR before suppressing.
### 2.B Post-release (after `cut-release.ps1` succeeds and the tag is pushed)
@@ -104,7 +105,7 @@ Run within 1 hour of the tag push. Independent steps can run in parallel.
3. **Merge `develop` → `main`** on GitHub so GitHub Pages picks up the new docs. Fast-forward only — never force-push `main`. If the push is rejected with `non-fast-forward`, a hotfix landed on `main` after the audit and the merge has to be redone after merging `origin/main` back into `develop`.
4. **Verify CI green on main** — `gh run list --branch main --limit 1` shows `success` for the tag commit.
5. **Smoke-test the install snippet** — minimal POM in `$env:TEMP`, `mvn dependency:resolve` against the snippet copy-pasted from README, expect 0 exit.
-6. **Re-run all examples against the published artifact** — `./mvnw -f examples/pom.xml clean package` followed by `exec:java -Dexec.mainClass=com.demcha.examples.GenerateAllExamples`. Expect 26+ `Generated:` lines.
+6. **Re-run all examples against the published artifact** — `./mvnw -f examples/pom.xml clean package` followed by `exec:java -Dexec.mainClass=com.demcha.examples.GenerateAllExamples`. Expect one `Generated:` line per example.
6b. **Run the external release-smoke suite** — once Central has indexed the train, dispatch the **Release Smoke** workflow ([`.github/workflows/release-smoke.yml`](../../.github/workflows/release-smoke.yml)) with `version=`, or run `bash scripts/release-smoke/run.sh --version `. This resolves every published coordinate from Maven Central in a clean, GraphCompose-evicted repository (no reactor / local install) and exercises the documented consumer scenarios — the wrapper renders PDF, `graph-compose-core` alone throws `MissingBackendException`, core+render-pdf renders, and templates/testing/bundle perform their roles. It is the authoritative "a real user can install and use this" check; the minimal step-5 snippet resolve is a faster subset. (Release smoke tests **published** artifacts, so it necessarily runs post-publish, not pre-tag.)
7. **Open the next development line** — `pwsh ./scripts/cut-release.ps1 -PostReleaseOnly`. This bumps the train poms to the next patch `-SNAPSHOT` (so develop builds are distinguishable from the release and the japicmp gate compares against it) **and** restores linkable "View Code" buttons by flipping ShowcaseMetadata back to `/blob/develop`. The README/showcase install snippets stay on the just-published release.
8. **GitHub Release — automated.** Pushing the `v` tag triggers [`.github/workflows/release.yml`](../../.github/workflows/release.yml): it re-runs `./mvnw clean verify` over the whole reactor against the tagged commit, then creates the Release with that version's CHANGELOG section as the body (hyphenated tags like `v1.7.0-rc.1` ship as pre-releases; the step is idempotent — it edits the notes if the Release already exists). The workflow titles it `GraphCompose v`; for a **minor** release, edit the title to add the codename (`v1.4`=cinematic, `v1.5`=intuitive, `v1.6`=expressive; patches drop it). Create the Release by hand (`gh release create v --notes-file `) only if the workflow is unavailable.
diff --git a/examples/README.md b/examples/README.md
index 27b908f50..fe9c7186d 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -15,7 +15,7 @@ Install the library artifact once from the repository root:
./mvnw -DskipTests install
```
-Then run all 63 examples in one shot:
+Then run every example in one shot:
```bash
./mvnw -f examples/pom.xml exec:java \
@@ -32,7 +32,7 @@ Then run all 63 examples in one shot:
Generated PDFs land in `examples/target/generated-pdfs/`. The same
`mvnw.cmd` form works on Windows PowerShell with backslash paths.
-`GenerateAllExamples` runs **63** example programs — 16 CV + 15
+`GenerateAllExamples` renders the whole catalogue in one pass — the CV and
cover-letter presets plus invoices, proposals, a schedule, the feature
demos, and the flagships. The showcase site surfaces the full generated
catalogue (~53 PDFs); a curated 39-PDF subset is committed under
diff --git a/web/index.html b/web/index.html
index 33b4cd3f2..c857a52ab 100644
--- a/web/index.html
+++ b/web/index.html
@@ -3,7 +3,7 @@
-
+
GraphCompose — Java PDF layout engine for structured business documents
@@ -30,7 +30,7 @@
-
+
@@ -94,7 +94,7 @@
{
"@type": "ItemList",
"name": "GraphCompose showcase examples",
- "description": "50+ runnable Java examples that produce real PDFs — CV templates, cover letters, invoices, proposals, schedules, plus every supported feature.",
+ "description": "80+ runnable Java examples that produce real PDFs — CV templates, cover letters, invoices, proposals, schedules, plus every supported feature.",
"numberOfItems": 51,
"itemListElement": [
{
From 78adb9d2c343a6f51a2a998cab7531e9818dae3d Mon Sep 17 00:00:00 2001
From: DemchaAV
Date: Fri, 31 Jul 2026 18:11:05 +0100
Subject: [PATCH 2/2] docs: give the release status one source of truth
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The README advertised v2.1.1 as latest stable behind a release link that 404s,
while the changelog carried v2.1.1 as Planned, the roadmap named 2.1.0 as the
current stable line, and the showcase metadata said 2.1.0. Four surfaces, three
answers.
The false one was the README, and it was false on purpose. cut-release.ps1 did
not rewrite the release-status block, so the post-release commit that would fix
it lives on develop and never reaches main — the v1.6.9 lesson recorded in the
runbook. The mitigation was a pre-cut hand-edit, enforced by a Step 0 gate that
demanded the block already name the version being cut. To leave main correct
after the tag, develop had to be wrong for the whole cycle.
The block is script-owned now. Step 1 promotes the in-development half to latest
stable, rewriting both the link text and the tag URL, and opens the next patch
line; Assert-ReleaseMetadata verifies the result after the mutation. Step 0 no
longer demands a hand-edit — it checks the block still exists in the shape the
rewrite matches, so a reflowed blockquote aborts loudly instead of being skipped
in silence. What the maintainer owns is the prose, not the version tokens.
SECURITY.md's supported-versions table jumped from 2.1.x straight to 1.9.x,
leaving 2.0.x uncovered — a regression introduced when the 1.9.x row was added.
The showcase page still stated a count in three places while the JSON-LD beside
them declared numberOfItems: 51 and the visible heading said "50+ generated
PDFs". Replacing one number with another moves the problem to the next release,
so the counts are gone; the page loads its catalogue from examples.json and can
say what it renders without claiming how many.
The runbook still told the maintainer that PackageMapGuardTest scans the
gitignored docs/private/ and can fail a local run CI cannot reproduce. That
stopped being true when the exclusion landed; its guard list was also missing
CiGuardListGuardTest.
The README API table sent anyone adding a visual primitive to
PdfFragmentRenderHandler alone, while CONTRIBUTING now requires registering with
both fixed-layout backends — a handler registered with one renders in one output
and vanishes from the other.
---
CHANGELOG.md | 12 ++++-
README.md | 5 +-
SECURITY.md | 1 +
docs/contributing/release-process.md | 4 +-
scripts/cut-release.ps1 | 78 ++++++++++++++++++++++------
web/index.html | 9 ++--
6 files changed, 83 insertions(+), 26 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e3602facd..f54c90bf8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,12 @@ follow semantic versioning; release dates are ISO 8601.
- **`graph-compose-render-pptx` declares PDFBox.** It compiles against
`org.apache.pdfbox` types while declaring only `fontbox`, taking the rest
transitively; the resolved version is unchanged.
+- **The release script owns the README release-status block.** It was a
+ hand-edit the script only validated, and the validation demanded the *target*
+ version — so between releases `develop` had to advertise an unpublished version
+ as "latest stable", behind a release link that 404s. `cut-release.ps1` now
+ promotes the in-development half to latest stable and opens the next patch line
+ as part of the release commit, and verifies the result after the mutation.
### Fixed
@@ -72,7 +78,11 @@ follow semantic versioning; release dates are ISO 8601.
- **The release surface names the release that shipped.** `ROADMAP.md` described
2.1 as in development and 2.0 as the current stable line; 2.1 is now current,
2.0 is history, and `## Now` points at what follows. The README release-status
- block described 2.1.1 with 2.1.0's headline.
+ block, the changelog, the roadmap and the showcase metadata now agree on which
+ version is published and which is in development.
+- **`SECURITY.md` covers every 2.x line.** The supported-versions table jumped
+ from `2.1.x` to `1.9.x`, so a 2.0.x user could not tell whether their version
+ was supported.
- **The documentation index carries the migration guide that applies.**
`docs/README.md` listed five superseded 1.x minor-to-minor upgrade guides on the
main path and omitted `migration/v2.0.0-modules.md` entirely. The current
diff --git a/README.md b/README.md
index ff96c4455..f80df44d0 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,8 @@
> **Release status** —
-> 🟢 **Latest stable**: [v2.1.1](https://github.com/DemchaAV/GraphCompose/releases/tag/v2.1.1) — a patch over the **PowerPoint** release: a heading no longer strands above a block that asked to stay whole, and the documented snippets were corrected against the shipped API. `graph-compose-render-pptx` (new in 2.1.0) turns the same resolved layout into an editable deck — one page per slide, geometry-identical to the PDF, text and panels as native shapes, `@Beta`. **[What each backend supports ↓](docs/architecture/backend-capability-matrix.md)**
+> 🟢 **Latest stable**: [v2.1.0](https://github.com/DemchaAV/GraphCompose/releases/tag/v2.1.0) — the **PowerPoint** release: `graph-compose-render-pptx` turns the same resolved layout into an editable deck — one page per slide, geometry-identical to the PDF, text and panels as native shapes. Ships as `@Beta`. **[What each backend supports ↓](docs/architecture/backend-capability-matrix.md)**
+> · 🟡 **In development**: v2.1.1 on `develop` — see [CHANGELOG.md](./CHANGELOG.md).
> · ⬆️ **Upgrading from 1.x?** `graph-compose` stays a drop-in for PDF with no code change; see the [2.0 modules migration guide](./docs/migration/v2.0.0-modules.md)
> · See [API stability policy](./docs/api-stability.md) for tier definitions.
@@ -331,7 +332,7 @@ GraphCompose uses PDFBox under the hood as the rendering backend — the com
|---|---|---|
| Generate a one-off PDF programmatically | DSL | `GraphCompose.document(...).pageFlow(...)` — see [Hello world](#hello-world) above |
| Generate a CV / cover letter from data | Layered templates | `ModernProfessional.create().compose(session, cvDocument)` — see [layered templates](./docs/templates/v2-layered/README.md) |
-| Add a custom visual primitive | Engine extension | `NodeDefinition` + `PdfFragmentRenderHandler` — see [extension guide](./docs/contributing/extension-guide.md) |
+| Add a custom visual primitive | Engine extension | `NodeDefinition` + a fragment handler per fixed-layout backend (`PdfFragmentRenderHandler`, `PptxFragmentRenderHandler`) — see [extension guide](./docs/contributing/extension-guide.md) |
| Regression-test generated layouts | Layout snapshots | `DocumentSession#layoutSnapshot()` — quickstart at [Testing your document](./docs/operations/test-your-document.md); full reference at [snapshot testing](./docs/operations/layout-snapshot-testing.md) |
| Pixel-test the rendered PDF (fonts, colours, anti-aliasing) | Visual regression | `PdfVisualRegression.standard()…assertMatchesBaseline(...)` — see [visual regression testing](./docs/operations/visual-regression-testing.md) |
| See the live gallery | Static showcase site | [Showcase](https://DemchaAV.github.io/GraphCompose/) — source under [`web/`](./web), deployed to GitHub Pages via the [Pages workflow](./.github/workflows/deploy-web.yml) |
diff --git a/SECURITY.md b/SECURITY.md
index 2d23feaa0..8d35966cc 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -9,6 +9,7 @@ Security fixes are issued for the latest minor release. Older minors do not rece
| Version | Supported |
|---------|-----------|
| 2.1.x | Yes — actively patched |
+| 2.0.x | No — upgrade to the latest 2.1.x; it is source-compatible, see [CHANGELOG.md](CHANGELOG.md) |
| 1.9.x | Critical fixes and security backports only, on the `1.x` branch — no features |
| < 1.9 | No — upgrade required (see [CHANGELOG.md](CHANGELOG.md) for per-version migration notes, and the [2.0 modules migration guide](docs/migration/v2.0.0-modules.md) for the 1.x → 2.x step) |
diff --git a/docs/contributing/release-process.md b/docs/contributing/release-process.md
index 2b009ed93..49276f2ad 100644
--- a/docs/contributing/release-process.md
+++ b/docs/contributing/release-process.md
@@ -33,7 +33,7 @@ The shell setup and exact PowerShell commands live in the `graphcompose-release-
- [ ] `./mvnw -B -ntp clean verify` exits 0 over the whole reactor. Every test must pass — no skips, no flake retries. Confirm `Tests run: , Failures: 0, Errors: 0, Skipped: 0` per module from `*/target/surefire-reports/*.txt`. **Read Maven's own exit code** — never end the command in a pipe, or the shell reports the last stage's status and a `BUILD FAILURE` slips through as `0`.
- [ ] Examples module compiles cleanly: `./mvnw -B -ntp -q -f examples/pom.xml clean compile` exits 0. Catches `double → float` lossy narrowing and similar bugs that don't surface in the engine module.
- [ ] All examples regenerate: `./mvnw -B -ntp -q -f examples/pom.xml exec:java -Dexec.mainClass=com.demcha.examples.GenerateAllExamples` exits 0, prints one `Generated:` line per example, and emits no `Fixed column ... is smaller than required natural width` or `Spanned cell ... requires extra width` errors. (Requires `./mvnw install -DskipTests` once first so the local `~/.m2` resolves the current SNAPSHOT version — any standalone goal that resolves train modules from `~/.m2`, including the `qa` suite and `javadoc:javadoc`, needs this after a version bump.)
-- [ ] Architecture-guard suite explicitly green: `./mvnw -B -ntp test -pl :graph-compose-core -Dtest='CanonicalSurfaceGuardTest,DocumentationCoverageTest,InternalAnnotationCoverageTest,PublicApiNoEngineLeakTest,PackageMapGuardTest,VersionConsistencyGuardTest'` exits 0. These guard against legacy-API leakage in docs and engine internals leaking into the public surface, and — via `VersionConsistencyGuardTest` — against version drift between the train poms and the README install snippets. Note `PackageMapGuardTest` also scans the gitignored `docs/private/`, so a stale local note there fails a local run that CI cannot reproduce.
+- [ ] Architecture-guard suite explicitly green: `./mvnw -B -ntp test -pl :graph-compose-core -Dtest='CanonicalSurfaceGuardTest,DocumentationCoverageTest,InternalAnnotationCoverageTest,PublicApiNoEngineLeakTest,PackageMapGuardTest,VersionConsistencyGuardTest,CiGuardListGuardTest'` exits 0. These guard against legacy-API leakage in docs and engine internals leaking into the public surface, and — via `VersionConsistencyGuardTest` — against version drift between the train poms and the README install snippets. Both markdown guards skip the gitignored `docs/private/`, so local planning notes cannot fail a run that CI is unable to reproduce.
- [ ] Javadoc gate green on the published modules: `./mvnw -B -ntp javadoc:javadoc -pl :graph-compose-core,:graph-compose-render-pdf,:graph-compose-render-docx,:graph-compose-render-pptx,:graph-compose-templates,:graph-compose-testing` exits 0. The release profile publishes a javadoc jar per module with `failOnError=false`, so a broken link only surfaces here.
### C. Documentation freeze (matches target version)
@@ -43,7 +43,7 @@ The shell setup and exact PowerShell commands live in the `graphcompose-release-
- [ ] `README.md` carries no hand-maintained count (test totals, example totals) that the release would falsify. Numbers in prose have no owner and no guard — prefer prose that stays true.
- [ ] `README.md` install snippets match the **current** `pom.xml` version (on `develop` between releases that is the last published version). `VersionConsistencyGuardTest` enforces README == pom, so the two move together: `cut-release.ps1` rewrites the README Maven + Gradle install snippets to the new version in the *same* release commit it bumps the POMs (section 1, Step 2/6). The README therefore flips to the new version at release-execution time, never on `develop` ahead of the tag — a snippet pointing at a version that has not been published yet would 404 for any user who copies it. Do **not** hand-flip the README ahead of the script: that desyncs README from the still-unbumped pom and fails the guard at the verify gate.
- [ ] `ROADMAP.md` names the version being released as the current stable line, and `## Now` points at what comes after it. Not guarded by a test.
-- [ ] **`README.md` "Release status" prose block** (the `> 🟢 Latest stable: vX … 🟡 In develop: vY` blockquote near the top) names the **NEW target** as "Latest stable" and the **next cycle** as "In develop", with the new release's one-line highlights. ⚠️ `cut-release.ps1` does **not** touch this block (it only rewrites the install snippets), and the post-release cycle-open commit that would fix it is **develop-only** and never reaches `main`. So it MUST be updated **on develop before the cut** (in the `docs: pre-release fixes` commit, section 2.A) — otherwise `main` (the README GitHub renders) keeps advertising the *previous* version as "latest stable". Not guarded by a test; this checklist item is the only gate.
+- [ ] **`README.md` "Release status" prose block** (the `> 🟢 Latest stable: vX … 🟡 In development: vY` blockquote near the top) is **script-owned since v2.1.1**: `cut-release.ps1` Step 1 promotes the in-development half to latest stable and opens the next patch line, and `Assert-ReleaseMetadata` verifies it after the mutation. What you own is the **prose** — the one-line highlight after the version. Update it on develop before the cut so the release commit carries the right description to `main`; the version tokens take care of themselves. Step 0 only checks the block still exists in the shape the rewrite matches, so a reflowed blockquote aborts loudly instead of being skipped in silence.
- [ ] `README.md` and `examples/README.md` link audits resolve: every `(./...)` and `(../...)` link must exist on disk. Use `grep -oE '\(\.?\.?/[^)]+\.(md|java|png|pdf|jpg)\)' README.md examples/README.md | sed 's/^(//;s/)$//' | sort -u | xargs -I{} test -e {} || echo MISSING: {}`.
- [ ] Every example the runner produces has a `ShowcaseMetadata` entry, so the published site carries no placeholder cards: compare `examples/target/generated-pdfs` against the entries in `examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java`.
- [ ] For minor releases (`vX.Y.0`): a migration note exists under `docs/migration/`. Patch releases skip this.
diff --git a/scripts/cut-release.ps1 b/scripts/cut-release.ps1
index d84693c88..60ab7953e 100644
--- a/scripts/cut-release.ps1
+++ b/scripts/cut-release.ps1
@@ -234,15 +234,55 @@ function Get-NextSnapshotVersion($version) {
}
function Test-ReadmeLatestStable($version) {
- # The README 'Latest stable' prose block (> ... **Latest stable**: [vX.Y.Z](...)) is a
- # maintainer pre-cut edit that cut-release does NOT rewrite and no test guards. Returns
- # $true when it names $version. Checked in Step 0 BEFORE any file mutation, so a stale
- # line aborts the cut with a still-clean tree — a post-mutation check would leave a
- # dirty tree that the next preflight run then refuses to work over.
+ # True when the README 'Latest stable' prose block names $version. Used to verify
+ # the block AFTER Step 1 rewrote it — see Update-ReadmeReleaseStatus.
$readme = Get-Content (Join-Path $repoRoot 'README.md') -Raw
return $readme -match "\*\*Latest stable\*\*:\s*\[v$([regex]::Escape($version))\]"
}
+function Update-ReadmeReleaseStatus($readmePath, $newVersion) {
+ # The README 'Release status' blockquote carries two halves:
+ #
+ # > 🟢 **Latest stable**: [vX.Y.Z](…/releases/tag/vX.Y.Z) — …
+ # > · 🟡 **In development**: vX.Y.(Z+1) on `develop` — …
+ #
+ # It used to be a maintainer pre-cut hand-edit, gated in Step 0. That forced a
+ # false state: to leave `main` correct after the tag, `develop` had to advertise
+ # an unpublished version as "latest stable" for the whole cycle, with a release
+ # link that 404s. The script owns the block now — Step 1 promotes the in-development
+ # half to latest-stable and opens the next patch line — so `develop` stays truthful
+ # between releases and the release commit still carries the right text to `main`.
+ # Only the version tokens are rewritten; the maintainer's prose is left alone.
+ if (-not (Test-Path $readmePath)) {
+ Note "skip (no file): $readmePath"
+ return
+ }
+ $content = Get-Content $readmePath -Raw
+ $next = Get-NextSnapshotVersion $newVersion
+ $nextVersion = if ($next) { $next -replace '-SNAPSHOT$', '' } else { $null }
+
+ $stable = [regex]'(?<=\*\*Latest stable\*\*:\s*\[v)[\w\.\-]+(?=\])'
+ $stableUrl = [regex]'(?<=/releases/tag/v)[\w\.\-]+(?=\))'
+ $inDev = [regex]'(?<=\*\*In development\*\*:\s*v)[\w\.\-]+'
+
+ $updated = $stable.Replace($content, $newVersion, 1)
+ $updated = $stableUrl.Replace($updated, $newVersion, 1)
+ if ($nextVersion) {
+ $updated = $inDev.Replace($updated, $nextVersion, 1)
+ }
+
+ if ($updated -eq $content) {
+ Note "README release-status block already names v$newVersion"
+ return
+ }
+ if ($DryRun) {
+ Note "[DRY RUN] README release status -> latest stable v$newVersion, in development v$nextVersion"
+ return
+ }
+ [System.IO.File]::WriteAllText($readmePath, $updated)
+ Note "README release status -> latest stable v$newVersion, in development v$nextVersion"
+}
+
function Assert-ReleaseMetadata($version, $isFinalRelease) {
# Fast, build-free validation of the metadata cut-release itself rewrote in Steps 1-2.
# Runs at Step 2b — after those mutations, before commit/tag; VersionConsistencyGuardTest
@@ -271,6 +311,13 @@ function Assert-ReleaseMetadata($version, $isFinalRelease) {
} elseif ($snippet.Groups[1].Value -ne $version) {
$problems += "README.md install snippet is $($snippet.Groups[1].Value), expected $version."
}
+
+ # 2b. README release-status block names this version as latest stable (Step 1
+ # rewrites it; this is the post-mutation verification that replaced the old
+ # pre-cut hand-edit gate).
+ if (-not (Test-ReadmeLatestStable $version)) {
+ $problems += "README.md release-status block does not name v$version as 'Latest stable'."
+ }
}
# 3. Every train pom's own is this version (belt-and-suspenders ahead
@@ -779,19 +826,17 @@ try {
}
Note ("tag {0}: available OK" -f $tag)
- # README 'Latest stable' prose block must already name the target — FINAL releases
- # only. Checked HERE, before Step 1 mutates any file, so a stale line aborts with a
- # still-clean tree. (cut-release does not rewrite this block; it is a maintainer
- # pre-cut edit.) A pre-release never becomes 'Latest stable', so there is nothing to
- # check for it.
+ # The README release-status block is rewritten by Step 1 (Update-ReadmeReleaseStatus)
+ # and verified after the mutation by Assert-ReleaseMetadata, so there is nothing to
+ # demand of it here. What Step 0 still checks is that the block EXISTS in the shape
+ # the rewrite expects — a renamed or reflowed blockquote would otherwise be skipped
+ # in silence and ship `main` advertising the previous release, the v1.6.9 failure.
if ($isFinalRelease) {
- if (Test-ReadmeLatestStable $Version) {
- Note "README 'Latest stable' = v$Version OK"
- } elseif ($DryRun) {
- Write-Host " [DRY RUN] WARNING: README 'Latest stable' does not name v$Version - update it on $Branch before the real cut." -ForegroundColor Yellow
- } else {
- throw "README 'Latest stable' block does not name v$Version. Update it on $Branch before cutting (cut-release.ps1 does not rewrite this block)."
+ $readmeRaw = Get-Content (Join-Path $repoRoot 'README.md') -Raw
+ if ($readmeRaw -notmatch '\*\*Latest stable\*\*:\s*\[v[\w\.\-]+\]') {
+ throw "README has no '**Latest stable**: [vX.Y.Z](...)' release-status line for the cut to rewrite."
}
+ Note "README release-status block present OK"
}
if ($isFinalRelease) {
@@ -849,6 +894,7 @@ try {
# latest published release for a non-final (SNAPSHOT or pre-release) working version.
if ($isFinalRelease) {
Update-ReadmeInstallVersion (Join-Path $repoRoot 'README.md') $Version
+ Update-ReadmeReleaseStatus (Join-Path $repoRoot 'README.md') $Version
# Per-module README install snippets (train modules only — fonts/emoji pin
# their own independent versions).
foreach ($moduleReadme in @('core/README.md', 'render-pdf/README.md', 'render-docx/README.md',
diff --git a/web/index.html b/web/index.html
index c857a52ab..60f5ef81c 100644
--- a/web/index.html
+++ b/web/index.html
@@ -3,7 +3,7 @@
-
+
GraphCompose — Java PDF layout engine for structured business documents
@@ -30,7 +30,7 @@
-
+
@@ -94,8 +94,7 @@
{
"@type": "ItemList",
"name": "GraphCompose showcase examples",
- "description": "80+ runnable Java examples that produce real PDFs — CV templates, cover letters, invoices, proposals, schedules, plus every supported feature.",
- "numberOfItems": 51,
+ "description": "Runnable Java examples that produce real PDFs — CV templates, cover letters, invoices, proposals, schedules, plus every supported feature.",
"itemListElement": [
{
"@type": "ListItem",
@@ -263,7 +262,7 @@ 30-second hello world
Showcase
-
50+ generated PDFs you can inspect.
+
Generated PDFs you can inspect.
Every example below is a real, runnable Java file. Click the preview to open the rendered PDF, or jump straight into the source.