Skip to content

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

Open
DemchaAV wants to merge 1 commit into
developfrom
fix/timeline-minimal-content-fidelity
Open

fix(templates): Timeline Minimal renders the whole CV#519
DemchaAV wants to merge 1 commit 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.

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.
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