Skip to content

Releases: DemchaAV/GraphCompose

GraphCompose v1.6.9

03 Jun 11:24

Choose a tag to compare

v1.6.9 — 2026-06-03

Housekeeping cycle plus the public pixel-level visual-regression API (Track N).

Public API

  • Promoted the pixel-level visual-regression harness to public API.
    com.demcha.compose.testing.visual.PdfVisualRegression and
    com.demcha.compose.testing.visual.ImageDiff (@since 1.6.9) move from the
    test source set into src/main/java, alongside the existing
    com.demcha.compose.testing.layout.* semantic snapshot helpers. Library
    consumers can now run the same render-PDF → diff-PNG baseline gate against
    their own presets and templates instead of copying the harness. Behaviour is
    unchanged; the PDF→image step is inlined on PDFBox's PDFRenderer.
  • Exposed PdfVisualRegression.APPROVE_PROPERTY (@since 1.6.9) — the
    graphcompose.visual.approve system-property name — so consumers can toggle
    baseline-approve mode without hard-coding the string (mirrors
    LayoutSnapshotAssertions.UPDATE_PROPERTY).

Documentation

  • Added docs/operations/visual-regression-testing.md:
    pixel-vs-semantic guidance, the PdfVisualRegression API, approve mode,
    baseline layout, and cross-platform tolerance calibration.
  • README "Which API should I use?" gains a pixel-level visual-regression row.
  • Made the entire com.demcha.compose.document.* public API Javadoc
    doclint-clean.
    Added the missing @param / @return / @throws tags and
    element descriptions across 142 files so mvn javadoc:javadoc
    (doclint=all) runs warning-free. Java's default -Xmaxwarns=100 cap had
    masked ~90% of the gaps (true count: 929 warnings, not the ~100 first
    visible). Additive Javadoc only — no behaviour change; the only code
    additions are 16 behaviour-neutral no-arg constructors in
    layout/definitions/* (documenting the otherwise-synthesised public default
    constructor) and removal of the @deprecated block-tags doclint forbids in
    package-info.java (the @Deprecated annotation + prose body already carry
    the notice).

Build

  • CI Javadoc validation (maven-javadoc-plugin, doclint=all) now covers the
    public com.demcha.compose.testing.* helpers (testing.layout + testing.visual)
    in addition to the canonical document API, so Javadoc regressions on the
    testing surface fail fast in CI. No artifact or behaviour change.
  • Bumped central-publishing-maven-plugin 0.9.0 → 0.10.0 (the Maven Central
    publishing plugin) and removed the Dependabot block on 0.10.0; the
    release-profile build is verified locally and the Central upload is exercised
    at the next publish.

GraphCompose v1.6.8

01 Jun 21:58

Choose a tag to compare

v1.6.8 — 2026-06-01

CV v2 migration completion + design-token expansion. v1.6.8
finishes the CV v2 migration with hyperlink-aware project / entry
titles: a row authored as "[GraphCompose](https://github.com/x/y) (Java, PDFBox)" now renders the title as a clickable link in the
final PDF, with the technology stack remaining a plain
(Java, PDFBox) tail. The mechanism is a small extension to
the inline-Markdown parser used by every CV / cover-letter body
row — the [label](url) syntax produces a RichText.link(...)
run; bare brackets stay literal; everything else (**bold**,
*italic*, _italic_) keeps working as before. The release also
ships four contemporary BusinessTheme factory presets
(nordic(), editorial(), cinematic(), monochrome())
alongside the classic / modern / executive trio, expanding the
built-in design-token range to seven presets. Senior-review
follow-ups from v1.6.7 round out the release: the two registry
mutation entry points on DocumentSession are now fully
interchangeable (both refuse to mutate a closed session and both
invalidate the layout cache), target-branch: develop is pinned
in Dependabot config so future bumps land on the integration
branch, and logback-classic rolls forward to 1.5.34 which
fixes CVE-2026-9828
(deserialisation whitelist bypass).

Zero breaking public API changes. The japicmp gate against
the v1.6.7 baseline reports semver PATCH, compatible bug fix
across every PR in the cycle. New BusinessTheme factories are
pure additions; MarkdownInline.append and plainText extend
their behaviour without changing their signatures; ProjectLabel. parse keeps its two-field record shape (the title() field now
preserves Markdown rather than returning a pre-flattened
projection, but the type contract is unchanged and the visible
text projection is one call away via MarkdownInline.plainText( title)). 1058 tests pass at the release-prep tip.

Migration from v1.6.7. No code changes required for typical
usage. If you build a custom renderer on top of
ProjectLabel.parse:

  • Old title() was already the visible plain text (emphasis +
    link syntax stripped). New title() preserves the original
    inline-Markdown. Wrap with MarkdownInline.plainText(...) to
    recover the old behaviour, or route through
    MarkdownInline.append(rich, title, style) to get
    emphasis / link rendering for free (the same path
    ProjectRenderer now uses).
  • MarkdownInline.append consumers automatically pick up link
    rendering for [label](url) syntax. If any CV / cover-letter
    fixture in your codebase contained a literal [...](...)
    string that previously rendered as text, it will now render
    as a hyperlink. Escape with HTML entities or restructure the
    string if you need to keep it literal.

The next release is v1.7.0 — the additive canonical-DSL
feature minor (LineBuilder.dashed, inline shapes, TimelineBuilder,
dx shortcuts, recipes docs). See ROADMAP.md.

Fixes

  • The two DocumentSession registration entry points are now
    fully interchangeable, not just cache-equivalent.
    session.registry().register(...) now calls ensureOpen()
    before mutating, matching the behaviour of
    session.registerNodeDefinition(...). Previously
    registry().register(...) on a closed session silently mutated
    the registry and invalidated a closed-session cache (harmless
    but semantically odd). After this change both paths throw
    IllegalStateException on a closed session. (Track J2 — carry-
    over polish from the v1.6.7 senior review.)

Internal

  • NodeRegistry Javadoc updated to call out the v1.6.7 non-final
    relaxation explicitly (Track J4). The class became non-final
    in v1.6.7 (Track I3) so DocumentSession could install the
    auto-invalidating subclass; the change was already binary-
    compatible (japicmp classified it as semver PATCH). The
    Javadoc just makes the rationale discoverable without reading
    the CHANGELOG.

Public API

  • MarkdownInline.append(...) (the inline-markdown adapter used by
    every CV / cover-letter body / row / entry renderer) now
    recognises standard Markdown link syntax [label](url) and emits
    a clickable hyperlink run via RichText.link(label, url). Pure
    parser extension — no CvRow data-shape change required.
    MarkdownInline.plainText(...) is updated in lockstep to strip
    link syntax cleanly so callers that pull a plain-text projection
    (e.g. ProjectLabel.parse) keep getting just the visible label.
  • ProjectRenderer.inline(...) and ProjectRenderer.titleThenBody(...)
    now route the project-row title segment through
    MarkdownInline.append(...) instead of emitting it as a flat
    RichText.style(...) run. End-to-end consequence: a CV row with
    label = "[GraphCompose](https://gc) (Java, PDFBox)" renders the
    title as a clickable hyperlink and the stack as plain
    " (Java, PDFBox)". Labels without inline Markdown render
    identically to before. ProjectRenderer.plainInline(...) (the
    one-line listing variant) intentionally continues to drop link
    syntax via MarkdownInline.plainText(...) because a clickable
    link would not survive the compact formatting context.
  • ProjectLabel.parse(...) now preserves inline Markdown syntax
    inside the returned title (the legacy implementation eagerly
    flattened **emphasis** and [links](url) via plainText and
    then split on the last (). The split heuristic now targets a
    trailing \s+\([^()]*\)\s*$ pattern so a leading
    [name](https://...) URL's (...) segment is not mistaken for
    the technology-stack delimiter. Callers that only need the
    visible-text projection should pass title() back through
    MarkdownInline.plainText(...).
  • Four new BusinessTheme factory presets @since 1.6.8:
    BusinessTheme.nordic() (Scandinavian minimal — cool whites +
    slate-blue accent + generous whitespace, for design-studio
    reports and product launch decks),
    BusinessTheme.editorial() (warm cream surface + deep ink +
    brick-red accent on a serif body, for long-form proposals and
    annual reports),
    BusinessTheme.cinematic() (inverted dark navy surface with
    light text + bright copper accent, for investor pitch decks and
    product launch one-pagers), and
    BusinessTheme.monochrome() (pure black-on-white with a single
    bold yellow accent, for brutalist editorial layouts where
    typographic contrast carries the identity). Pure additions —
    no change to the existing classic() / modern() /
    executive() presets. japicmp gate against v1.6.7 reports
    semver PATCH (compatible additions only).

Build

  • Bumped jackson-bom 2.21.3 → 2.21.4 (broken 2.22.0 skipped via
    the .github/dependabot.yml ignore entry added in v1.6.7),
    logback-classic 1.5.32 → 1.5.34 (fixes
    CVE-2026-9828
    deserialization whitelist bypass in HardenedModelInputStream),
    central-publishing-maven-plugin 0.7.0 → 0.9.0 (0.10.0
    blocked by the existing ignore entry; revisit after a focused
    release-profile evaluation), japicmp-maven-plugin 0.23.1 →
    0.26.1, and a handful of maven-*-plugin minor/patch bumps
    (clean / site / resources / enforcer 3.5.0 → 3.6.3 / surefire
    3.5.5 → 3.5.6 / source 3.3.1 → 3.4.0 / gpg 3.2.7 →
    3.2.8) (#115,
    cherry-picked from main to align develop).

CI

  • .github/dependabot.yml now pins both ecosystems
    (maven, github-actions) to target-branch: develop so future
    grouped PRs land on the integration branch instead of main.
    Closes the divergence root cause behind the v1.6.7-era #111 /
    #115 episodes where every Dependabot PR force-split history
    between branches and required a cherry-pick to align.

Documentation

  • New quickstart guide
    Testing your document
    end-to-end recipe (author the document → add a layout
    snapshot test → bless the baseline → CI guards the
    shape on every PR), with a "when to use which layer" table for
    the three protection tiers (smoke / layout snapshot / pixel-level
    visual). Complements the existing
    layout-snapshot-testing.md
    reference: that one is reference-style, the new one is
    tutorial-style. README's "What can I do with this?" table row
    now links to both.

Web

  • New Next.js showcase site under site/ is now the official
    GitHub Pages deploy target for v1.6.8 onwards. Fully static
    one-page marketing / playground built with Next.js 14 App
    Router + TypeScript + Tailwind. next build emits ./out (4
    static pages, 99.7 kB first-load JS) and the new
    .github/workflows/deploy-site.yml
    uploads it to Pages on every push to main that touches
    site/**. Repo Settings → Pages source must be flipped to
    "GitHub Actions"
    for the workflow to take over from the
    legacy branch-based deploy of docs/index.html; both files
    coexist in the tree for one more cycle as a rollback.
  • Live code snippets in the Hero / Playground sections mirror
    the canonical README hello-world, examples/.../InvoiceFileExample,
    and ModernProfessional.create() paths, so a visitor copying
    any snippet into a fresh Maven project pulled at
    io.github.demchaav:graph-compose:1.6.8 gets compiling code.
    Gallery enumerates the full 16-preset cv/v2 lineup (15
    paired cover letters; MinimalUnderlined ships without a
    paired letter by design).
  • scripts/cut-release.ps1 learns a new Update-SiteDepsVersion
    step so the Maven / Gradle install snippets in
    site/lib/deps.ts flip in lockstep with the README + pom
    versions at cut time — no more silent drift between the site
    and the real released coordinates. The same release commit
    now also stages `site...
Read more

GraphCompose v1.6.7

01 Jun 13:36

Choose a tag to compare

v1.6.7 — Dependency cleanup release

TL;DR — Zero breaking changes. Lighter runtime classpath
(Kotlin stdlib gone, flexmark narrowed to the 3 modules actually
used, jackson-yaml marked optional). Plus one latent bug fix in
DocumentSession.registry().register(...) cache invalidation.

Install

Maven Central (recommended):

<dependency>
    <groupId>io.github.demchaav</groupId>
    <artifactId>graph-compose</artifactId>
    <version>1.6.7</version>
</dependency>

Gradle:

implementation("io.github.demchaav:graph-compose:1.6.7")

Highlights

  • 🪶 Lighter runtime — Kotlin stdlib removed (the library is
    Java-first; no production .kt sources existed), flexmark-all
    aggregator replaced by flexmark + flexmark-util-ast +
    flexmark-util-data (the three modules MarkDownParser actually
    uses), jackson-dataformat-yaml marked <optional>true</optional>
    (mirrors the existing poi-ooxml pattern), unused
    jackson-module-jsonSchema and the direct snakeyaml declaration
    dropped.
  • 🐛 Layout-cache fixsession.registry().register(...) now
    invalidates the layout cache the same way
    session.registerNodeDefinition(...) does. Previously, registering
    a definition through registry() silently left a stale compiled
    layout in the cache.
  • japicmp verdictsemver PATCH, compatible bug fix vs
    the v1.6.6 baseline. The one surface delta is NodeRegistry
    becoming non-final so the session can install an
    auto-invalidating subclass.

Migration from v1.6.6

No code changes required for typical usage. Pure-PDF consumers
and JSON-only ConfigLoader callers carry on unchanged.

If you previously got these dependencies transitively through
GraphCompose, declare them explicitly:

If you transitively depended on… Add to your build
Kotlin stdlib org.jetbrains.kotlin:kotlin-stdlib-jdk8
Flexmark extensions (tables, footnotes, gfm-strikethrough, …) the relevant com.vladsch.flexmark:flexmark-ext-* modules
YAML config loading through ConfigLoader com.fasterxml.jackson.dataformat:jackson-dataformat-yaml
jackson-module-jsonSchema com.fasterxml.jackson.module:jackson-module-jsonSchema
The commons-logging API beyond SLF4J routing commons-logging:commons-logging (we exclude it from PDFBox and bridge via jcl-over-slf4j)

Next

v1.6.8 is in develop — CV v2 migration completion (inline
markdown-link parser for project/education titles) plus polish
follow-ups from the v1.6.7 senior review. v1.7.0 is the
canonical-DSL feature minor (LineBuilder.dashed, inline shapes,
TimelineBuilder, dx shortcuts, recipes docs).

Resources

📦 Maven Central
 ·  📚 Javadocs
 ·  📋 Full CHANGELOG
 ·  🗺️ Roadmap
 ·  📐 API stability policy


Detailed release notes

v1.6.7 — 2026-06-01

Transitive dependency cleanup. v1.6.7 narrows the runtime
classpath GraphCompose imposes on consumers. The Kotlin standard
library is gone (the codebase is Java-first; no production
.kt sources exist), the flexmark-all aggregator is replaced
with the three modules MarkDownParser actually references,
jackson-dataformat-yaml is marked <optional>true</optional>
(mirroring the existing poi-ooxml pattern — only consumers that
load YAML configs through ConfigLoader need to pull it in),
jackson-module-jsonSchema and the explicit snakeyaml
declaration are dropped as unused, and jcl-over-slf4j is added
explicitly so PDFBox's commons-logging call sites keep routing
through SLF4J after the flexmark narrowing (the bridge was
previously provided transitively via flexmark-all). The cycle
also fixes a latent layout-cache staleness bug on
DocumentSession.registry().register(...) (Track I3): the
registry returned by registry() is now a session-owned wrapper
that invalidates the layout cache on every mutation, matching the
semantics of DocumentSession.registerNodeDefinition(...).

Zero breaking public API changes. The japicmp gate against
the v1.6.6 baseline reports semver PATCH, compatible bug fix
the one surface delta is NodeRegistry becoming non-final so
DocumentSession can install the auto-invalidating subclass
described above. All existing call sites compile and run
unchanged. The transitive cleanup is a runtime-classpath change,
not a compile-surface change.

Migration from v1.6.6. Consumers that relied on dependencies
flowing transitively through GraphCompose must now declare them
explicitly:

If you transitively depended on… Add to your build
Kotlin stdlib via GraphCompose org.jetbrains.kotlin:kotlin-stdlib-jdk8
Flexmark extensions (tables, footnotes, gfm-strikethrough, …) the relevant com.vladsch.flexmark:flexmark-ext-* modules
YAML config loading through ConfigLoader com.fasterxml.jackson.dataformat:jackson-dataformat-yaml
jackson-module-jsonSchema com.fasterxml.jackson.module:jackson-module-jsonSchema
The commons-logging API beyond SLF4J routing declare commons-logging:commons-logging explicitly (GraphCompose intentionally excludes it from PDFBox and bridges via jcl-over-slf4j)

No code changes are required for typical usage — pure-PDF
consumers and JSON-only ConfigLoader callers carry on as before.
The next minor with new canonical DSL primitives is v1.7.0
(see ROADMAP.md).

Build

  • Dropped the kotlin-stdlib-jdk8 compile dependency, the
    kotlin-test test dependency, and the kotlin-maven-plugin
    build extension. GraphCompose is Java-first; no production
    Kotlin sources exist, and the runtime now no longer carries
    the Kotlin standard library transitively. Consumers that
    relied on kotlin-stdlib flowing through GraphCompose must
    declare it explicitly.
  • Replaced the flexmark-all aggregator dependency with the three
    modules actually referenced by MarkDownParser: flexmark
    (core parser + AST), flexmark-util-ast (Node / NodeVisitor /
    VisitHandler), and flexmark-util-data (MutableDataSet). No
    extension modules (tables, footnotes, gfm-strikethrough, etc.)
    are used by GraphCompose. Consumers that relied on extensions
    flowing through GraphCompose must depend on the relevant
    flexmark-ext-* modules explicitly.
  • Added jcl-over-slf4j as an explicit compile dependency. PDFBox
    3.0.7's PDDocument.<clinit> calls org.apache.commons.logging. LogFactory directly; we exclude PDFBox's own commons-logging
    artifact to keep one logging facade, and the bridge routes those
    calls through SLF4J. Previously the bridge was provided
    transitively via flexmark-all; making it explicit keeps the
    classpath reproducible after the flexmark narrowing above.
  • Marked jackson-dataformat-yaml as <optional>true</optional>,
    mirroring the existing poi-ooxml pattern. The only consumer is
    ConfigLoader.loadConfigWithEnv(...) when the caller passes a
    .yaml / .yml resource; library consumers that load JSON
    configs (or skip ConfigLoader altogether) no longer pull in the
    ~1.7 MB SnakeYAML transitive footprint. Applications that load
    YAML configs through this helper must now declare
    jackson-dataformat-yaml in their own build.
  • Removed the unused jackson-module-jsonSchema dependency — no
    code path references it.
  • Removed the explicit snakeyaml dependency declaration and the
    snakeyaml.version property. SnakeYAML is now resolved
    transitively (and optional) through jackson-dataformat-yaml,
    which version-aligns it with Jackson's BOM.
  • Bumped net.sf.jasperreports:jasperreports 6.21.3 → 7.0.7
    in the benchmarks module. Benchmarks are a sibling Maven module
    consumed only by the manual performance harness — no impact on
    library consumers (#111).

Documentation

  • ConfigLoader.loadConfigWithEnv Javadoc now states the YAML
    path requires jackson-dataformat-yaml on the classpath and
    throws NoClassDefFoundError when the optional dep is absent.
  • DocumentSession.registry() Javadoc now explains that the
    returned registry is a session-owned wrapper whose
    register(...) mutates the registry and invalidates the
    layout cache, making the two registration entry points
    (session.registry().register(...) and
    session.registerNodeDefinition(...)) interchangeable.

Fixes

  • DocumentSession.registry().register(...) now invalidates the
    layout cache the same way
    DocumentSession.registerNodeDefinition(...) does. Previously,
    registering a node definition through registry() mutated the
    registry in place but left the cached LayoutGraph pinned to
    the previous compile, so a follow-up call to render(...) or
    layoutGraph() silently returned the stale graph routed through
    the old definition. Implemented by wrapping the session's
    NodeRegistry in a private session-owned subclass that funnels
    every register(...) call through invalidate(). (Track I3.)

Internal

  • NodeRegistry is no longer final so DocumentSession can
    install a session-owned subclass that auto-invalidates the
    layout cache on mutation (see Fixes above). Standalone
    NodeRegistry instances retain their previous behaviour.
  • Replaced eight residual org.jetbrains.annotations.NotNull /
    @Nullable usages with lombok.NonNull (where the surrounding
    file already used Lombok) or removed them entirely (private
    methods and test fixtures). org.jetbrains:annotations is no
    longer on the runtime classpath after the Kotlin...
Read more

GraphCompose v1.6.6

31 May 21:37

Choose a tag to compare

v1.6.6 — 2026-05-31

First Maven Central release. GraphCompose now ships under
io.github.demchaav:graph-compose:1.6.6 — note the hyphenated
artifactId, chosen for readability ahead of the Central debut. The
release adds publishable sources/javadoc jars, GPG-signed artefacts,
a binary-compatibility gate against v1.6.5, the metadata Maven
Central requires, and a substantial documentation polish for the
maturity / stability / migration story.

Zero breaking changes from v1.6.5. Existing JitPack callers continue
to resolve through the same coordinates (com.github.DemchaAV:GraphCompose:v1.6.5);
existing API surface compiles and runs unchanged (validated by the new
japicmp gate against the v1.6.5 baseline). New: the @Beta
annotation marker, the @since 1.0.0 class-level Javadoc on
entry-point packages, and a curated docs pass (decision guide for
the two template surfaces, examples maturity index, explicit API
stability policy).

Migration from v1.6.5: no code changes required. Swap the
JitPack <dependency> for the Maven Central form
(io.github.demchaav:graph-compose:1.6.6). The legacy JitPack URL
keeps resolving for callers pinned to v1.6.5 and earlier.

Build

  • Binary-compatibility gate against v1.6.5 (japicmp profile,
    Track E1). The new binary-compat CI job builds the artifact on every
    pull request and diffs it against com.github.DemchaAV:GraphCompose:v1.6.5
    pulled from JitPack. Binary-incompatible modifications to the public
    surface fail the build; source-incompatible changes are reported only
    (phased policy, will tighten after the 1.6.6 cut). Run locally with
    ./mvnw -DskipTests -P japicmp verify -pl .; HTML/MD/XML reports
    land in target/japicmp/. JitPack repository is scoped to the
    japicmp profile, so downstream consumers do not inherit it.
  • Maven Central publish workflow (Track D4). New
    .github/workflows/publish.yml fires
    on the same v* tag push that triggers the existing
    release.yml. It re-runs mvnw verify at the tagged commit, imports
    the GPG key (Track D2) into the runner keyring, writes the
    <server id="central"> credentials block into ~/.m2/settings.xml
    via actions/setup-java@v5, then invokes
    ./mvnw -P release -Dgpg.skip=false deploy — the
    central-publishing-maven-plugin (Track D3) uploads to Central and
    blocks until Sonatype's validator responds. Hyphenated tags
    (-rc, -alpha, -beta, -snapshot) are explicitly skipped — those
    ship only to JitPack and the GitHub Release pre-release surface.
    A workflow_dispatch input lets the maintainer re-publish an
    existing tag without re-cutting it if Central had a transient
    validator hiccup. The workflow is dormant until four GitHub repo
    secrets are wired: MAVEN_GPG_PRIVATE_KEY, MAVEN_GPG_PASSPHRASE,
    CENTRAL_USERNAME, CENTRAL_TOKEN.
  • docs/contributing/release-process.md updated with the
    end-to-end Maven Central runbook (Track D4 docs). New § 2.C
    "One-time Maven Central setup (maintainer)" walks through GPG key
    generation, keyserver upload, Sonatype account / namespace
    verification, Central user-token generation, the four GitHub
    secrets, and the release-candidate dry-run strategy. § 2.B
    post-release checklist gains a new step 9 for the Central publish
    alongside the existing JitPack step.
  • Hosted Javadocs via javadoc.io (Track H3). README's
    distribution-status note now points callers at
    javadoc.io/doc/io.github.demchaav/graph-compose,
    which auto-mirrors any artefact published to Maven Central within
    minutes — no separate hosting infrastructure required. The note
    also pins Maven Central as the going-forward primary distribution
    starting v1.6.6 (JitPack stays available alongside for existing
    callers). The full Central install snippet ("Central as primary,
    JitPack as fallback") lands in the v1.6.6 release-prep PR after the
    first Central publish proves the pipeline end-to-end.
  • central-publishing-maven-plugin in the release profile
    (Track D3). Adds Sonatype's central-publishing-maven-plugin 0.7.0
    to the existing release profile as a packaging extension. Replaces
    the legacy nexus-staging-maven-plugin + manual staging-repository
    workflow with a single deploy call. Configuration:
    publishingServerId=central (matches the <server id="central">
    entry the publish workflow writes from CENTRAL_USERNAME /
    CENTRAL_TOKEN secrets), autoPublish=false (validation gate before
    the artefact goes live — flips to true once we're confident
    post-D4), waitUntil=validated (the build waits for Sonatype's
    validator so any rejection surfaces in the workflow run, not a
    silent stuck upload). Requires the io.github.demchaav namespace to
    be verified on central.sonatype.com (one-time human step via
    GitHub auth or DNS TXT record). The plugin loads inert until D4's
    workflow provides the credentials.
  • GPG signing in the release profile (Track D2). Adds
    maven-gpg-plugin 3.2.7 to the existing release profile, binding
    to the verify phase to sign main / sources / javadoc / pom
    artefacts — Maven Central rejects unsigned uploads. Off by
    default
    : a new property <gpg.skip>true</gpg.skip> keeps local
    mvn -P release package runs working without a configured GPG key.
    The publish workflow (Track D4) flips it explicitly with
    -Dgpg.skip=false once the MAVEN_GPG_PRIVATE_KEY and
    MAVEN_GPG_PASSPHRASE secrets are wired. gpgArguments declares
    --pinentry-mode loopback so non-interactive CI runs accept the
    passphrase from -Dgpg.passphrase / MAVEN_GPG_PASSPHRASE without
    needing a TTY for gpg-agent.
  • release Maven profile with sources + javadoc jars (Track D1).
    Activated with -P release, attaches *-sources.jar and
    *-javadoc.jar to the package phase via the standard
    maven-source-plugin (3.3.1) and maven-javadoc-plugin (3.12.0)
    configurations Maven Central requires. The Javadoc plugin runs with
    doclint=none and failOnError=false so Lombok-generated members
    and @Internal engine surface don't block a publish; warnings are
    surfaced quietly. Default mvnw verify still does not pay the
    ~30 s of extra packaging — the profile is off by default and turned
    on by cut-release.ps1 (once Track D3's central-publishing plugin
    lands) and the publish workflow (Track D4).
  • SCM block canonicalised in pom.xml (Track D1 polish). The
    Central metadata validator is strict about the <scm> block:
    <connection> now uses scm:git:https://… (HTTPS, not the legacy
    git:// transport) and <developerConnection> now uses
    scm:git:ssh://git@github.com/… (the canonical SSH URL with the
    git@ user, not the older ssh://github.com:… form). Matches the
    shape every Central artefact's POM carries.
  • New benchmarks/README.md (Track B1). Honest framing for the
    manual benchmark layer ahead of the Maven Central debut: explicitly
    positions the harness as a smoke / diff / endurance tool — not a
    JMH-grade benchmark — and tells callers when not to use it
    (publishable performance claims, architectural decisions,
    cross-library comparisons that read too much into a single number).
    Documents the file-by-file role of each runner / report tool, the
    exact CI smoke invocation, and a "How to read a report" cheat sheet.
    Cross-links the planned JMH chain (Track C, B3 → B6 in 1.7.0) so a
    reader knows what's coming and how to identify "rigorous"
    measurements when they arrive.
  • Class-level @since 1.0.0 Javadoc on the public entry-point
    surface
    (Track H1). 26 public types in the canonical user-reached
    packages (com.demcha.compose.GraphCompose, com.demcha.compose.document.api.{DocumentSession, DocumentPageSize, PageBackgroundFill},
    com.demcha.compose.document.dsl.{DocumentDsl, RichText, Transformable} plus all 19 DSL builders)
    now carry class-level @since 1.0.0 Javadoc tags so callers can see
    the introduction version at IDE quick-doc / generated Javadoc time
    without trawling CHANGELOG history. New guard test
    PublicApiSinceTagCoverageTest source-scans the three entry-point
    roots and fails the build if a new public top-level type lands
    without a class-level @since tag; internal/ sub-packages are
    excluded by convention (InternalAnnotationCoverageTest covers those).
    Method-level @since backfill for the ~380 public methods in these
    packages is intentionally out of scope here and tracked separately.
  • maven-enforcer-plugin gate (Track E2). Binds three rules to the
    validate phase so the build refuses to start when a precondition is
    broken: requireJavaVersion (≥ 17 — the declared baseline, catches
    accidental JDK 11 / 15 attempts), requireMavenVersion (≥ 3.8.0 —
    the oldest version the planned central-publishing pipeline supports),
    and requirePluginVersions (every plugin must declare an explicit
    non-LATEST / non-RELEASE / non-SNAPSHOT version — the
    generalisation of the PR-7.1 exec-plugin drift lesson).
    Default-lifecycle plugins (clean / install / site / resources /
    deploy) are now pinned in a new <pluginManagement> block so
    requirePluginVersions has nothing to flag. Minimums and versions
    live in <properties> (enforcer.requireMavenVersion,
    enforcer.requireJavaVersion, maven.enforcer.plugin.version).
  • Parallel-session stress test (Track I2). New
    DocumentSessionParallelStressTest drives 32 independent
    DocumentSession instances on a fixed-size thread pool through 4
    iterations and asserts (a) all parallel renders produce a layout-graph
    signature byte-equal to the sequential baseline — exercising the
    shared font registry, glyph cache, built-in node definitions, and
    shape-outline cache for race conditions; (b) every PDF output starts
    with the %PDF magic, is at least 256 bytes, an...
Read more

GraphCompose v1.6.5

30 May 10:09

Choose a tag to compare

v1.6.5 — 2026-05-30

Templates v2

  • Added the CenteredHeadline CV preset to the cv/v2 layered
    template surface, including its isolated theme tokens, visual
    regression baselines, and reusable Subheadline /
    SectionHeader.flatSpacedCaps widget support.
  • Added the Mint Editorial template set: a two-page, two-column
    editorial CV preset MintEditorial (centred spaced-caps masthead with
    a full-width mint accent rule; sidebar contact / interests / education /
    expertise / skill-bars / social beside a profile / experience / awards /
    references main column) and its paired MintEditorialLetter, both on
    CvTheme.mintEditorial() and with visual regression baselines.
  • Added two reusable cv/v2/widgets: SkillBar (data-driven proficiency
    bar — spaced-caps label above a track with a level-positioned marker;
    no bar when the level is absent) and IconTextRow (inline icon + text
    row, optionally a single click target), with WidgetSmokeTest coverage.
  • Added optional proficiency levels to SkillGroup via the new
    CvSkill record and SkillsSection.Builder.leveledGroup(...). Fully
    backward-compatible: name-only skills carry no level and every existing
    name-based renderer is unaffected.
  • Added MintEditorial.Options (and a matching MintEditorialLetter.Options)
    — an additive masthead colour API (accent, rule, name, and an optional
    full-width page-1 header band) whose defaults reproduce the stock render
    exactly, so the committed look and the parity baselines are unchanged.

Public API

  • PageBackgroundFill band helpers. Added topBand, bottomBand,
    band, topBandPoints, and bandPoints factory methods for full-width
    horizontal background bands (top, bottom, or arbitrary vertical offset;
    ratio- or point-based), complementing the existing column helpers and
    building on the v1.6.5 y-coordinate fix below.

Bug fixes

  • PageBackgroundFill y-coordinate. A partial-height page-background
    fill (heightRatio < 1.0) was painted from the page bottom upward
    instead of from the yRatio top edge the API documents, so a band with
    yRatio = 0 rendered at the bottom of the page. Fills now convert the
    top-down ratios to the PDF bottom-up origin correctly
    (y = (1 - yRatio - heightRatio) * pageHeight); full-page and
    full-height column fills are unchanged. Adds top-/bottom-/mid-band
    regression tests.
  • GraphCompose.document().pageBackgrounds(emptyList()) now actually
    clears.
    The builder's Javadoc promised that an explicit empty list
    overrides any earlier pageBackground(color) on the same builder, but
    the implementation skipped empty lists, so pageBackground(LIGHT_GRAY)
    followed by pageBackgrounds(List.of()) still emitted the grey
    background. The guard is removed; the empty list is now the documented
    clear. Adds a regression test.
  • distributeRowSlotWidths weights / children mismatch. When a row
    was constructed with a weights list whose size did not match the
    number of children (only reachable by bypassing RowBuilder and
    building a RowNode directly), the engine's row distribution code
    walked off the end of the weights list with a raw
    IndexOutOfBoundsException. Both row-distribution call sites
    (LayoutCompiler#distributeRowSlotWidths, NodeDefinitionSupport#measureRow)
    now reject the mismatch with an IllegalArgumentException whose
    message names both sizes and the expected fix. RowNode's canonical
    constructor already validated this at construction time; the new
    engine guards are defence-in-depth for any path that bypasses it
    (e.g. reflection-based deserialization). Adds regression tests for
    the canonical-constructor IAE and the RowBuilder.build() ISE.

Build

  • byte-buddy is now <scope>test</scope>. Mockito already excludes
    its transitive byte-buddy and the project pins a single version in a
    standalone dependency; that dependency was missing a scope, so the
    published POM advertised byte-buddy as a compile dependency even
    though no production code references it. Setting <scope>test</scope>
    keeps the version pin but keeps byte-buddy out of consumers' runtime
    classpath (mvn dependency:tree shows it only as :test).
  • CI exec-maven-plugin version drift removed. The CI workflow's
    three benchmark steps invoked
    org.codehaus.mojo:exec-maven-plugin:3.5.0:java directly, while
    benchmarks/pom.xml already declared exec-maven-plugin at 3.6.3
    for local runs — a silent version split between CI and local invocations
    that grew the surface area to keep aligned. CI now calls the configured
    plugin via exec:java, picking up the pinned 3.6.3 from
    benchmarks/pom.xml. No behaviour change; one fewer hardcoded version
    to bump.

v1.6.4 — Boxed Sections parser fix + structured WorkHistory/Education blocks

22 May 12:17

Choose a tag to compare

Bug fix + structured-block patch. Adds two new public Block types —
WorkHistoryBlock and EducationBlock — that let template authors
declare work-history and education entries with explicit (title,
organisation, date, description) / (degree, institution, year,
details) fields instead of relying on the legacy
MultiParagraphBlock pipe-separated string parser. Also closes a
Boxed Sections layout defect that bundled the date and description
into the right-aligned date column for any author-supplied line that
used an em-dash (" — "), en-dash (" – "), or contained
prose-shaped content the parser misread as a date. No public API
break
— the sealed Block permit list grows from six to eight,
existing MultiParagraphBlock work-history strings continue to
parse, and the deprecated parser path stays in place for backward
compatibility.

Templates — new structured blocks

  • WorkHistoryBlock. New public record block carrying a list of
    Item(title, organisation, date, description) entries. The
    BoxedSections preset renders each item as a structured row:
    title bold on the left, date right-aligned on the same row,
    organisation italic on the next line under the title, and
    description as a full-width paragraph beneath. Other presets fall
    back to a single concatenated paragraph per item. Authors who use
    WorkHistoryBlock bypass the legacy
    BoxedSections#parseWorkEntry heuristic parser entirely.
  • EducationBlock. New public record block carrying a list of
    Item(degree, institution, year, details) entries. Renders with
    the same structured layout as WorkHistoryBlock (degree bold
    left, year right, institution italic, details paragraph) so
    Education & Certifications sections visually match Professional
    Experience.
  • Sample data migrated. ExampleDataFactory.sampleCvSpecV2 now
    uses WorkHistoryBlock for Professional Experience and
    EducationBlock for Education & Certifications. The legacy
    MultiParagraphBlock pattern remains supported and is exercised
    by PresetLayoutSnapshotTest / PresetVisualParityTest to lock
    the backward-compat path.

Templates — parser robustness (legacy path)

  • parseWorkEntry accepts em-dash and en-dash. Used to split
    the post-pipe segment on ASCII " - " only; now tries " — ",
    " – ", and " - " in order, mirroring splitHeading. Authors
    who typed "*2024-Present* — Led reusable document flows." saw
    the whole tail collapse into the date column — this no longer
    happens.
  • parseWorkEntry rejects prose dressed up as a date. The
    loose looksLikeDate check accepted any string containing a
    year and a hyphen anywhere, which caused education lines like
    "... | 2019. First-class honours. Specialisation ..." to
    parse as work entries (the hyphen inside "First-class" was
    enough to satisfy the heuristic). Parser now rejects post-pipe
    segments that contain sentence-ending punctuation (., :,
    ;) when no explicit date / description separator was found,
    letting these lines fall back to plain paragraph rendering.
    Marked @Deprecated with a @deprecated Javadoc pointing
    callers to WorkHistoryBlock / EducationBlock.
  • parseProjectItem picks up the same em-dash / en-dash /
    ASCII separator set so future Project items typed with em-dash
    don't regress into "title only" rendering.

Tests

  • BlockTest.blockSealingPermitsAllEightVariants updated for the
    two new permitted block types.
  • PresetVisualGalleryTest.sampleSpec migrated to
    WorkHistoryBlock so the visible "primary example" exercises the
    new structured shape.
  • PresetLayoutSnapshotTest intentionally retained on
    MultiParagraphBlock to lock the legacy parser's behaviour.

v1.6.3 — Tight PDF link rects + preserved whitespace + Projects layout fix

22 May 08:52

Choose a tag to compare

Bug fix patch. Closes two independent hyperlink clickable-area
defects that surfaced on CV gallery presets and made the LinkedIn /
GitHub contact rows hijack each other's clicks (paragraph-level
link path) or drift past their visible text (span-level link path
through multi-space separators). No public API change — engine,
DSL, themes, templates, and backend records all stay
source-compatible with v1.6.2.

Engine

  • Paragraph-level link annotations now hug rendered text.
    PdfFixedLayoutBackend used to emit a paragraph's linkOptions
    as a single rectangle covering the entire fragment box
    (fragment.x() + fragment.width()), ignoring TextAlign.RIGHT
    / TextAlign.CENTER. Stacked right-aligned contact paragraphs
    (e.g. one per LinkedIn / GitHub icon row in Timeline Minimal /
    Sidebar Portrait / Monogram Sidebar) therefore produced
    full-column-wide rects that overlapped the empty alignment gap of
    neighbouring rows — hovering over GitHub clicked the LinkedIn row.
    The backend now emits one per-line rect tight to line.width()
    positioned at the alignment-aware lineX, matching how
    inline-span links already worked. Span-level link emission, table
    / shape / barcode payload links, and bookmark anchoring are
    unchanged.
  • Glyph sanitizer preserves all author whitespace.
    PdfFont.sanitizeForRender used to collapse any run of consecutive
    spaces into a single space, both for whitespace-only tokens (the
    " " halves of a " | " separator) and for inter-word gaps
    in spaced-caps strings (spacedUpper("ARTEM DEMCHYSHYN") produces
    "A R T E M D E M C H Y S H Y N" with deliberate triple-spaces
    between words). The collapse shrank the rendered glyph stream
    under measurement, drifting inline-link rectangles ~8pt per
    " | " separator past their visible labels and visually
    merging spaced-caps titles back into a single run ("A R T E M D E M C H Y S H Y N" — no word boundary). The sanitizer no longer
    collapses adjacent spaces; newlines / NBSP / non-tab control
    characters still resolve to a single space each, but author
    whitespace is now preserved verbatim so wrap geometry,
    link-rectangle emission, and showText(...) all see the same
    string. Layout snapshot baselines for five CV presets and one
    nested-list document widened to reflect the recovered whitespace —
    the deliberate visual change is the bug fix.

Templates

  • Boxed Sections projects render as title + indented description.
    The "Projects" module now renders each bullet-list or
    IndentedBlock item as two stacked paragraphs — bullet plus bold
    project name (with an optional tech-stack chunk in parentheses) on
    the first line, then a hanging-indented description below aligned
    to the project name (not the bullet). The previous single-line
    rendering ran the project name and description together. Bullet
    marker, hanging-indent, and surrounding modules are unchanged.
    Example data in ExampleDataFactory.sampleCvSpecV2 and
    PresetVisualGalleryTest now ships tech-stack chunks ("Java 21, PDFBox, Maven, JMH") so the gallery PDFs reflect the new layout.

Tests

  • New regression in PdfFixedLayoutBackendFeaturesTest
    shouldTightlyHugRightAlignedParagraphLinkRectangles — stacks
    three right-aligned link paragraphs and asserts each clickable
    rect hugs its rendered label width (≤ 150pt), sits flush against
    the inner right margin, and does not overlap the Y-band of
    neighbouring rows.
  • New regression in PdfFixedLayoutBackendFeaturesTest
    shouldKeepCenteredInlineLinkRectanglesAlignedAcrossMultiSpaceSeparators
    — renders a centered contact line built with " | " separators
    and asserts the three resulting link rectangles preserve
    left-to-right order with non-overlapping X ranges and a sane
    per-separator gap (5..40pt), pinning the bug where collapsed
    whitespace pushed later rects past the line.
  • New regression in PdfFontSanitizerTest
    sanitizeForRender_preservesWhitespaceOnlyTokensVerbatim — pins
    the whitespace-only short-circuit so render width stays in
    lockstep with getTextWidth for tokenised contact-line
    separators.

v1.6.2 — Sorry, the PDF was crashing on emoji

20 May 09:33

Choose a tag to compare

v1.6.2 — Sorry, the PDF was crashing on emoji

Patch release. Closes four engine bugs I found while building yet
another CV template and assuming "well, this one definitely works."
It did not.

Zero breaking changes. If you're on v1.6.1, upgrading is safe.
Honest.

What's fixed

🔡 Any Unicode glyph no longer kills the document.
Before: a single in a paragraph, watermark, or footer would
make PDFBox throw IllegalArgumentException deep inside showText
and the whole render went up in smoke. I was convinced this was a
known limitation. It was a bug. PdfFont.sanitizeForRender already
existed in the codebase, nobody was calling it. Now everybody is.
Emoji, arrows, copyright marks — substituted to ? with one WARN
per unique (font, codepoint). No crashes. No surprises.

📏 842pt no longer "too large" for A4.
DocumentPageSize.A4.height() is 841.88977pt. Exactly. Per the
spec. If you are a regular human being who writes 842 because A4
is 842pt tall (as everyone learned) — you used to get
requires outer height 842.0 but page capacity is 841.88977. A
0.11pt overflow. That's 0.04 mm. The capacity check now has a 0.5pt
tolerance — invisible to the eye, but enough to stop fainting at
round-trip rounding.

🎨 Row inside LayerStack is now legal.
Want a dark top band on your CV with a sidebar+main row underneath?
Obviously through a LayerStack (layer 0 = band, layer 1 = row of
two columns). Obviously the validator rejected this, because it
couldn't tell a parent-row-band from a parent-layer-rectangle. It
can now. Row inside a layer works. Row inside another row still
does not — that one is a real composition conflict.

🛠️ Exception messages now tell you what to do.
Before: cannot contain a nested horizontal row; use a section column instead. Sounds like a lecture from your compiler. After:
Wrap the inner row in a LayerStack layer (allowed since v1.6.2), or stack horizontal content as sections inside a vertical column.
Meaning: try this thing. Breakthrough, I know.

Tests

828+ existing tests stay green, plus:

  • PdfFontSanitizerTest — 8 sanitizer cases
  • LayerStackRowCompositionTest — 3 positive + 1 negative
  • PaginationEdgeCaseTest — 2 new boundary cases
  • Three visual demo PDFs under target/visual-tests/:
    glyph-fallback/, page-capacity/, layer-stack/
  • Bonus: DevelopTest scratch class for manual experimentation

Meta

  • Maintainer email in pom.xml is fixed. Nobody was emailing the
    old one, which was convenient. If you ever wanted to reach me —
    it now actually goes somewhere: demchishynartem@gmail.com.
  • README links to the companion experiment
    graphcompose-ai-flow
    — where I'm exploring AI-assisted document authoring on top of
    GraphCompose. Independent codebase, separate lifecycle, broken
    things welcome
    . Check it out if that sounds fun.

Install

<dependency>
    <groupId>com.github.DemchaAV</groupId>
    <artifactId>GraphCompose</artifactId>
    <version>v1.6.2</version>
</dependency>
implementation("com.github.demchaav:GraphCompose:v1.6.2")

What's next — v1.7

DX helpers on top of an engine that no longer crashes: heading
levels (h1/h2/h3), addFieldRow(label, value),
page.twoColumn(...), addHeadingBar(...), inline shape runs
(ratingDots, badge). All additive. Plan tracked privately;
follow develop.

Full diff: v1.6.1...v1.6.2

GraphCompose v1.6.1 — Java 17 baseline + dependency refresh

09 May 09:09

Choose a tag to compare

For users

GraphCompose v1.6.1 is a maintenance + compatibility patch. Drops the Java 21 source/target baseline to Java 17+ so the library can ship into older enterprise stacks without a fork, and refreshes test/build dependencies (incl. CVE pass + ByteBuddy/Mockito update so Mockito works on JDK 25).

No public API change — engine, DSL, themes, templates, and backend records all stay source-compatible with v1.6.0; existing v1.6.0 callers compile and behave unchanged.

Co-developed with external contributor @jottinger (#8, #10).

Toolchain

  • Java 17 baseline. <maven.compiler.release> flips from 21 to 17 across pom.xml, examples/pom.xml, and benchmarks/pom.xml. Engine source loses the Java 21–only constructs (switch-with-type-patterns, switch-with-deconstruction, List.getFirst(), Thread.threadId()) in favour of Java 17–compatible forms. CI runs against Temurin JDK 17 / 21 / 25 in matrix.
  • Dependency refresh + CVE pass. Bumps Jackson 2.20.1 → 2.21.3, Logback 1.5.18 → 1.5.32, Lombok 1.18.38 → 1.18.46, POI 5.4.0 → 5.5.1, SnakeYAML 2.4 → 2.6, AssertJ 3.27.3 → 3.27.6, JUnit 5.12.2 → 5.14.4, Mockito 5.20.0 → 5.23.0. Adds explicit ByteBuddy 1.18.7 so Mockito works on the Java 25+ access rules. Maven plugin bumps: maven-compiler-plugin 3.13 → 3.15, maven-surefire-plugin 3.2.5 → 3.5.5, exec-maven-plugin 3.5 → 3.6.2.

Verified

  • CI green on Temurin JDK 17 / 21 / 25 in parallel matrix.
  • 819 / 0 / 0 / 0 canonical test suite green on every JDK.
  • 26 runnable examples regenerate cleanly.
  • Engine source has zero remaining Java 21–only constructs (getFirst, getLast, threadId, switch type patterns, switch deconstruction).

Quick start

Maven:

<repositories>
    <repository><id>jitpack.io</id><url>https://jitpack.io</url></repository>
</repositories>

<dependency>
    <groupId>com.github.DemchaAV</groupId>
    <artifactId>GraphCompose</artifactId>
    <version>v1.6.1</version>
</dependency>

Gradle (Kotlin DSL):

repositories { maven("https://jitpack.io") }
dependencies { implementation("com.github.demchaav:GraphCompose:v1.6.1") }

Looking ahead

Maven Central distribution (#7) remains queued for the upcoming v1.7.0, alongside the JMH benchmark migration. v1.6.1 stays on JitPack.

Links


Author intent, not coordinates.

GraphCompose v1.6.0 — expressive release

07 May 22:08

Choose a tag to compare

For users

v1.6.0 — "expressive" — makes GraphCompose more expressive without breaking your engine code:

  • Nested lists are now easier to compose — ListBuilder.addItem(label, Consumer) opens a child scope, and a per-depth marker cascade (·) handles the visual hierarchy. Mixed flat / nested authoring preserves source order.
  • Table cells can hold any composable node — DocumentTableCell.node(...) accepts paragraphs, nested lists, sub-tables, layer stacks. Two-pass measurement preserves the row-by-row pagination contract.
  • Canvas layers support pixel-precise (x, y) placement — CanvasLayerNode is the controlled free-canvas primitive for when you do need coordinates, with ClipPolicy.CLIP_BOUNDS clipping and atomic pagination.
  • Templates v2 ships 14 CV presets and 14 paired cover-letter presets, theme-driven via BusinessTheme, with one-liner create(theme) factories. Inline markdown, active hyperlinks, slot-based multi-column layouts. The Templates v2 visual pipeline is hardened with pixel-diff parity checks against v1 reference renders to catch visual regressions on every build.
  • Architecture hardening@Internal API stability marker on engine internals, public PdfFragmentRenderHandler SPI for custom render handlers, DocumentRenderingException wrapping the convenience render path so buildPdf / writePdf / toPdfBytes no longer declare throws Exception, documented thread-safety contract.

Engine source-compatibility with v1.5 is preserved for unmodified callers. Every public record that grew a new field ships back-compat constructors that default the new value, so v1.5 callers compile and behave unchanged.

Breaking change

Templates v2 replaces the legacy CV / cover-letter template classes. Legacy classes (CvTemplateV1, NordicCleanCvTemplate, MonogramSidebarCvTemplate, …) are deleted, not deprecated. Anyone constructing those classes must switch to the matching v2 preset's create(BusinessTheme) factory. The full v1 → v2 mapping (every old class → its v2 replacement, with before/after code) is documented in docs/migration-v1-5-to-v1-6.md.

Quick start

Maven:

<repositories>
    <repository><id>jitpack.io</id><url>https://jitpack.io</url></repository>
</repositories>

<dependency>
    <groupId>com.github.DemchaAV</groupId>
    <artifactId>GraphCompose</artifactId>
    <version>v1.6.0</version>
</dependency>

Gradle (Kotlin DSL):

repositories { maven("https://jitpack.io") }
dependencies { implementation("com.github.demchaav:GraphCompose:v1.6.0") }

Distribution status — currently published via JitPack. Maven Central is planned for v1.7 (tracking issue #7).

What's verified

  • 819 / 0 / 0 / 0 — full canonical test suite green via mvnw verify.
  • 26 runnable examples regenerate cleanly via GenerateAllExamples and ship as a CI artifact on every build.
  • 28 layout-snapshot baselines + 29 pixel-diff visual baselines cover every CV / cover-letter preset. The parity gate runs on every CI build with a calibrated mismatchedPixelBudget for cross-platform PDFBox font drift.

Links

  • Live showcase — install snippet, every example with a generated PDF preview, template gallery, feature examples.
  • Templates v2 landing — preset gallery, four-layer architecture explanation, quick-start.
  • Migration v1.5 → v1.6 — v1 → v2 template upgrade table.
  • CHANGELOG.md — full technical changelog, including architecture-hardening details, package-level changes, and ADRs 0011-0014.

Author intent, not coordinates.