Skip to content

feat(templates): declare which presets can be handed a runtime module - #581

Open
DemchaAV wants to merge 1 commit into
feat/cv-module-sectionsfrom
feat/cv-modular-templates
Open

feat(templates): declare which presets can be handed a runtime module#581
DemchaAV wants to merge 1 commit into
feat/cv-module-sectionsfrom
feat/cv-modular-templates

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

Stacked on #580 (base feat/cv-module-sections), which is itself stacked on #579. Retarget down the chain as each merges.

Why

The previous PR made a CV section's shape a runtime value. That is only worth anything if the template you hand it to renders it — and not every preset can promise that. Several compose a fixed set of modules and find each by matching headings against private keyword lists, so a section they do not recognise never reaches a renderer at all. The CV still comes out, minus a section, looking finished. Nothing about that failure is visible at the point it happens; it surfaces weeks later as a job that was never listed.

So the question "can I give this template a module I invented?" needs an answer that is checked rather than assumed. And a second question follows it: if a preset can render the module, can it render it in its own style rather than the canonical one — the limitation #580 documented and left open.

What changed

  • ModularCvTemplate is the promise; CvTemplates.modular() is the list to offer. The registry also answers byId, all, ids, and recommendedMargin (the margin a preset was designed at, which a caller needs while building the session, before it has a template). Picking a template at runtime stops being a map kept by hand in each consumer — the one this model exists for keeps exactly that map in another repository, where nothing tells it a preset shipped.
  • Declaring the interface costs something. ModularCvTemplateFidelityTest enumerates CvTemplates.modular() and every CvKind — not lists of its own — and renders: every kind, a section this catalogue has no name for, a heading in a script no keyword list contains, and a heading that does match one. Seven presets qualify. ClassicSerif does not — it draws any shape it is given, but only gives itself the sections it recognises. Finding that out is what the gate is for; it kept its kit and lost the claim.
  • The promise names its scope: Slot.MAIN. Every shipped preset composes one main column, so a section placed in Slot.SIDEBAR is dropped — by these templates as by every other, which is what CvDocument has always documented. Saying so in the contract, and pinning it in the gate, is the difference between a contract and a slogan; when slots go live that test goes red and the promise moves with the code.
  • CvRenderKit is the three shapes a section body reduces to — a paragraph, a label/value row, a timeline entry — and a template hands back the kit it draws them with. The lowering from CvItem stays shared: what a linked title looks like, which fields a kind reads, what an empty description does to a trailing colon are the model's decisions and must not be re-made sixteen times. It is also the shape the presets already had — their private renderers take a CvEntry or a CvRow today. BlueBanner, ClassicSerif and EditorialBlue now draw modules with their own entry and project shapes; the rest return CvRenderKit.defaults(), and every one of them passes its kit to the dispatcher so overriding it later actually does something.
  • EditorialBlue stops renaming a module's heading. Its keyword vocabulary turned "Certifications & Awards" into EDUCATION — the one thing the promise says cannot happen. The canonical sections keep the rename that gives the preset its voice.
  • CvTemplatesCoverageTest derives the catalogue from the presets package rather than trusting it, so a preset that ships unregistered fails the build instead of being invisible to every caller that looks one up by id. No preset list in this repo had such a guard.

Verification

  • Full reactor gate — ./mvnw -B -ntp clean verify -pl :graph-compose-core,:graph-compose-render-pdf,:graph-compose-render-docx,:graph-compose-render-pptx,:graph-compose-templates,:graph-compose-testing,:graph-compose-qa,:graph-compose-coverage -amBUILD SUCCESS, qa 821 tests (+14). The 16 CV visual baselines are untouched: the three-argument SectionDispatcher.renderBody delegates with defaults(), whose methods are the same static renderers, and the kit-passing presets reach the four-argument form only from their module branch.
  • Fidelity gate, 43 cases across the seven modular templates. Proven non-vacuous: restoring EditorialBlue's heading rename turns aHeadingThePresetHasAWordForIsStillTheAuthorsHeading red for editorial-blue and nothing else.
  • Registry guard, 6 cases, proven fail-closed: removing Panel from the catalogue fails everyPresetInThePackageIsInTheCatalogue with the directory named in the message.
  • japicmp (both baselines, both modules) → BUILD SUCCESS — the new types are additions and SectionDispatcher gained an overload rather than changing one. javadoc:javadoc0 warnings.
  • Two cases that duplicated the new gate were moved out of ModuleSectionKindCoverageTest rather than copied, so the 16×6 documents are rendered once.

Lane: templates (api + components + registry + nine presets).

Limits:

  • Eight presets still guard their module slots on the section's Java type and are not modular; that, SectionRole-driven routing, and live slots are the next PR.
  • EditorialBlue's kit draws entries in its experience styling: it picks between experience and education by sniffing a heading, which is what a module carries a role to avoid, and the kit is not handed that role yet.
  • A description line the author asked to bullet is drawn by the shared primitives rather than the kit — a bullet glyph is the shape the model was asked for, not a style a template gets a say in.

A module is only worth building if the template renders it, and not every
preset can promise that. Several compose a fixed set of modules and find
each by matching headings, so a section they do not recognise never
reaches a renderer: the CV comes out, minus a section, looking finished.
Nothing about that failure is visible at the point it happens.

ModularCvTemplate is the promise and CvTemplates.modular() is the list a
CV builder should offer; CvTemplates also answers byId, all, ids, and
recommendedMargin, so picking a preset at runtime stops being a map kept
by hand in every consumer. Declaring the interface costs something:
ModularCvTemplateFidelityTest renders every kind, a section this
catalogue has no name for, a heading in a script no keyword list
contains, and a heading that does match one, through each template that
declares it. Seven presets qualify. ClassicSerif does not — it draws any
shape it is given but only gives itself the sections it recognises, and
finding that out is what the gate is for.

The promise covers Slot.MAIN and says so, rather than leaving "renders
whatever it is handed" to be read generously: every shipped preset
composes one main column, so a sidebar section is dropped by these
templates as by every other. The gate pins that too, so the contract and
the code have to change together.

CvRenderKit is the three shapes a section body reduces to — a paragraph,
a label/value row, a timeline entry — and a template hands back the kit
it draws them with. The lowering from CvItem stays shared: what a linked
title looks like, which fields a kind reads, what an empty description
does to a trailing colon are the model's decisions and must not be
re-made sixteen times. BlueBanner, ClassicSerif and EditorialBlue now
draw modules with their own entry and project shapes.

EditorialBlue also stops renaming a module's heading. Its keyword
vocabulary turned "Certifications & Awards" into EDUCATION, which is the
one thing the promise says cannot happen; the canonical sections keep the
rename that gives the preset its voice.

CvTemplatesCoverageTest derives the catalogue from the presets package
rather than trusting it, so a preset that ships unregistered fails the
build instead of being invisible to every caller that looks one up by id.
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