Skip to content

feat(templates): let the column-flow presets take a runtime module - #591

Merged
DemchaAV merged 1 commit into
feature/cv-constructor-layerfrom
feat/cv-render-kits
Aug 21, 2026
Merged

feat(templates): let the column-flow presets take a runtime module#591
DemchaAV merged 1 commit into
feature/cv-constructor-layerfrom
feat/cv-render-kits

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

Why

CvTemplates.modular() is the list a CV builder offers when the CV is assembled at
runtime, and every template on it was single-column. The three column-flow presets —
SidebarPortrait, MonogramSidebar, MintEditorial — had everything the promise needs
after the leftover-tail and slot-heading work, but nothing said so, so a caller building
a CV from data could not offer a two-column design at all.

What

The three declare ModularCvTemplate; the list goes from seven templates to ten. They
earn it through their own renderers rather than the shared dispatcher: a module is
lowered by SectionRouter to the shape its slot draws and drawn by that slot, or by the
leftover tail under the author's heading — which is why it already comes out in the
preset's style.

Rather than guess what the contract needed, I declared the interface and let
ModularCvTemplateFidelityTest answer. It found six failures, and the split is the
interesting part: four were the harness, one was over-specification, and two
were real
.

The real one. Sidebar Portrait and Monogram Sidebar drew a grouped skills section as
one flat run of every skill in the CV. The shipped fixture has thirty-two skills under six
category names and not one of those names reached a page, so a module whose only label was
its group name arrived anonymous. Each group is headed by its own name now — which is also
easier to read than twelve-plus unlabelled lines.

That label is skipped when it would repeat the heading the block just drew: a module whose
items carry no bodies is lowered into a single group named after the module itself, so the
sidebar would otherwise print the section's title twice in a row. The comparison is on the
titles as written, not through SectionLookup.normalize — that strips every character
outside [a-z0-9] because it matches English keyword lists, so two Cyrillic titles both
reduce to "" and every non-Latin CV would lose the label instead. The gate caught that
one on the way in. MintEditorial's own bars/index dedup went the same way; it was safe
only because one side of it is always a Latin literal.

kit() returns the canonical kit for all three, and says why: a kit styles the bodies
a preset routes through SectionDispatcher, and these route none. SectionDispatcher
also resets the host's spacing and padding, which would fight a column layout, and
CvRenderKit has no way to say which of a two-column preset's body styles it is drawing.

Tests

ModularCvTemplateFidelityTest gained three corrections, all of which weaken nothing:

  • Item assertions dropped case but not letter-spacing while the heading assertion dropped
    both, so a preset that letter-spaces an entry title failed for its typography rather
    than for losing anything. Items are normalised the same way headings always were — and
    per paragraph, so a match has to live inside one node rather than being assembled
    out of two that happen to adjoin, which whole-page squashing would have allowed.
  • It required the canonical "Java 21, Kotlin" join for a skills group. That is a
    rendering style, not a promise — a preset drawing bars or a list shows both skills and
    failed anyway. It asserts the group's name and its skills in order, as a subsequence,
    which keeps the grouping and ordering the join carried.
  • Its per-kind markers were prefixes of one another: Item ENTRIES lives inside
    Item ENTRIES DATED, so a template could drop two of the six kinds outright and stay
    green. Pre-existing, and this change re-derived every one of those assertions, so it is
    fixed here.

SlotHeadingFidelityTest gains the case the fidelity fixtures never reach — a skills
module whose items carry no bodies, which is the natural runtime shape for a flat list.
Both presets were sabotaged to confirm it goes red without the dedup.

groupTokens reads SkillGroup.skills() instead of splitting skillsInline() back apart
on commas, which cut a skill carrying a comma of its own in half.

Full reactor gate green. Four baselines re-recorded (sidebar_portrait ×2,
monogram_sidebar ×2), the twelve visual.approve rewrote as collateral reverted, two
committed previews refreshed. The pixel gate passed at every step without being told
anything — measured at budget 0, the grouping moves 3,515 + 11,923 px on Sidebar Portrait
and 1,019 + 9,687 on Monogram Sidebar, all well under the 50,000 the budget allows for
cross-platform font drift.

docs/templates/v2-layered/using-templates.md still told readers these three were kept
off the modular list because they rename headings — which stopped being true when every
slot started printing the author's title. That paragraph, the single-main-column claim it
repeated, and the CvRenderKit paragraph are updated; the same falsified sentence in
ModularCvTemplate's own Javadoc is corrected too.

SidebarPortrait, MonogramSidebar and MintEditorial declare ModularCvTemplate,
so CvTemplates.modular() offers a two-column design to a CV assembled at
runtime rather than only single-column ones. They earn it through their own
renderers: a module is lowered by SectionRouter to the shape its slot draws
and drawn by that slot, or by the leftover tail under the author's heading,
which is why it already comes out in the preset's style.

Declaring it cost two of them a habit. Sidebar Portrait and Monogram Sidebar
drew a grouped skills section as one flat run of every skill in the CV — the
shipped fixture has thirty-two of them under six category names, and not one
of those names reached a page — so a module whose only label was its group
name arrived anonymous. Each group is headed by its own name now.

That label is skipped when it would repeat the heading the block just drew.
A module whose items carry no bodies is lowered into a single group named
after the module itself, so the sidebar would otherwise print the section's
title twice in a row. The comparison is on the titles as written rather than
through SectionLookup.normalize: that strips every character outside [a-z0-9]
because it matches English keyword lists, so two Cyrillic titles both reduce
to the empty string and every non-Latin CV would lose the label instead.
MintEditorial's own bars/index dedup went the same way — it was safe only
because one side of it is always a Latin literal.

kit() returns the canonical kit for all three, and says why: a kit styles the
bodies a preset routes through SectionDispatcher, and these route none. The
interface's promise repeated a sentence this change falsifies — "every shipped
preset composes a single main column" — which is now what it always meant:
every preset reads Slot.MAIN and no other, including the ones that compose a
sidebar of their own.

ModularCvTemplateFidelityTest grew three corrections. Its item assertions
dropped case but not letter-spacing while its heading assertion dropped both,
so a preset that letter-spaces an entry title failed for its typography rather
than for losing anything; items are normalised the same way headings always
were, per paragraph, so a match has to live inside one node instead of being
assembled out of two that happen to adjoin. It required the canonical
"Java 21, Kotlin" join for a skills group, which is a rendering style and not a
promise; it asserts the group's name and its skills in order as a subsequence
now, keeping the grouping and ordering the join carried while leaving bars,
chips or a list to the preset. And its per-kind markers were prefixes of one
another — "Item ENTRIES" lives inside "Item ENTRIES DATED" — so a template
could drop two of the six kinds outright and stay green.

groupTokens reads SkillGroup.skills() rather than splitting skillsInline()
back apart on commas, which cut a skill carrying a comma of its own in half.

docs/templates/v2-layered/using-templates.md still said these three were kept
off the modular list because they rename headings, which stopped being true
when every slot started printing the author's title.
@DemchaAV
DemchaAV merged commit f943e59 into feature/cv-constructor-layer Aug 21, 2026
12 checks passed
@DemchaAV
DemchaAV deleted the feat/cv-render-kits branch August 21, 2026 18:23
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