Skip to content

chore(release): 0.4.0 - #52

Merged
DemchaAV merged 10 commits into
mainfrom
release/0.4.0
Aug 5, 2026
Merged

chore(release): 0.4.0#52
DemchaAV merged 10 commits into
mainfrom
release/0.4.0

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Why

Two things this library renders were wrong, and both trace to the engine line it was pinned to.

A heading could be left alone at the bottom of a page while the block it introduces flowed to
the next one. GraphCompose 1.9.x had no way to express "keep this with what follows"; 2.1.0
added SectionBuilder.keepWithNext(), and 2.1.1 fixed the gap that made it miss every
panel-shaped block this library builds.

Separately, every theme has always set ShapeTokens.underlineLinks = true and ~~strike~~ has
always mapped to DocumentTextDecoration.STRIKETHROUGH — but the PDF backend resolved those
flags to font faces only, so both rendered as plain text. Nobody noticed because the request was
in the model the whole time; only the painting was missing. 2.1.0 draws them.

This release is the 0.4.0 line landing: the 1.9.1 pickup, the 2.0.0 module-split migration and
the 2.1.1 engine, plus the heading fix built on top.

This PR targets main, not develop — the template's checklist item is for feature work.
Releases are cut on main here (v0.3.03593899, v0.3.1bac6978), and the tag that
triggers release.yml / publish.yml has to sit there.

main requires linear history, and this branch carries one merge commit (ab64d05), so it
needs squash or rebase merge — the merge-commit button will be refused.

What changed

  • BuiltinRenderers.HeadingRenderer wraps its paragraph in a one-child section marked
    keepWithNext().
    The flag has to live on a section: it is a node-level pagination property
    and ParagraphNode cannot carry one. The bookmark and the anchor stay on the paragraph, so
    outline entries and #slug destinations resolve to the heading's own position — a bookmark
    moved to the wrapper would point at the top of the space-above margin instead of at the text.
  • PageTokens gains a fourth component, keepHeadingWithNext (default true), so a theme
    can restore the plain flow. The three-argument constructor is kept as an overload, so existing
    theme code — including DefaultMarkdownTheme, PackSupport and downstream consumers building
    their own PageTokens — compiles untouched and gets the fix by default.
  • Engine 1.9.0 → 2.1.1 across the line. 2.1.1 is what makes the heading rule complete: on
    2.1.0 its lookahead measured the first line of the following block, but a keepTogether()
    block has no line to break after — it relocates entire — so the heading stayed behind. That is
    every panel this library builds: code block, blockquote, GitHub alert, custom callout,
    unsupported-block panel.
  • Back-merge of main into the 0.4.0 line (ab64d05). develop branched at v0.3.0 and
    never received the v0.3.1 patch: its CHANGELOG had no v0.3.1 section, its README still
    advertised 0.3.0, and the committed manual renders were the 0.3.0 ones. Cutting 0.4.0 from
    that line would have published a changelog with a hole in it.
  • Release bookkeeping (5573a5d): version literals 0.4.0-SNAPSHOT0.4.0 in lockstep
    across the root, cli/ and examples/ poms; README status line and install snippet, and
    examples/README, move to 0.4.0; CHANGELOG [Unreleased] becomes ## v0.4.0 — 2026-08-05.
  • Committed manual renders regenerated on the new engine — manual.pdf 44538 → 44685 bytes,
    manual-dark.pdf 44669 → 44820. That growth is the underline and strikethrough marks the
    1.9.x backend was dropping.

Verification

  • ./mvnw -B -ntp clean verifyBUILD SUCCESS, 194 tests, 0 failures.
  • cd cli && ../mvnw -B -ntp verifyBUILD SUCCESS, 7 tests. examples compiles clean
    against the released 0.4.0 coordinate.
  • ./mvnw -B -ntp javadoc:javadoc → clean.
  • CI matrix (JDK 17 / 21 / 25 + detached modules + Javadoc) green on develop for every content
    commit in this PR — run 30988185762. Only 5573a5d is new to CI here.

HeadingKeepWithNextTest (new, 6 tests) pins the orphan window from both sides, which is
what stops it rotting into a test that asserts nothing:

test asserts
headingMovesDownRatherThanStrandingAtThePageBottom with the token on, heading and body share a page
withoutTheTokenTheHeadingStrandsAtThePageBottom with it off, the untreated flow does strand — so a shift in theme metrics fails loudly instead of silently skipping the case
headingWithRoomForItsBodyIsUnaffected a heading with room is placed identically either way
theBookmarkFollowsTheRelocatedHeading the outline entry resolves to the page the heading landed on
anAnchorLinkFollowsTheRelocatedHeading a [text](#slug) link resolves there too — a separate path, via a named destination rather than an outline entry
defaultThemeKeepsHeadingsWithTheirContent light and dark defaults have the token on

Honest limit on those last two: neither discriminates bookmark-on-paragraph from
bookmark-on-wrapper — verified by moving them and watching all six stay green, because a
one-child wrapper relocates whole and never separates from its paragraph. The difference shows
up only in the destination's Y (~8 pt, the heading's space-above margin), which is too brittle
to assert. The javadoc says so rather than implying a guard that is not there.

End-to-end on real documents. Rendered the five sample documents of the gp-markdown-view
viewer and counted true orphans — a heading that is the last text line on its page:

library engine orphans
0.3.1 2.1.0 1 — kitchen-sink.md p2, ## Blockquote
this branch 2.1.0 1 — the wrapping alone does not rescue a keepTogether() block
this branch 2.1.1 0

The middle row is the one worth reading: both halves are required.

Cost. No measurable render cost from the extra node — stress.md (201 headings): 250 ms →
255 ms best, 288 ms → 283 ms mean over 7 runs after warmup. Page counts are identical across all
five documents without and with the wrapping, so the extra section shifts nothing.

After merge

  1. Tag v0.4.0 on main and push → release.yml cuts the GitHub Release, publish.yml ships
    to Maven Central. That push is irreversible — a Central version cannot be withdrawn.
  2. chore: start 0.4.1-SNAPSHOT on develop.

DemchaAV and others added 10 commits July 4, 2026 03:18
Post-release: bump develop to 0.4.0-SNAPSHOT (root + cli/examples gcmd.version +
module versions in lockstep) and reopen the CHANGELOG [Unreleased] section.
Long inline-code with no spaces — package coordinates, FQCNs, URLs — overflowed
its table cell or paragraph on the 1.9.0 engine, drawing over the neighbouring
column or off the right page margin (graph-compose-markdown renders inline code
through RichText.highlight, the exact path the engine fix repairs).

Bump graphcompose.version 1.9.0 -> 1.9.1 so develop (and the eventual 0.4.0)
carries the fix; the 0.3.x line ships it as 0.3.1. No source or public API change.
GraphCompose 2.0 splits the monolithic engine into per-concern modules,
but the `io.github.demchaav:graph-compose` coordinate stays the drop-in
default — it is now a thin aggregator over `graph-compose-core` +
`graph-compose-render-pdf`. So this consumer migrates with a single-line
version bump and keeps rendering PDF with no dependency change and no
change to any public type.

What changed:
- pom.xml: graphcompose.version 1.9.1 -> 2.0.0 (the sole engine pin;
  cli/ and examples/ resolve 2.0.0 transitively, fonts/emoji keep their
  own version lines).
- InlineRendererTest: 2.0 removed the deprecated linkOptions() read
  accessor on the engine's inline runs; the test now reads the surrounding
  link through linkTarget() / ExternalLinkTarget.options().uri() (and now
  asserts the preserved URI, not just non-null). Main source is unchanged.
- CHANGELOG: [Unreleased] Build entry documenting the bump.
- README: refresh the stale "GraphCompose 1.9" sample prose to 2.0.

Verification: 188 library tests + the CLI suite pass on 2.0.0, the
examples module compiles, and QuickStartExample renders a real PDF
end-to-end (proving the render-pdf backend is present transitively — no
MissingBackendException). Version-lockstep is untouched (all 0.4.0-SNAPSHOT).
A heading rendered at a page bottom could be left there alone while the body
it introduces flowed to the next page. GraphCompose 2.1.0 adds the opt-in rule
for exactly this -- SectionBuilder.keepWithNext() -- but it is a node-level
pagination property and ParagraphNode cannot carry one, while HeadingRenderer
emits a heading as a paragraph. So the paragraph is now wrapped in a one-child
section that carries the flag. The bookmark and the anchor stay on the
paragraph, so outline entries and #slug destinations still resolve to the
heading's own position rather than the wrapper's.

PageTokens gains keepHeadingWithNext (default true) so a theme can restore the
plain flow; the three-argument constructor is kept as an overload defaulting the
flag on, so existing theme code compiles and renders identically apart from the
fix. The engine bump to 2.1.0 also makes the PDF backend draw UNDERLINE and
STRIKETHROUGH, which every theme already asked for through
ShapeTokens.underlineLinks and which ~~strike~~ already mapped to; both had been
silently rendering as plain text.

Known limitation, recorded in the CHANGELOG: a heading followed by a block this
library marks keepTogether() (blockquote, code panel, callout, alert, front
matter) is still stranded. The engine lookahead measures the following block's
first line, but a keep-together block cannot split and relocates whole. That
needs LayoutCompiler.leadingUnitHeight to treat a keep-together node that fits
on a page as its own leading unit.

Tests: HeadingKeepWithNextTest pins the orphan window from both sides -- the
heading joins its body with the token on, and the untreated flow strands it with
the token off -- plus a no-op case with room to spare and an outline check.
193 library tests and 7 CLI tests green on the 2.1.0 engine.
The limitation note listed front matter, which does not request keep-together,
and omitted the unsupported-block panel, which does. The five sites are
CodeBlockRenderer, UnsupportedBlockRenderer, QuoteRenderer, AlertRenderer and
CalloutRenderer, all unconditional.
2.1.1 fixes the engine gap this library's heading rule was hitting: the
keepWithNext lookahead measured the first line of the following block, but a
keepTogether block has no line to break after and relocates entire, so the
heading stayed behind. That is every panel-shaped block this library builds --
code block, blockquote, GitHub alert, custom callout, unsupported-block panel --
so on 2.1.0 the new rule covered paragraphs, lists and tables only.

Measured through the gp-markdown-view viewer over its five sample documents: the
single true orphan, kitchen-sink.md p2 '## Blockquote', survives the wrapping on
2.1.0 and is gone on 2.1.1. The CHANGELOG's known-limitation note is replaced by
what now holds.

193 library tests green.
Review of the keep-with-next change found two weak spots in its own test.

pageOf returned -1 when the needle was on no page, so a render that produced no
text at all would have two -1s compare equal and pass. It now fails outright.

wrappingDoesNotDisturbTheOutline asserted that the first bookmark is titled
'Doc' -- true before the change, after it, and under any plausible breakage. It
is replaced by two tests that follow the heading after it relocates: the outline
entry resolves to the page the heading landed on, and a [text](#slug) link to it
resolves there too, which is a separate path through a named destination rather
than an outline entry.

Neither test discriminates bookmark-on-paragraph from bookmark-on-wrapper --
verified by moving them and watching all six stay green, because a one-child
wrapper relocates whole and never separates from its paragraph. That choice
shows up only in the destination's Y (~8 pt, the heading's space-above margin),
which is too brittle to assert; the javadoc says so rather than implying a
guard that is not there.

194 tests green.
develop branched at v0.3.0 and never received the v0.3.1 patch cut on main, so
its CHANGELOG had no v0.3.1 section, its README still advertised 0.3.0, and the
committed manual renders were the 0.3.0 ones. A release cut from this line would
have published a changelog with a hole in it and an install snippet two versions
stale.

Conflicts were the version literals and the changelog head. The poms keep this
line's values (0.4.0-SNAPSHOT, GraphCompose 2.1.1) since main's are older; the
changelog keeps [Unreleased] and gains main's v0.3.1 section in its place above
v0.3.0. README, the manual source and both committed manual PDFs come from main
unchanged -- they are re-rendered as part of the release commit.
Minor release. Headings stop being stranded at the bottom of a page apart from
the block they introduce, and the PDF backend now draws the link underlines and
strikethrough every theme already asked for -- both from moving to the
GraphCompose 2.x engine line, which this release lands on at 2.1.1.

The one public-API change is a fourth PageTokens component,
keepHeadingWithNext, defaulting to true. The three-argument constructor is kept
as an overload, so existing theme code compiles and behaves identically apart
from the fix.

Version literals move 0.4.0-SNAPSHOT -> 0.4.0 in lockstep across the root, cli/
and examples/ poms (the CI version-lockstep guard), README's status line and
install snippet move 0.3.1 -> 0.4.0, and examples/README's install line with
them.

The committed manual renders are regenerated on the new engine and do change:
manual.pdf 44538 -> 44685 bytes, manual-dark.pdf 44669 -> 44820. That growth is
the underline and strikethrough marks the 1.9.x backend was dropping.

Verified: ./mvnw clean verify green (194 tests), cli verify green (7 tests),
examples compile clean against the released coordinate.
The heading fix is on by default and moves content, so anyone comparing rendered
PDFs against committed baselines will see them drift. The entry described the
fix but not its blast radius.

Rendering this repository's own manual made the scale concrete: two headings
were stranded there, not zero, and fixing them redrew four of six pages by
22-39 percent of their pixels. Says so, and names the two flags that restore the
old flow.
@DemchaAV
DemchaAV merged commit 892f2f3 into main Aug 5, 2026
4 checks passed
@DemchaAV
DemchaAV deleted the release/0.4.0 branch August 5, 2026 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant