Skip to content

feat(templates): route CV sections by what they mean, not by what they are called - #582

Open
DemchaAV wants to merge 1 commit into
feat/cv-modular-templatesfrom
feat/cv-role-routing
Open

feat(templates): route CV sections by what they mean, not by what they are called#582
DemchaAV wants to merge 1 commit into
feat/cv-modular-templatesfrom
feat/cv-role-routing

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

Stacked on #581#580#579, all targeting feature/cv-constructor-layer at the bottom. Merge in order; that branch is the snapshot to try the approach on before anything reaches develop.

Why

A preset with a designed layout places sections into fixed slots, and it chose what went where by matching the section's heading against a list of English words each preset kept privately — then guarded the slot on the section's Java type as well. A CV headed Ausbildung, Опыт работы, or anything else in the author's own language matched nothing: the section was dropped and the slot that wanted it rendered empty. Nothing failed; the CV came out looking finished, one job short. That is the failure the whole runtime-module model exists to remove, and until now it removed it only for the presets that render every section in order.

A module already knows the answer, because its author said so when they picked the role.

What changed

  • SectionRouter asks the role first, and falls back to the headings for the sections that carry none — every hand-written one, and any module left as SectionRole.OTHER — so a document of hand-written sections routes exactly as it did.
  • A heading may not overrule a role. A module declared EXPERIENCE and headed "Projects" goes where its author put it; without this the experience slot claims it by role and the projects slot claims it by heading, and it renders twice. An empty module also no longer shadows a section that has content.
  • Each slot receives the section in the shape it draws. These slots are written against one section type because each draws its content its own way, so a correctly-routed module was still discarded by if (!(section instanceof EntriesSection …)) return;. The router lowers a matched module to the type the slot expects, and the preset then draws it with the entry style, rules and spacing that make it that preset — rather than the canonical components, which several of these layouts cannot even host (a nested row inside a card throws).
  • What the lowering costs is stated per finder and pinned by tests: a description is joined where the target type holds one string; bulleted points join with a comma and prose with a space (joined the other way, "Rebuilt the ledger Cut p99 40%" reads as one garbled sentence and "Shipped it., Measured it." puts a comma after a full stop); a linked title survives only the row style that bolds through the text style rather than markdown markers; and a skill with nothing under it stays a skill instead of becoming a category holding itself (Java 21: Java 21).
  • SectionAllocation.claim(role, keys) is the same idea for the preset that allocates rather than looks up, and TimelineMinimal learned to flatten a module the way it flattens everything else — one line per item, because those lines are what its column pagination measures; rendering it richly would decouple the draw from the height estimate and overflow an atomic row.
  • SidebarPortrait's languages slot sniffs its rows for something language-shaped, because it also accepts an Additional Information section and has to pick them out of it. A section routed there by role is entirely languages and needs to look like nothing, so when the sniff finds none it now takes them all — otherwise role routing would have replaced a silent drop with a heading over blank space, which is worse.

Verification

  • Full reactor gate → BUILD SUCCESS, qa 859, templates 135 (+12). japicmp (both baselines, both modules) clean; javadoc:javadoc → 0 warnings. The 16 visual baselines are untouched, which follows from the fallback being the old lookup verbatim for sections that carry no role.
  • RoleRoutingTest: a CV written in Russian and German rendered through all sixteen presets, asserting the experience and education modules reach the page — plus the "wrong" kind for each slot (experience as bullets, education as an inline list), because the author picks the kind and the preset gets no veto. Proven non-vacuous: disabling role lookup turns 17 of its 34 cases red.
  • SectionRouterTest (12 cases) pins each lowering's output strings — the separator, the linked title, the skills grouping, the period suppression, and that a section already of the right type passes through untouched rather than being rebuilt.
  • SectionAllocationTest (+4) covers the role-first overload: role beats heading, heading still works for role-less sections, a declared role is not claimable by another slot's keywords, and claiming still hands each section out once.

Lane: templates (two components, ten presets).

Limits:

  • These presets still drop a section they have no slot for — an extra "Volunteering" module is lost on all ten. The cause is structural: the whole body of each is one flow.addRow(...), which is atomic and cannot break across pages, so there is nowhere to put an extra section. Lifting it is the pagination work (ColumnPagination), not routing, and the *_LIMIT caps that exist because of it come off in the same change.
  • None of these presets becomes ModularCvTemplate here, for exactly that reason; CvTemplates.modular() still lists seven.
  • The RowStyle a caller passes to SectionRouter.rows reaches the page only in Panel; the other slots draw rows their own way and ignore it.

…y are called

A preset with a designed layout places sections into fixed slots, and it
chose what went where by matching the section's heading against a list of
English words each preset kept privately — then guarded the slot on the
section's Java type as well. A CV headed Ausbildung, Опыт работы, or
anything else in the author's own language matched nothing: the section
was dropped and the slot that wanted it rendered empty. Nothing failed.
The CV came out looking finished, one job short.

A module already knows the answer, because its author said so.
SectionRouter asks the role first and falls back to the headings for the
sections that carry none — every hand-written one, and any module left as
OTHER — so a document of hand-written sections routes exactly as before. A
heading may not overrule a role: a module declared EXPERIENCE and headed
"Projects" goes where its author put it, and the projects slot does not
also claim it, which would have rendered it twice. An empty module does
not shadow a section that has content.

The second half is the shape. These slots are written against one section
type because each draws its content its own way, so a module reaching one
was discarded by the guard however well it was routed. The router hands
each slot the section lowered to the type it expects, and the preset then
draws it with the entry style, rules and spacing that make it that preset.
What the lowering costs is stated per finder and pinned by tests: a
description is joined where the target holds one string, bulleted points
join with a comma and prose with a space, a linked title survives only the
row style that bolds without markdown markers, and a skill with nothing
under it stays a skill rather than becoming a category holding itself.

SectionAllocation.claim gained the same role-first overload for the preset
that allocates rather than looks up, and TimelineMinimal learned to
flatten a module the way it flattens everything else — one line per item,
because those lines are what its column pagination measures.

SidebarPortrait's languages slot sniffs its rows for something
language-shaped, because it also accepts an Additional Information section
and has to pick them out. A section routed there by role is entirely
languages and needs to look like nothing, so when the sniff finds none it
now takes them all; otherwise role routing would have replaced a silent
drop with a heading over blank space.

These presets still drop a section they have no slot for. Their whole body
is one atomic row that cannot break across pages, so there is nowhere to
put it — that half waits on pagination, not routing.
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