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..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
@@ -69,6 +75,40 @@ 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, 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
+ 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..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) — 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.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.
@@ -212,71 +213,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 +284,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.
-### 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).
+### 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.
-### 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)
+### 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` + 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) |
+
+> **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.
+
+## 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..8d35966cc 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -9,7 +9,9 @@ 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) |
+| 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) |
## Reporting a vulnerability
@@ -44,7 +46,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..49276f2ad 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.)
-- [ ] 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.
+- [ ] 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,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)
- [ ] `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.
-- [ ] **`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.
+- [ ] `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 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: {}`.
-- [ ] `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/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 33b4cd3f2..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": "50+ 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.