chore(release): 0.4.0 - #52
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 everypanel-shaped block this library builds.
Separately, every theme has always set
ShapeTokens.underlineLinks = trueand~~strike~~hasalways mapped to
DocumentTextDecoration.STRIKETHROUGH— but the PDF backend resolved thoseflags 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.
What changed
BuiltinRenderers.HeadingRendererwraps its paragraph in a one-child section markedkeepWithNext(). The flag has to live on a section: it is a node-level pagination propertyand
ParagraphNodecannot carry one. The bookmark and the anchor stay on the paragraph, sooutline entries and
#slugdestinations resolve to the heading's own position — a bookmarkmoved to the wrapper would point at the top of the space-above margin instead of at the text.
PageTokensgains a fourth component,keepHeadingWithNext(defaulttrue), so a themecan restore the plain flow. The three-argument constructor is kept as an overload, so existing
theme code — including
DefaultMarkdownTheme,PackSupportand downstream consumers buildingtheir own
PageTokens— compiles untouched and gets the fix by default.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.
maininto the 0.4.0 line (ab64d05).developbranched atv0.3.0andnever received the
v0.3.1patch: its CHANGELOG had nov0.3.1section, its README stilladvertised
0.3.0, and the committed manual renders were the 0.3.0 ones. Cutting 0.4.0 fromthat line would have published a changelog with a hole in it.
5573a5d): version literals0.4.0-SNAPSHOT→0.4.0in lockstepacross the root,
cli/andexamples/poms; README status line and install snippet, andexamples/README, move to0.4.0; CHANGELOG[Unreleased]becomes## v0.4.0 — 2026-08-05.manual.pdf44538 → 44685 bytes,manual-dark.pdf44669 → 44820. That growth is the underline and strikethrough marks the1.9.x backend was dropping.
Verification
./mvnw -B -ntp clean verify→ BUILD SUCCESS, 194 tests, 0 failures.cd cli && ../mvnw -B -ntp verify→ BUILD SUCCESS, 7 tests.examplescompiles cleanagainst the released
0.4.0coordinate../mvnw -B -ntp javadoc:javadoc→ clean.developfor every contentcommit in this PR — run
30988185762. Only5573a5dis new to CI here.HeadingKeepWithNextTest(new, 6 tests) pins the orphan window from both sides, which iswhat stops it rotting into a test that asserts nothing:
headingMovesDownRatherThanStrandingAtThePageBottomwithoutTheTokenTheHeadingStrandsAtThePageBottomheadingWithRoomForItsBodyIsUnaffectedtheBookmarkFollowsTheRelocatedHeadinganAnchorLinkFollowsTheRelocatedHeading[text](#slug)link resolves there too — a separate path, via a named destination rather than an outline entrydefaultThemeKeepsHeadingsWithTheirContentHonest 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-viewviewer and counted true orphans — a heading that is the last text line on its page:
0.3.1kitchen-sink.mdp2,## BlockquotekeepTogether()blockThe 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
v0.4.0onmainand push →release.ymlcuts the GitHub Release,publish.ymlshipsto Maven Central. That push is irreversible — a Central version cannot be withdrawn.
chore: start 0.4.1-SNAPSHOTondevelop.