From dde5a8af0402e75016aeaa3978fe91590a83823b Mon Sep 17 00:00:00 2001
From: DemchaAV
Date: Sat, 1 Aug 2026 10:34:51 +0100
Subject: [PATCH 1/2] docs: put the template-authoring guides on the packages
that exist
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
A contributor following the authoring guide wrote code that does not
compile. It described a family as `templates..v2` with exactly
five sub-packages, one of them a per-family `theme/`; 2.0 dropped the
`.v2` level and moved the tokens into the shared `templates.core.theme`,
leaving four. The contributing guide routed the same work into
`templates.builtins` and `templates.support`, neither of which has held a
file since the split, and still listed the weekly schedule among the
built-in templates although its document is composed by an example.
Package names join the retired-surface guard. It could only match types,
which is why two dead package names sat in the document that sends every
new contributor to them.
Also: the extension guide pointed at a snapshot-test directory that is
not in the repository, the layout-snapshot page listed two tests among
six that are not either, the docs index carried a rework warning for a
page reworked in 2.0, and the roadmap asked for a warning on dropped
DOCX content that the backend already emits — the part still missing is
a mode that refuses instead of dropping, which is what it now asks for.
---
CHANGELOG.md | 15 +++
CONTRIBUTING.md | 15 +--
ROADMAP.md | 2 +-
.../CanonicalSurfaceGuardTest.java | 9 +-
docs/README.md | 6 +-
docs/contributing/extension-guide.md | 6 +-
docs/operations/layout-snapshot-testing.md | 2 -
.../templates/v2-layered/contributor-guide.md | 103 ++++++++----------
.../LayoutSnapshotRegressionExample.java | 2 +-
9 files changed, 86 insertions(+), 74 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 17eb8177..c495bec6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -127,6 +127,21 @@ follow semantic versioning; release dates are ISO 8601.
### Documentation
+- **The template-authoring guide describes the packages that exist.** It told a
+ contributor to put a new family under `templates..v2` in exactly five
+ sub-packages, one of them a per-family `theme/` — a layout 2.0 replaced. A family has
+ `data` / `components` / `widgets` / `presets`, the cosmetic tokens are the shared
+ `BrandTheme`, and the rule about not editing the v1 surface outlived that surface. The
+ contributing guide had the same problem in one paragraph, routing new template code
+ into `templates.builtins` and `templates.support`; both are gone, so the instruction
+ produced code that does not compile. Package names join the retired-surface guard,
+ which until now could only see types.
+- **Three documents stop pointing at things that are not there.** The extension guide
+ sent a reader to a snapshot-test directory that does not exist, the layout-snapshot
+ page listed two tests among six that are not in the repository, and the docs index
+ still carried a rework warning for a page reworked a major release ago. The roadmap
+ asked for a warning on dropped DOCX content that the backend already logs; what it is
+ really asking for — a mode that refuses instead of dropping — now says so.
- **The example catalogue stops offering a type that ships nowhere.** Its entry-point
table introduced the cover letter as a `BusinessTheme.modern()` document, and
`BusinessTheme` is a record local to the examples module — a reader adding the
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f47981ff..c0e9fd0d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -120,7 +120,7 @@ The 2.0 GA shipped, so the branches now hold their long-term roles:
- `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
+ Template families (CV, cover letter, invoice, proposal), their data records, the shared `BrandTheme`, and the widgets they compose from. Schedule data records live here too; the schedule document is composed by an example rather than by a template.
- `core/src/main/java/com/demcha/compose/document/showcase`
`FontShowcase` (bundled-font preview renderer) — stays in the core engine
- `core/src/main/java/com/demcha/compose/engine/*`
@@ -176,12 +176,13 @@ template feature. The rules:
- Layout integration for a new node is a `NodeDefinition`
registered with `NodeRegistry`. See `BuiltInNodeDefinitions` for
the established pattern.
-- Built-in templates in `...document.templates.builtins` stay thin
- public facades over reusable scene composers in
- `...document.templates.support`. Keep PDF-only setup in the document
- session/backend layer rather than inside template composers, and do
- not import `PDDocument`, `PDPage`, `PDRectangle`, or low-level PDF
- composer types into scene composer classes.
+- A template family lives under `...document.templates.` with
+ `data` / `components` / `widgets` / `presets`; cosmetic tokens are the
+ shared `BrandTheme` in `...document.templates.core.theme`, never a
+ package of the family's own. Keep PDF-only setup in the document
+ session/backend layer rather than inside a preset, and do not import
+ `PDDocument`, `PDPage`, `PDRectangle` or other low-level PDF types into
+ template code.
- Public template contracts are compose-first: prefer
`compose(DocumentSession, ...)`. New README snippets, runnable
examples, and integration docs must show `compose(...)` rather than
diff --git a/ROADMAP.md b/ROADMAP.md
index 98fa0d4d..a51cf188 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -37,7 +37,7 @@ Committed internal direction for the post-2.0 line: refactors, scale work, and t
Not committed. Reflects current thinking; priorities may shift based on user feedback and adoption signals.
- **PPTX beyond beta.** Graduate the fixed-layout PPTX backend from `@Beta` to stable and close the remaining fidelity gaps — true vector clipping instead of the raster fallback ([#413](https://github.com/DemchaAV/GraphCompose/issues/413)), exact numeric dash arrays, and distinct per-corner radii.
-- **DOCX visibility for unsupported nodes.** Make currently-silent skips (`shape`, `line`, `ellipse`, `barcode`) loud — minimum a warn log, ideally a strict-mode flag that fails instead of dropping content silently.
+- **A strict mode for DOCX.** Dropping a node the format cannot carry (`shape`, `line`, `ellipse`, `barcode`) now warns, so the loss is at least visible in the log. What is missing is the option to refuse: a flag that fails the export instead of silently producing a document with content gone.
- **Block-level alignment for fixed-size flow children.** Paths, images, layer stacks, shape containers and barcodes currently left-align in a flow; centring one means wrapping it in a full-width `ShapeContainer` just to use its CENTER anchor. Add a per-node horizontal align (left / centre / right — the `margin: auto` / `align(center)` analogue) so a fixed box can place itself in the flow directly. Surfaced by the v1.8 SVG icon-gallery and feature-catalog work.
- **Backend-neutral layout measurement.** Decouple measurement from PDFBox-specific resources so non-PDF backends do not pull PDFBox into the dependency graph.
- **DOCX maturity.** Either expand DOCX coverage toward PDF parity, or move DOCX behind an explicitly experimental flag.
diff --git a/core/src/test/java/com/demcha/documentation/CanonicalSurfaceGuardTest.java b/core/src/test/java/com/demcha/documentation/CanonicalSurfaceGuardTest.java
index 0ba64a6d..5434edc9 100644
--- a/core/src/test/java/com/demcha/documentation/CanonicalSurfaceGuardTest.java
+++ b/core/src/test/java/com/demcha/documentation/CanonicalSurfaceGuardTest.java
@@ -49,7 +49,14 @@ class CanonicalSurfaceGuardTest {
"Breakable",
"hasRender(",
"CvSpec",
- "CvBuilder");
+ "CvBuilder",
+ // Package names, not types. The retired-type list could not catch a
+ // contributing guide that routed new template code into
+ // `templates.builtins` and `templates.support` — two packages the 2.0
+ // split left behind and nothing has occupied since, so the instruction
+ // read as current and produced code that does not compile.
+ "templates.builtins",
+ "templates.support");
/**
* Types a contributor must not be pointed at when told how to build against the
diff --git a/docs/README.md b/docs/README.md
index efc6ff57..74f6930d 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -34,8 +34,8 @@ back here.
- **[troubleshooting.md](troubleshooting.md)** — symptom-first fixes for common gotchas: stray `?` glyphs, silent DOCX drops, optional-dependency `NoClassDefFoundError`, running the bundled examples.
### Templates
-- **[templates/business-templates.md](templates/business-templates.md)** — invoice & proposal templates: the compose-first contract, end to end. ⚠️ Being reworked for the layered `ModernInvoice` / `ModernProposal` surface.
-- **[templates/v2-layered/](templates/v2-layered/)** — the template surface (CV is the reference implementation): `data` / `theme` / `components` / `widgets` / `presets`.
+- **[templates/business-templates.md](templates/business-templates.md)** — invoice & proposal templates: the compose-first contract, end to end, on the layered `ModernInvoice` / `ModernProposal` surface.
+- **[templates/v2-layered/](templates/v2-layered/)** — the template surface (CV is the reference implementation): `data` / `components` / `widgets` / `presets` per family, over the shared `templates.core.theme`.
- **[templates/v1-classic/](templates/v1-classic/)** — 🗄️ archived: the classic spec/builder/presets surface removed in 2.0; kept for pre-2.0 callers.
### Output backends
@@ -107,7 +107,7 @@ it does.
- **[adr/0012-nested-list-evolution.md](adr/0012-nested-list-evolution.md)** — nested list rendering evolution.
- **[adr/0013-composed-table-cell.md](adr/0013-composed-table-cell.md)** — composed table cell model.
- **[adr/0014-controlled-absolute-placement.md](adr/0014-controlled-absolute-placement.md)** — controlled absolute placement strategy.
-- **[adr/0015-layered-template-architecture.md](adr/0015-layered-template-architecture.md)** — the layered `cv.v2` / `coverletter.v2` authoring model (current standard); supersedes the preset/builder portion of 0011.
+- **[adr/0015-layered-template-architecture.md](adr/0015-layered-template-architecture.md)** — the layered `templates.cv` / `templates.coverletter` authoring model (current standard); supersedes the preset/builder portion of 0011.
> **ADR numbering gap (0005–0010)** is intentional — those numbers
> were reserved during a v1.5 restructure that landed under ADR 0011
diff --git a/docs/contributing/extension-guide.md b/docs/contributing/extension-guide.md
index 082e9524..02546114 100644
--- a/docs/contributing/extension-guide.md
+++ b/docs/contributing/extension-guide.md
@@ -212,9 +212,9 @@ Set-up per node:
The
[`LayoutSnapshotAssertions`](../../testing/src/main/java/com/demcha/compose/testing/layout/LayoutSnapshotAssertions.java)
-helper class wraps the diff machinery; existing snapshot tests under
-`core/src/test/java/com/demcha/compose/document/templates/builtins/`
-(e.g. `BuiltInTemplateLayoutSnapshotTest`) show the call site shape.
+helper class wraps the diff machinery; the snapshot tests in the `qa`
+module (`ChartLayoutSnapshotTest`, `ShapeContainerLayoutSnapshotTest`)
+show the call site shape.
## Reading the source by responsibility
diff --git a/docs/operations/layout-snapshot-testing.md b/docs/operations/layout-snapshot-testing.md
index 518a04e0..b1afdf30 100644
--- a/docs/operations/layout-snapshot-testing.md
+++ b/docs/operations/layout-snapshot-testing.md
@@ -340,8 +340,6 @@ Prioritize documents that are most sensitive to layout regressions:
## Examples in this repository
-- `RepositoryShowcaseRenderTest`
-- `TablePaginationIntegrationTest`
- `FontShowcaseLayoutSnapshotTest`
- `ChartLayoutSnapshotTest`
- `ShapeContainerLayoutSnapshotTest`
diff --git a/docs/templates/v2-layered/contributor-guide.md b/docs/templates/v2-layered/contributor-guide.md
index aab4da0f..7bcecdd8 100644
--- a/docs/templates/v2-layered/contributor-guide.md
+++ b/docs/templates/v2-layered/contributor-guide.md
@@ -1,9 +1,9 @@
# Contributor Guide — add a new template family
You're a GraphCompose contributor and you want to bring a brand-new
-document type onto the layered architecture — invoice-v2,
-cover-letter-v2, report-v2, anything that isn't CV. This doc is
-your checklist + convention reference.
+document type onto the layered architecture — a report, a
+statement, anything the shipped families do not already cover. This
+doc is your checklist + convention reference.
It assumes you've read [quickstart.md](quickstart.md) and
[authoring-presets.md](authoring-presets.md) — those explain *why*
@@ -13,7 +13,7 @@ the layered pattern looks the way it does.
## Table of contents
-1. [The 5-layer convention](#the-5-layer-convention)
+1. [The layer convention](#the-5-layer-convention)
2. [Package map for a new family](#package-map-for-a-new-family)
3. [Naming rules](#naming-rules)
4. [Worked walkthrough — `invoice`](#worked-walkthrough)
@@ -26,30 +26,35 @@ the layered pattern looks the way it does.
---
-## The 5-layer convention
+## The layer convention
Every new template family lives under
-`com.demcha.compose.document.templates..v2` with **exactly
-five sub-packages**:
+`com.demcha.compose.document.templates.` with these
+sub-packages:
```
-/v2/
+/
├── data/ records describing what's on the page
-├── theme/ cosmetic tokens (palette, typography, spacing, decoration)
├── components/ internal renderers + low-level primitives
-├── widgets/ reusable visual LEGO bricks
+├── widgets/ reusable visual LEGO bricks — omit if the family has none
└── presets/ composition: data + theme + widgets → DocumentTemplate
```
+Cosmetic tokens are **not** per-family. `templates.core.theme` holds
+`BrandTheme` and its parts (`Palette`, `Typography`, `Spacing`,
+`Decoration`), and every family reads from it — that is what keeps two
+presets from two families looking like one product. A family that needs
+a token nobody else has adds it there, not to a package of its own.
+
Each layer's contract:
| Layer | Contract |
|---|---|
| `data/` | Pure records. Zero dependencies on rendering, theming, or DSL. Sealed hierarchy for section / block subtypes. |
-| `theme/` | Records + factories. No rendering logic. Aggregate root is `Theme(palette, typography, spacing, decoration)`. |
+| `templates.core.theme` | Shared, not per-family. `BrandTheme(palette, typography, spacing, decoration)` plus its parts. Records + factories, no rendering logic. |
| `components/` | Static helpers. Take `(host, data, theme)`. No statics holding state. No magic numbers — read tokens from theme. |
-| `widgets/` | Static helpers. Named factory methods per visual variant. Compose internally from `components/`. |
-| `presets/` | One `public final class` per visual style. Two factories: `create()` and `create(Theme)`. Inner `Template` implements `DocumentTemplate<Document>`. |
+| `widgets/` | Static helpers. Named factory methods per visual variant. Compose internally from `components/`. Optional — the cover-letter family has none. |
+| `presets/` | One `public final class` per visual style. Two factories: `create()` and `create(BrandTheme)`. Inner `Template` implements `DocumentTemplate<Document>`. |
**The convention is the same regardless of domain.** A cv has
identity + sections; an invoice has parties + line items + totals; a
@@ -61,7 +66,7 @@ data differs; the *layering* is identical.
## Package map for a new family
-Mirror the CV v2 layout. Concrete example for invoice:
+Mirror the CV layout. Concrete example for invoice:
```
templates/src/main/java/com/demcha/compose/document/templates/invoice/
@@ -79,13 +84,6 @@ templates/src/main/java/com/demcha/compose/document/templates/invoice/
│ ├── TotalsSection.java ← concrete subtype
│ ├── NotesSection.java ← concrete subtype
│ └── Slot.java ← if multi-column variants needed
-├── theme/
-│ ├── package-info.java
-│ ├── InvoicePalette.java ← ink / muted / rule / accent
-│ ├── InvoiceTypography.java ← scale
-│ ├── InvoiceSpacing.java ← margins, gaps
-│ ├── InvoiceDecoration.java ← row separators, totals divider
-│ └── InvoiceTheme.java ← aggregate + factories
├── components/
│ ├── package-info.java
│ ├── ParagraphPrimitive.java ← internal — package-private
@@ -116,13 +114,13 @@ differ.
- **Family prefix** on top-level records to avoid name collisions.
CV uses `CvName`, `CvIdentity`, `CvSection`. Invoice should use
- `InvoiceParty`, `InvoiceLine`, `InvoiceTheme`. Cover letter:
- `CoverLetterRecipient`, `CoverLetterTheme`. Etc.
+ `InvoiceParty`, `InvoiceLine`. Cover letter:
+ `CoverLetterRecipient`, `CoverLetterBody`. Etc.
- **`Document`** for the root record. (`CvDocument`,
`InvoiceDocument`, `CoverLetterDocument`.)
- **`Section` (sealed)** for the body content hierarchy.
-- **`Theme(palette, typography, spacing, decoration)`** for
- the aggregate theme record.
+- **No `Theme`.** The aggregate theme record is the shared
+ `BrandTheme`; a family names a factory on it, not a type of its own.
- **Widgets** are domain-specific verbs / nouns describing the
visual: `Headline`, `ContactLine`, `SectionHeader` (CV);
`Letterhead`, `LineTable`, `TotalsCard` (invoice). Don't try to
@@ -162,23 +160,16 @@ Sealed `InvoiceSection` lists every body shape an invoice can have.
Concrete subtypes (`LineItemsSection`, `TotalsSection`, …) are
records carrying the section data.
-### 2. `theme/` second (no rendering, just tokens)
+### 2. the theme second (there is nothing to write)
+
+Pick the `BrandTheme` factory the family should default to, or add
+one beside the others in `templates.core.theme` if none fits. The
+record is shared, so a token added for an invoice is available to a
+CV — which is the point, and also the reason to think before adding
+one.
```java
-public record InvoiceTheme(
- InvoicePalette palette,
- InvoiceTypography typography,
- InvoiceSpacing spacing,
- InvoiceDecoration decoration) {
-
- public static InvoiceTheme classic() {
- return new InvoiceTheme(
- InvoicePalette.classic(),
- InvoiceTypography.classic(),
- InvoiceSpacing.classic(),
- InvoiceDecoration.classic());
- }
-}
+BrandTheme theme = BrandTheme.invoiceModern();
```
### 3. `components/` third (low-level renderers consume data + theme)
@@ -188,7 +179,7 @@ public final class LineRowRenderer {
private LineRowRenderer() {}
public static void render(SectionBuilder host, InvoiceLine line,
- InvoiceTheme theme) {
+ BrandTheme theme) {
// … DSL calls reading theme tokens …
}
}
@@ -205,7 +196,7 @@ public final class LineTable {
private LineTable() {}
public static void render(SectionBuilder host,
- List lines, InvoiceTheme theme) {
+ List lines, BrandTheme theme) {
for (InvoiceLine line : lines) {
LineRowRenderer.render(host, line, theme);
}
@@ -227,10 +218,10 @@ public final class ClassicInvoice {
private ClassicInvoice() {}
public static DocumentTemplate create() {
- return create(InvoiceTheme.classic());
+ return create(BrandTheme.invoiceModern());
}
- public static DocumentTemplate create(InvoiceTheme theme) {
+ public static DocumentTemplate create(BrandTheme theme) {
return new Template(theme);
}
@@ -250,15 +241,15 @@ Mirror the CV preset shape exactly.
A new template family PR ships with:
-- [ ] **5 packages** under `/v2/` populated per convention
+- [ ] **The sub-packages** under `/` populated per convention
- [ ] **At least 1 reference preset** that renders a sample document
- [ ] **`AUTHORS.md`** in the family root (recipe cookbook — copy
the cv one as starting structure)
- [ ] **`package-info.java`** at family root + each sub-package
- [ ] **Sample fixture** in `ExampleDataFactory.sampleDocumentV2()`
-- [ ] **Example runner** in `examples/.../templates//v2/`
+- [ ] **Example runner** under `examples/.../templates//`
- [ ] **Smoke tests** per the checklist below
-- [ ] **No edits** to `engine/`, `dsl/`, or v1 `/` surface
+- [ ] **No edits** to `engine/` or `dsl/`
---
@@ -339,13 +330,13 @@ to re-bless or fix.
## Doc checklist
-- [ ] `/v2/package-info.java` — ASCII diagram of the 5
- layers, plus a 4-step "how to author a document" walkthrough
- (copy the cv one's structure).
-- [ ] `/v2/AUTHORS.md` — recipe cookbook. At least:
+- [ ] `/package-info.java` — ASCII diagram of the layers,
+ plus a 4-step "how to author a document" walkthrough (copy the
+ cv one's structure).
+- [ ] `/AUTHORS.md` — recipe cookbook. At least:
change a glyph, change colours, add a new section subtype,
conditional sections.
-- [ ] `/v2//package-info.java` — each
+- [ ] `//package-info.java` — each
sub-package gets a paragraph explaining its role.
- [ ] **Update [`docs/templates/v2-layered/README.md`](README.md)** to
list the new family in the "implementations" section.
@@ -359,9 +350,9 @@ to re-bless or fix.
`document/engine`, `document/node`, `document/style`). If your
family needs an engine feature that doesn't exist, that's a
separate prerequisite PR.
-- ❌ **Don't edit v1 surface** for the same family. They coexist.
- Mark v1 `@Deprecated` only after the v2 surface is feature-complete
- and shipped — that's a follow-up PR.
+- ❌ **Don't add a theme package to your family.** Tokens live in
+ `templates.core.theme` so families stay visually consistent; a
+ private palette is how that consistency is lost one family at a time.
- ❌ **Don't fork widgets across families.** If invoice needs a
`Headline`, write `templates/invoice/widgets/Letterhead.java`
— an invoice letterhead has different needs from a CV name
@@ -395,7 +386,7 @@ A new template family PR is reviewed against:
4. **Visual signature** — render the reference preset, attach the
PDF to the PR description, eyeball-validate it matches the
intent.
-5. **No engine / v1 edits** — additive only.
+5. **No engine edits** — additive only.
Expected size: ~1500-2500 lines of new code for a fresh family.
Compare to cv baseline (PR #45) which was 2082 lines including
diff --git a/examples/src/main/java/com/demcha/examples/features/snapshots/LayoutSnapshotRegressionExample.java b/examples/src/main/java/com/demcha/examples/features/snapshots/LayoutSnapshotRegressionExample.java
index 6385003c..85b0e5c3 100644
--- a/examples/src/main/java/com/demcha/examples/features/snapshots/LayoutSnapshotRegressionExample.java
+++ b/examples/src/main/java/com/demcha/examples/features/snapshots/LayoutSnapshotRegressionExample.java
@@ -45,7 +45,7 @@
* GraphCompose source tree calls.
*
* See {@code docs/recipes/streaming.md} and the existing
- * {@code BuiltInTemplateLayoutSnapshotTest} for the production
+ * {@code ChartLayoutSnapshotTest} for the production
* pattern.
*
* @author Artem Demchyshyn
From 833756a12895bf04b9e7119d497cf52128aba2bc Mon Sep 17 00:00:00 2001
From: DemchaAV
Date: Sat, 1 Aug 2026 10:51:14 +0100
Subject: [PATCH 2/2] docs: finish the layout correction in the two pages that
also taught it
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The guide was fixed and the pages around it were not, so the folder's own
landing page still described `/v2/data` with a per-family `theme/`
box, and the contributing guide contradicted itself a hundred lines below
the paragraph that had just been rewritten. A reader routed from the docs
index lands on the landing page first.
The four-sub-package claim was also wrong in the other direction: only
`presets/` is always there. CV carries all four, cover letter has no
`widgets/`, and invoice and proposal are presets alone over records in
the shared `templates.data.` — which made the worked walkthrough,
built around invoice, describe directories that are not on disk. It now
presents the fullest shape and says which families take less.
Three leftovers in the same file: a required test for a `Theme`
the guide forbids two sections earlier, and two pointers into `core/` for
a parity test and its baselines that moved to `qa` in the module split.
The preset cookbook had the third.
The retired-package tokens now cover the path spelling as well as the
dotted one — the sentence that carried a dead package for a release
wrote it as a path.
---
CONTRIBUTING.md | 6 +--
.../CanonicalSurfaceGuardTest.java | 26 ++++++++-----
docs/templates/v2-layered/README.md | 37 ++++++++++---------
.../templates/v2-layered/authoring-presets.md | 2 +-
.../templates/v2-layered/contributor-guide.md | 19 ++++++----
5 files changed, 50 insertions(+), 40 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c0e9fd0d..ff75325d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -276,9 +276,9 @@ 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, over the shared
-`templates.core` layer.
+A family has `presets/` and adds `data/`, `components/` and `widgets/`
+as it needs them, each with a clear contract, over the shared
+`templates.core` layer that owns the theme.
- Every preset is a `public final class` — no inheritance — with a
`create(BrandTheme)` factory returning `DocumentTemplate`, plus a
diff --git a/core/src/test/java/com/demcha/documentation/CanonicalSurfaceGuardTest.java b/core/src/test/java/com/demcha/documentation/CanonicalSurfaceGuardTest.java
index 5434edc9..a97156d5 100644
--- a/core/src/test/java/com/demcha/documentation/CanonicalSurfaceGuardTest.java
+++ b/core/src/test/java/com/demcha/documentation/CanonicalSurfaceGuardTest.java
@@ -29,13 +29,14 @@ class CanonicalSurfaceGuardTest {
"ModuleSummary");
/**
- * Types the 2.0 line removed. Every one is absent from every {@code src/main}
- * tree, so a contributor-facing document naming one is teaching code that cannot
- * compile — which is how the template-authoring and engine-primitive sections of
- * CONTRIBUTING went stale for a full release cycle.
+ * Names the 2.0 line removed — types, and the packages that held them. Every one is
+ * absent from every {@code src/main} tree, so a contributor-facing document naming
+ * one is teaching code that cannot compile — which is how the template-authoring
+ * and engine-primitive sections of CONTRIBUTING went stale for a full release
+ * cycle, and how two dead package names outlived the types that lived in them.
*
* Deliberately absent: {@code BusinessTheme} survives as an examples-local
- * theme helper used by twenty-one example sources, and {@code PptxSemanticBackend}
+ * theme helper the example catalogue still uses, and {@code PptxSemanticBackend}
* still ships beside the fixed-layout PPTX backend. Forbidding either would fail
* the build on text that is correct.
*/
@@ -54,9 +55,14 @@ class CanonicalSurfaceGuardTest {
// contributing guide that routed new template code into
// `templates.builtins` and `templates.support` — two packages the 2.0
// split left behind and nothing has occupied since, so the instruction
- // read as current and produced code that does not compile.
+ // read as current and produced code that does not compile. Both spellings
+ // are listed: a doc names a package either as a dotted coordinate or as the
+ // path it lives at, and the sentence that carried this one for a release
+ // used the path.
"templates.builtins",
- "templates.support");
+ "templates.support",
+ "templates/builtins/",
+ "templates/support/");
/**
* Types a contributor must not be pointed at when told how to build against the
@@ -218,9 +224,9 @@ void contributorFacingDocsShouldNotNameSurfaceRetiredIn2_0() throws IOException
}
assertThat(violations)
- .describedAs("these documents name a type 2.0 removed, so anyone following "
- + "them writes code that does not compile. A document whose purpose "
- + "is to record the removal belongs under one of %s.",
+ .describedAs("these documents name a type or package 2.0 removed, so anyone "
+ + "following them writes code that does not compile. A document whose "
+ + "purpose is to record the removal belongs under one of %s.",
HISTORICAL_RECORD_PREFIXES)
.isEmpty();
}
diff --git a/docs/templates/v2-layered/README.md b/docs/templates/v2-layered/README.md
index 36a988b5..00cd3ccc 100644
--- a/docs/templates/v2-layered/README.md
+++ b/docs/templates/v2-layered/README.md
@@ -52,20 +52,19 @@ inline DSL, and how to ship a new preset as ~150 lines that anyone can
read end-to-end.
### 🛠 You're adding a new template family to the library
-You're a GraphCompose maintainer or contributor. You want to bring
-invoice-v2, cover-letter-v2, or a new document type onto the same
-5-layer pattern that CV uses.
+You're a GraphCompose maintainer or contributor. You want to bring a
+new document type onto the same layering the shipped families use.
→ **[contributor-guide.md](contributor-guide.md)**
-You'll get the package convention (`/v2/data` /
-`theme` / `components` / `widgets` / `presets`), naming rules, test
-expectations, doc expectations, and a worked checklist for a new
-template family from empty folder to merged PR.
+You'll get the package convention (`/data` / `components` /
+`widgets` / `presets` over the shared `templates.core.theme`), naming
+rules, test expectations, doc expectations, and a worked checklist for a
+new template family from empty folder to merged PR.
---
-## The 5-layer pattern at a glance
+## The layering at a glance
```
presets/ composition: data + theme + widgets → DocumentTemplate
@@ -74,10 +73,11 @@ presets/ composition: data + theme + widgets → DocumentTemplate
widgets/ LEGO bricks: Headline, Subheadline, ContactLine, SectionHeader, …
│ delegate to read tokens from
▼ ▼
-components/ internal renderers + primitives theme/ palette
- typography
- spacing
- decoration
+components/ internal renderers + primitives templates.core.theme
+ BrandTheme: palette
+ typography
+ spacing
+ decoration
│ render
▼
data/ records describing what to render (no styling)
@@ -95,16 +95,17 @@ The detailed contract for each layer is in
## What this pattern is *not*
-- ❌ **Not a migration mandate.** Existing v1 templates
- (`cv/spec`, `cv/builder`, `cv/presets`) continue to work and
- ship. The layered pattern is for **new** templates and major
- rewrites.
+- ❌ **Not one shape for every family.** `presets/` is the only
+ package a family always has. CV carries all four; cover letter has no
+ `widgets/`; invoice and proposal are presets alone, reading data
+ records from the shared `templates.data.`. Add a layer when
+ the family needs one.
- ❌ **Not a framework with magic.** Every file is plain
Java records + static helpers. No reflection, no annotations,
no codegen.
- ❌ **Not coupled to CV.** The pattern is domain-agnostic; CV is
- just the first family migrated. Invoice or cover-letter would
- use the same five folders with their own data shapes inside.
+ just the family that uses every layer, which is why it is the
+ reference implementation.
- ❌ **Not a UI framework.** No state, no events, no lifecycle.
Templates render static PDFs from immutable data.
diff --git a/docs/templates/v2-layered/authoring-presets.md b/docs/templates/v2-layered/authoring-presets.md
index 0ee2259a..a54739ac 100644
--- a/docs/templates/v2-layered/authoring-presets.md
+++ b/docs/templates/v2-layered/authoring-presets.md
@@ -384,7 +384,7 @@ When you do add a new widget:
A new preset needs at least:
-1. **Smoke test** in `core/src/test/.../cv/presets/MyPresetSmokeTest.java`:
+1. **Smoke test** in `qa/src/test/.../cv/presets/MyPresetSmokeTest.java`:
- `exposes_stable_identity` — checks `id()` and `displayName()`
- `default_factory_renders` — calls `create().compose(...)` with
a full sample document, asserts `session.roots()` is non-empty
diff --git a/docs/templates/v2-layered/contributor-guide.md b/docs/templates/v2-layered/contributor-guide.md
index 7bcecdd8..e0049dcb 100644
--- a/docs/templates/v2-layered/contributor-guide.md
+++ b/docs/templates/v2-layered/contributor-guide.md
@@ -66,10 +66,14 @@ data differs; the *layering* is identical.
## Package map for a new family
-Mirror the CV layout. Concrete example for invoice:
+The fullest shape, which is CV's. A family takes the layers it needs and
+no more: cover letter has no `widgets/`, and the shipped invoice and
+proposal families are `presets/` alone, reading their records from the
+shared `templates.data.`. The tree below is an invoice-shaped
+domain drawn at full size, so every layer has something in it:
```
-templates/src/main/java/com/demcha/compose/document/templates/invoice/
+templates/src/main/java/com/demcha/compose/document/templates//
├── package-info.java ← ASCII diagram + 4-step walkthrough
├── AUTHORS.md ← recipe cookbook
├── data/
@@ -104,8 +108,8 @@ templates/src/main/java/com/demcha/compose/document/templates/invoice/
└── (more as added)
```
-The structure is **identical** to cv — only the records inside
-differ.
+That is cv's structure with invoice records inside. A smaller family
+keeps `presets/` and drops the rest.
---
@@ -261,7 +265,6 @@ Minimum test coverage matching CV v2:
| Test class | What it asserts |
|---|---|
| `DocumentTest` | Builder rejects null / blank required fields; valid build succeeds |
-| `ThemeTest` | All factories produce valid themes; deprecated constructors (if any) wrap correctly |
| `SmokeTest` | `id()`, `displayName()`, default-factory render, custom-theme render |
| `SectionDispatcherTest` *(optional)* | Each sealed subtype routes correctly |
| `WidgetSmokeTest` | Each public widget variant renders without throwing |
@@ -292,7 +295,7 @@ git commit -m "test: refresh visual baselines after "
```
**Where baselines live:**
-`core/src/test/resources/visual-baselines/-v2-layered/-page-N.png`
+`qa/src/test/resources/visual-baselines/-v2-layered/-page-N.png`
One PNG per page per preset. Pages overflow naturally — a 2-page
preset gets `-page-0.png` and `-page-1.png`.
@@ -322,7 +325,7 @@ baseline so a reviewer can see exactly what changed before deciding
to re-bless or fix.
**Reference**: see
-`core/src/test/java/com/demcha/compose/document/templates/cv/presets/CvV2VisualParityTest.java`
+`qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CvV2VisualParityTest.java`
— a 200-line drop-in template you can copy for a new family.
---
@@ -380,7 +383,7 @@ A new template family PR is reviewed against:
1. **Layer discipline** — do data / theme / components / widgets /
presets each obey their contract?
2. **Test coverage** — smoke tests for every public surface;
- builder validation tests; theme factory tests.
+ builder validation tests.
3. **Doc completeness** — `package-info.java` everywhere,
`AUTHORS.md` with at least 4 recipes, root README updated.
4. **Visual signature** — render the reference preset, attach the