Skip to content

Latest commit

 

History

History
339 lines (267 loc) · 14 KB

File metadata and controls

339 lines (267 loc) · 14 KB

GraphCompose v1.6 Roadmap — "expressive"

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.

Theme

"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 legend

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.

Committed scope (must ship in v1.6)

Phase A — Nested list ergonomics

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 ListItem value type carrying (label, marker, children).
  • ListNode record signature extended to carry List<ListItem> plus the existing top-level fields. Back-compat constructor preserves every v1.4 / v1.5 caller.

Architecture

  • ListNodeDefinition (in BuiltInNodeDefinitions) walks the nested tree at prepare time. Marker resolution honours per-level ListMarker defaults (bulleted depth 0 → ◦ depth 1 → · depth 2, numbered depth 0 → 1. depth 1 → 1.1 depth 2). Authors override per-level via ListBuilder.markerFor(int depth, ListMarker).
  • Pagination policy stays "list is splittable on item boundary"; individual items remain atomic.

Tests

  • ListBuilderNestedTest covers 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.md records the ListNode-extension-vs-new-NestedListNode decision.

Phase B — Composed table cell content

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 TableCellContent variants:
    • existing Lines(List<TableLine>) — preserved
    • new NodeContent(DocumentNode child) — accepts any composable node
  • DocumentTableCell.node(DocumentNode) factory mirrors the existing text(String) and lines(...) factories.

Architecture

  • TableLayoutSupport gains a two-pass cell measurement when the cell holds a NodeContent: 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 LayoutCompiler recursion path so any registered NodeDefinition works inside a cell automatically.

Tests

  • TableCellComposedContentTest covers 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.md records why composite cells do not change row-span / col-span semantics and why nested rows in cells stay atomic for pagination.

Stretch goals (ship in v1.6 if time allows; otherwise v1.7)

Phase C — Controlled free-canvas (CanvasLayerNode)

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) plus size(width, height) setters and a clipPolicy(...) overload reusing ClipPolicy from shape-as-container.
  • AbstractFlowBuilder.addCanvas(width, height, Consumer<CanvasLayerBuilder>) shortcut.

Architecture

  • CanvasLayerDefinition reuses the existing LayerStackNode placement plumbing but flips the layout from "stack box + alignment" to "explicit (x, y)".
  • DOCX backend logs a one-time docx.export.canvas-layer-fallback capability warning and renders children inline. PDF backend honours the explicit placement via the existing PlacedFragment pipeline.

ADR

  • docs/adr/0007-controlled-absolute-placement.md records why CanvasLayerNode is separate from LayerStackNode and ShapeContainerNode, and why "absolute placement" is rejected as a global policy on RowBuilder / SectionBuilder while accepted inside an explicit canvas node.

Phase D — Real PPTX semantic export

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 DocumentMetadata to 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

Phase E — Maven Central distribution

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-plugin configuration in pom.xml
  • README install snippets switch from com.github.demchaav:GraphCompose:v1.6.0 to io.github.demchaav:graphcompose:1.6.0 as the primary form (JitPack stays documented as a fallback)
  • automated deployment via GitHub Actions on tag push

Phase F — Benchmark infrastructure modernisation

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 existing examples/ module pattern. Pull benchmark code out of test scope into a self-contained module that depends on the published graphcompose artifact. Build a self-executing JMH jar via maven-shade-plugin; run via java -jar benchmarks/target/benchmarks.jar with optional -rf json -rff results.json for CI-friendly output.
  • Standalone layoutGraph()-only benchmark. Today's stage breakdown derives Layout time from sub-totals inside the full pipeline. Add an explicit layoutGraph() 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 from scripts/run-benchmarks.ps1 to 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.ps1 stays as a thin wrapper around the new JMH jar so the documented one-command workflow keeps working.
  • Existing target/benchmarks/current-speed/run-*.json baseline files keep their schema; BenchmarkDiffTool continues 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.

Non-goals (intentionally out of scope for v1.6)

  • No revival of the retired legacy PDF entry point or public EntityManager. Application code stays on GraphCompose.document(...)DocumentSessionDocumentDsl.
  • 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.

Phase ordering

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.

Verification gates per phase

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 CurrentSpeedBenchmark smoke profile passes the perf gate with no scenario regressing more than 5% against the v1.5 baseline recorded in CHANGELOG.md
  • the matching docs/recipes/*.md and a runnable example under examples/src/main/java/com/demcha/examples/ exist
  • the matching ADR is committed to docs/adr/
  • docs/canonical-legacy-parity.md is updated to reflect the new feature's parity status (Partial → Done where applicable)

Out-of-scope but tracked

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

Release identity

  • Maven coordinates io.github.demchaav:graphcompose:1.6.0
  • JitPack coordinates com.github.demchaav:GraphCompose:v1.6.0
  • Tag v1.6.0 on main after develop merge
  • Migration guide at docs/migration-v1-5-to-v1-6.md

References