Skip to content

fix(templates): Timeline Minimal renders the whole CV - #519

Merged
DemchaAV merged 3 commits into
developfrom
fix/timeline-minimal-content-fidelity
Aug 7, 2026
Merged

fix(templates): Timeline Minimal renders the whole CV#519
DemchaAV merged 3 commits into
developfrom
fix/timeline-minimal-content-fidelity

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Why

TimelineMinimal dropped CV content three ways, none of them visible in the
rendered PDF.

Per-module caps. ModulePlacement carried a line limit per block — Profile 1,
Expertise 3, Languages 3, Work Experience 4, Education 5, Skills 6 — applied as
lines.stream().limit(n). Entries past the cap were not drawn and nothing said so.
A fourth degree, a third employer, the eighth skill group: gone, on a page whose
fixed-height axis left it looking four-fifths used.

Character-budget clipping. excerpt(line, 76 | 136 | 245) cut text with an
ellipsis by counting characters, not by what fit, so a summary ended mid-sentence
with the column still open beneath it.

Keyword-only slotting. Each module called SectionLookup.firstMatching
independently. A second section answering the same keywords was shadowed by the
first; a section whose title matched no list — a user's own "Awards",
"Publications" — was never looked at; and matched sections were relabelled, so a
section the author titled "Projects" printed as EXPERTISE and "Additional
Information" printed as LANGUAGES.

What

Everything the document carries is rendered.

SectionAllocation (new, cv/components) hands each section out once and
returns what no module claimed. The leftovers go into the main column under their
own headings. Module headings are the section's own title; the preset's labels are
left for a module that matched nothing — and since such a module also has nothing
to draw, they are effectively unreachable.

Pagination instead of truncation. The body is an addRow, and a row is atomic:
only ListDefinition, ParagraphDefinition and TableDefinition implement
NodeDefinition.split, so the paginator cannot break inside a row and one taller
than the page raises AtomicNodeTooLargeException rather than flowing. Dropping
the caps outright therefore trades silent loss for a crash — reproduced before
writing the fix. ColumnPagination (new, package-private) estimates a column's
height by turning a character count into wrapped lines from the column width and
the theme's font metrics; the preset emits one row per page and lets each finished
row overflow naturally, the same way MintEditorial reaches its second page. What
the masthead takes off the budget is measured from the identity, because the
contact stack grows a row per link.

The axis keeps its full 620pt on the opening page — it is the preset's signature —
and follows the content on a continuation page, so a page carrying three lines does
not get 620pt of rule beside them.

Not in scope. Nine sibling presets still slot by keyword and still discard what
does not match (BlueBanner, ClassicSerif, CompactMono, EngineeringResume,
MintEditorial, MonogramSidebar, NordicClean, Panel, SidebarPortrait);
three still cap, one still clips with an ellipsis. Untouched here.

Tests

TimelineMinimalContentFidelityTest (new, 5) renders a dense CV — four degrees,
three employers, three projects, eight skill groups, a second prose section, an
"Awards" section matching no keyword list — and reads the text back out of the PDF.
Verified from both sides: with the preset reverted to develop, four of the five
fail; with it restored, all pass.
The first fixture's summary sat under the old
245-character budget, so the ellipsis test passed on the broken code until the
summary was lengthened enough to discriminate.

ColumnPaginationTest (new, 10) covers the estimator directly, including
conservation across pages, a block carried across pages keeping its heading, a
block moving whole rather than orphaning its opening lines, and — under
@Timeout(5) — a budget too small for even a heading, which must still terminate
and still keep every line.

SectionAllocationTest (new, 11) covers claim-once semantics, document-order
leftovers, empty sections, null tolerance, and that a blank title cannot reach the
fallback because every CvSection rejects one at construction.

Visual baselines for timeline_minimal were re-approved after inspecting the
render; page 1 goes from 80% to 94% of the page used and page 2 is new. The other
eleven baselines the approve run rewrote were reverted — the flag re-records every
preset, and those are recorded on a different platform.

Gates: full reactor clean verify across the eight CI modules BUILD SUCCESS,
coverage checks met; examples verify 73/73 including CommittedAssetDriftTest
against the refreshed preview; javadoc:javadoc green for :graph-compose-core
and :graph-compose-templates.

DemchaAV and others added 3 commits August 5, 2026 20:20
The preset dropped content three ways, none of them visible in the
output. Per-module caps kept the first few lines of each block and
discarded the rest, so a fourth degree or a third employer was simply
never drawn — on a page the fixed-height axis left looking four-fifths
used. Prose was cut at a character count, ending a summary mid-sentence
with an ellipsis while the column still had room. And sections were
matched to modules by title keyword, first hit wins: a second prose
section was shadowed by the first, a section whose title matched nothing
was never looked at, and the ones that did match were relabelled, so
"Projects" printed as EXPERTISE and "Additional Information" as
LANGUAGES.

Everything the document carries is now rendered. SectionAllocation hands
each section out once and returns what no module claimed, which lands in
the main column under its own heading; headings come from the section's
own title, leaving the preset's labels for a module that matched nothing.

Content past one page continues on the next. The body is a row and a row
is atomic — only List, Paragraph and Table implement NodeDefinition.split,
so the paginator cannot break inside one and a too-tall row raises
AtomicNodeTooLargeException rather than flowing. ColumnPagination
therefore estimates each column's height from its width and the theme's
font metrics, and the preset emits one row per page, letting each
finished row overflow naturally. The axis keeps its full height on the
opening page and follows the content on a continuation page. What the
masthead takes off the budget is measured from the identity, since the
contact stack grows a row per link.

The nine other slot presets still match by keyword and still discard what
does not match; they are untouched here.
A section that matched no module reached the main column through a single
path that built every block as a bulleted one. A ParagraphSection arriving
that way was therefore set in the bullet face on bullet leading — a smaller
size and tighter lines than the same prose gets when the preset recognises
it. The text was all there, which is why it read as a styling quirk rather
than a bug, but a summary and a leftover summary rendered differently for no
reason the document expressed.

The leftover loop now picks the block kind from the section type, the way
the claimed sections already do.

TimelineMinimalPaginationTest pins a neighbouring guarantee that had no test
and no obvious owner: one computed slice gets one PDF page. It holds because
a slice closes early only when the next block will not fit what is left, and
that block opens the following slice, so two neighbours always exceed a page
between them. The invariant lives in the splitting code while the preset
depends on it, so it is asserted where it shows — page indices of the body
rows on a document no page of which is packed to the margin.

Verification: ./mvnw clean verify -pl :graph-compose-core,:graph-compose-render-pdf,
:graph-compose-templates,:graph-compose-testing,:graph-compose-qa -am — 720 tests,
0 failures, visual baselines unchanged. The new styling assertion fails on the
previous leftover loop and passes on this one.
@DemchaAV
DemchaAV merged commit 72bf06e into develop Aug 7, 2026
12 checks passed
@DemchaAV
DemchaAV deleted the fix/timeline-minimal-content-fidelity branch August 7, 2026 14:08
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