This document tracks the post-1.5 plan. v1.5 (the "intuitive" release) locked in the cinematic visual surface — shape-as-container, transforms on every leaf builder, advanced tables, two cinematic templates, and a fully canonical-first authoring narrative. v1.6 closes the remaining canonical-vs-legacy parity gaps for advanced authoring without reanimating the legacy low-level entry points.
"Expressive." Authors get the few remaining advanced primitives
that the retired low-level entity-assembly entry points expressed
naturally — nested list structures, rich content inside table cells,
and an explicit "controlled free canvas" — but each ships through the
established DocumentNode + NodeDefinition + render handler extension
seam, not through a low-level bypass.
The architectural rule stays: every new public primitive is a semantic
record, every new layout behaviour is a NodeDefinition, every new
backend behaviour is a render handler. Public entry points remain
GraphCompose.document(...) → DocumentSession → DocumentDsl. The
retired legacy PDF entry point and public EntityManager are not
returning.
| Status | Meaning |
|---|---|
| Planned | Specification accepted, implementation not started. |
| In progress | Implementation underway; track via v1.6.0-betaN CHANGELOG entries. |
| Done | Available on develop and covered by tests. |
| Stretch | Goal for v1.6 if time allows; otherwise carried into v1.7. |
| Non-goal | Explicitly out of scope — see "Non-goals" below. |
ListBuilder currently flattens to a single level; authors compose
sub-bullets through nested addSection(...) calls, which loses marker
semantics and makes pagination harder. Phase A lands a real nested
list contract.
Public API
ListBuilder.addItem(String label, Consumer<ListBuilder> body)— appends a list item with a label and a builder callback for nested items.- New
ListItemvalue type carrying(label, marker, children). ListNoderecord signature extended to carryList<ListItem>plus the existing top-level fields. Back-compat constructor preserves every v1.4 / v1.5 caller.
Architecture
ListNodeDefinition(inBuiltInNodeDefinitions) walks the nested tree at prepare time. Marker resolution honours per-levelListMarkerdefaults (bulleted depth 0 → ◦ depth 1 → · depth 2, numbered depth 0 → 1. depth 1 → 1.1 depth 2). Authors override per-level viaListBuilder.markerFor(int depth, ListMarker).- Pagination policy stays "list is splittable on item boundary"; individual items remain atomic.
Tests
ListBuilderNestedTestcovers depth ≥ 3, mixed bulleted/numbered nesting, marker inheritance, override-per-depth, and the back-compat invariant for flat lists.- Layout snapshot baseline
nested_list_three_levels.json.
ADR
docs/adr/0005-nested-list-evolution.mdrecords theListNode-extension-vs-new-NestedListNodedecision.
DocumentTableCell currently accepts only lines (plain text runs).
Real reports want a paragraph with a styled status keyword, a small
inline icon row, or even a sub-table inside a cell. Phase B lifts the
constraint.
Public API
- New sealed
TableCellContentvariants:- existing
Lines(List<TableLine>)— preserved - new
NodeContent(DocumentNode child)— accepts any composable node
- existing
DocumentTableCell.node(DocumentNode)factory mirrors the existingtext(String)andlines(...)factories.
Architecture
TableLayoutSupportgains a two-pass cell measurement when the cell holds aNodeContent: first pass measures the child against the cell's resolved inner width, second pass propagates the natural height back to the row.- Pagination: a cell with composite content stays atomic on the row, but the row's own splittable nature is preserved (a long table with composite cells still paginates row-by-row).
- Renderer: the PDF table row handler dispatches composite cells
through the standard
LayoutCompilerrecursion path so any registeredNodeDefinitionworks inside a cell automatically.
Tests
TableCellComposedContentTestcovers paragraph-in-cell, layer-stack-in-cell, and a small "sub-table" smoke test.- Layout snapshot baseline
table_cell_with_paragraph.json.
ADR
docs/adr/0006-composed-table-cell.mdrecords why composite cells do not change row-span / col-span semantics and why nested rows in cells stay atomic for pagination.
Some adopters genuinely need pixel-precise placement (badges, diagrams, custom margins). Today they reach for the retired low-level entity-assembly path. Phase C adds an explicit, controlled "I want absolute placement" opt-in that is still a canonical node.
Public API
CanvasLayerNode— atomic composite. Authors place children at explicit(x, y)coordinates relative to the canvas's bounding box. No pagination splitting (the canvas always lives on one page).CanvasLayerBuilder.position(DocumentNode child, double x, double y)plussize(width, height)setters and aclipPolicy(...)overload reusingClipPolicyfrom shape-as-container.AbstractFlowBuilder.addCanvas(width, height, Consumer<CanvasLayerBuilder>)shortcut.
Architecture
CanvasLayerDefinitionreuses the existingLayerStackNodeplacement plumbing but flips the layout from "stack box + alignment" to "explicit (x, y)".- DOCX backend logs a one-time
docx.export.canvas-layer-fallbackcapability warning and renders children inline. PDF backend honours the explicit placement via the existingPlacedFragmentpipeline.
ADR
docs/adr/0007-controlled-absolute-placement.mdrecords whyCanvasLayerNodeis separate fromLayerStackNodeandShapeContainerNode, and why "absolute placement" is rejected as a global policy onRowBuilder/SectionBuilderwhile accepted inside an explicit canvas node.
PptxSemanticBackend ships as a manifest skeleton today. Phase D
builds it out into a working semantic exporter against Apache POI.
Scope
- map paragraphs → PowerPoint text boxes
- map tables → PowerPoint tables
- map sections → slides (each top-level section becomes one slide; sub-sections become text frames)
- pass through
DocumentMetadatato PPTX core properties - limited fidelity: clip-path, transform, and shape-as-container fall back inline with capability warnings (same pattern as DOCX)
Tests
- round-trip POI loadability: every produced PPTX must reload without error
- semantic invariants: paragraph text survives, table cell text survives, slide count matches top-level section count
JitPack stays as a fallback, but Maven Central is the standard for Java open-source libraries.
Scope
- Sonatype OSSRH account
- GPG signing of release artifacts
maven-deploy-pluginconfiguration inpom.xml- README install snippets switch from
com.github.demchaav:GraphCompose:v1.6.0toio.github.demchaav:graphcompose:1.6.0as the primary form (JitPack stays documented as a fallback) - automated deployment via GitHub Actions on tag push
GraphCompose already ships a substantial benchmark suite in test
scope (CurrentSpeedBenchmark, ComparativeBenchmark,
ScalabilityBenchmark, FullCvBenchmark, GraphComposeBenchmark,
plus BenchmarkDiffTool / BenchmarkMedianTool /
BenchmarkReportWriter and a 452-line PowerShell runner). It
produces stage breakdown (Compose / Layout / Render / Total),
throughput, comparative numbers vs iText 5 / JasperReports /
OpenHTMLToPDF, stress results, and JSON/CSV reports — already
surfaced in the README Performance section and the v1.5 CHANGELOG
baseline. Phase F lifts that infrastructure to industry-standard
tooling without changing the published numbers' meaning.
Scope
- JMH migration. Replace the custom warmup / measurement harness
with
org.openjdk.jmh:jmh-core(@Benchmark,@Warmup,@Measurement,@Fork,@State). Same scenarios, same stage breakdown intent — but JIT-aware blackhole consumption, dead-code elimination protection, and proper statistical output (raw samples, percentiles, variance) come for free. - Separate
benchmarks/Maven module. Mirrors the existingexamples/module pattern. Pull benchmark code out of test scope into a self-contained module that depends on the publishedgraphcomposeartifact. Build a self-executing JMH jar viamaven-shade-plugin; run viajava -jar benchmarks/target/benchmarks.jarwith optional-rf json -rff results.jsonfor CI-friendly output. - Standalone
layoutGraph()-only benchmark. Today's stage breakdown derives Layout time from sub-totals inside the full pipeline. Add an explicitlayoutGraph()scenario so the README can publish a true "Layout vs Render" table backed by independently measured values, not breakdown subtractions. - CI integration.
.github/workflows/ci.yml"Performance Smoke Check" job switches fromscripts/run-benchmarks.ps1to the JMH jar smoke profile. Same smoke gate threshold semantics; numbers may shift slightly because JMH's statistical method differs from the custom harness — the existing tolerance accommodates that.
Compatibility
scripts/run-benchmarks.ps1stays as a thin wrapper around the new JMH jar so the documented one-command workflow keeps working.- Existing
target/benchmarks/current-speed/run-*.jsonbaseline files keep their schema;BenchmarkDiffToolcontinues to consume them. - README Performance section keeps the same structure. Numbers may shift on the order of milliseconds because JMH measures differently, but the smoke gate already covers that variance band.
Out of scope for Phase F
- No new comparison libraries (iText / JasperReports / OpenHTMLToPDF coverage stays as is).
- No microbenchmarks at engine-internal level (layout primitives, pagination math) — those stay test scope.
- No JMH-specific perf gating beyond the existing smoke threshold.
- No revival of the retired legacy PDF entry point or public
EntityManager. Application code stays onGraphCompose.document(...)→DocumentSession→DocumentDsl. - No nested rows or nested tables inside
RowBuilder. Rows stay atomic from the paginator's perspective; nesting would force a per-row pagination contract that breaks the deterministic split behaviour. - No DOCX path-clipping or transform support. Apache POI cannot express graphics-state matrices or path clipping. Authors who need rotated or clipped output must export to PDF.
- No deprecation of v1.4 / v1.5 public records. Every record that grew a field in v1.5 keeps its back-compat constructor; v1.6 additions follow the same pattern.
- No new template families beyond the existing CV / cover letter / invoice / proposal / weekly schedule set. New templates can be added, but they ship as user-side opt-ins; we are not seeding more built-ins.
Phases A and B are independent and can land in either order; they both extend public records, so each closes one back-compat-ctor discipline cycle.
Phase C depends on familiarity with the layer-stack /
shape-container pipeline. It is best done after at least one of
A or B is in develop so the contributor confidence with
NodeDefinition extension is calibrated.
Phase D depends on Phase E only if we want the v1.6 release to ship PPTX through Maven Central in one motion. Otherwise the two are independent.
Phase E should be the last commit on develop before the v1.6 tag
because it touches pom.xml distribution metadata and
README install snippets.
Phase F is independent of every other phase — it touches benchmark tooling and CI workflow only. It can land any time after v1.5 ships and before v1.6 tag, or be deferred to v1.7 if Phase A / B consume the available time.
Every phase ships only when:
- the full canonical test suite passes (
./mvnw -B -ntp -pl . test) - the architecture-and-documentation guards pass
(
DocumentationCoverageTest,DocumentationExamplesTest,CanonicalSurfaceGuardTest,PublicApiNoEngineLeakTest,SemanticLayerNoPdfBoxDependencyTest) - the
CurrentSpeedBenchmarksmoke profile passes the perf gate with no scenario regressing more than 5% against the v1.5 baseline recorded inCHANGELOG.md - the matching
docs/recipes/*.mdand a runnable example underexamples/src/main/java/com/demcha/examples/exist - the matching ADR is committed to
docs/adr/ docs/canonical-legacy-parity.mdis updated to reflect the new feature's parity status (Partial → Done where applicable)
These items are not committed to v1.6 but stay on the long-tail roadmap:
- absolute placement on
RowBuilder/SectionBuilder— rejected at policy level, may revisit if Phase C reveals demand - nested rows or nested tables inside
RowBuilder— rejected, see non-goals - DOCX path-clipping / transform — fundamentally not closeable without a different Word backend
- deprecation of v1.x public records — earliest v2.0
- Maven coordinates
io.github.demchaav:graphcompose:1.6.0 - JitPack coordinates
com.github.demchaav:GraphCompose:v1.6.0 - Tag
v1.6.0onmainafter develop merge - Migration guide at
docs/migration-v1-5-to-v1-6.md
- docs/canonical-legacy-parity.md — feature parity matrix
- docs/template-authoring.md — current authoring cheatsheet
- docs/release-process.md — release checklist
- CHANGELOG.md —
## v1.6.0 — Plannedsection mirrors this roadmap's committed scope