Skip to content

test(cv/v2): pixel-diff visual parity gate for 3 layered presets#52

Merged
DemchaAV merged 1 commit into
developfrom
feature/cv-v2-visual-regression
May 24, 2026
Merged

test(cv/v2): pixel-diff visual parity gate for 3 layered presets#52
DemchaAV merged 1 commit into
developfrom
feature/cv-v2-visual-regression

Conversation

@DemchaAV
Copy link
Copy Markdown
Owner

Summary

Phase 4 — defensive infrastructure. After 5 PRs (#45-#51, ~4000
lines) of layered architecture work, there was no automated guard
against silent visual regression
. Refactoring widgets / theme
tokens / renderers could change the rendered PDF and nothing would
fail the build. This PR closes that gap.

What's new

CvV2VisualParityTest.java — parameterised over the 3 shipped
v2 presets:

Slug Preset
boxed_sections BoxedSections
minimal_underlined MinimalUnderlined
modern_professional ModernProfessional

Each renders a canonical CvDocument fixture (Jordan Rivera —
exercises every section subtype: ParagraphSection, RowsSection
with all 3 RowStyle variants, EntriesSection), rasterises
page-by-page via PDFBox, and asserts per-pixel diff against a
checked-in baseline PNG.

src/test/resources/visual-baselines/cv-v2-layered/ — 6
baseline PNGs (3 presets × 2 pages each). Captured via
-Dgraphcompose.visual.approve=true against the current
develop-state rendering.

Reuses PdfVisualRegression — the same harness used by v1's
PresetVisualParityTest. Same budget calibration: 20 000
mismatched pixels per page at per-channel tolerance 8

calibrated for cross-platform PDFBox font/colour drift between
Windows-recorded baselines and Linux CI.

Workflow

After a deliberate visual change (theme rebalance, widget tweak,
new component):

./mvnw test -Dtest=CvV2VisualParityTest -Dgraphcompose.visual.approve=true
git add src/test/resources/visual-baselines/cv-v2-layered/*.png
git commit -m "test: refresh visual baselines after <reason>"

Normal CI run (default) just diffs. Failures write
<slug>-page-N.actual.png and <slug>-page-N.diff.png next to the
baseline so a reviewer can see exactly what changed before deciding
to re-bless or fix.

Docs

  • docs/templates/v2-layered/contributor-guide.md gains a "Visual
    regression — pixel-diff parity gate"
    section under "Test
    checklist":
    • explains the workflow (refresh + diff)
    • where baselines live
    • budget calibration rationale
    • points contributors at this test file as a drop-in template
      for new template families
  • New row in the test-checklist table marking visual parity as
    required (was previously "encouraged but optional").

What's NOT changed

  • No engine / source edits.
  • No v1 surface edits.
  • No widget / preset / theme behavioural changes — pure test
    • baseline addition.

Test plan

  • CvV2VisualParityTest — 3/3 tests pass against fresh baselines
  • CanonicalSurfaceGuardTest — 8/8 still green (no docs
    surface drift)
  • Inline canonical document (test doesn't depend on examples
    module)
  • CI green

Decision points for reviewer

  • Budget = 20 000 px / tolerance = 8: copied from v1
    PresetVisualParityTest. If you find a preset flakes
    consistently in CI, widen for that preset specifically rather
    than relaxing the global setting.
  • Inline canonicalDocument() vs pull from ExampleDataFactory:
    chose inline so the test depends only on main + main-test code
    (no circular dep with examples module). Tradeoff: sample content
    duplicated. If ExampleDataFactory ever moves into main, we can
    consolidate.

Next phase (if you want)

  • Phase 5 — port TimelineMinimal (multi-column with slots —
    first real consumer of Slot.SIDEBAR)
  • Phase 6invoice-v2 migration following the contributor
    guide
  • Or stop here — concept is now defended by snapshot tests.

@DemchaAV DemchaAV force-pushed the feature/cv-v2-visual-regression branch from 6e4de08 to e71eff4 Compare May 24, 2026 18:04
DemchaAV added a commit that referenced this pull request May 24, 2026
… rows

Reader feedback: Education / Experience / Projects sections rendered
as a "wall of text" — consecutive university entries, jobs, and
projects shared the same line spacing as their internal lines
(title → subtitle → body), so the boundary between one entry and
the next was visually invisible.

Fix
---

- theme/CvSpacing — new field `entrySeparation` (double, points)
  controlling the vertical spacer between consecutive multi-line
  entries.
  - classic preset: 6.0pt
  - modernProfessional preset: 8.0pt (denser overall layout, bigger
    separator stands out more)
- @deprecated 13-arg constructor preserved for backward-compat —
  fills the new field with 6.0pt default so any pre-existing custom
  CvSpacing instance keeps compiling AND benefits from the
  improvement automatically.

- components/SectionDispatcher inserts `host.spacer(0, entrySeparation)`
  between items for:
  - EntriesSection — always (Education, Experience are multi-line)
  - RowsSection with BULLETED_STACKED style — between project items
    (each project is a two-line block — bold name + indented body)
  Single-line styles (PLAIN, BULLETED) are unaffected — they
  already breathe via the per-row paragraphMarginTop.

Visual diff (verified by re-rendered cv-modern-professional-v2.pdf):

  before:                          after:
    MSc Computer Science             MSc Computer Science
    University of Manchester         University of Manchester
    Distinction. Thesis: ...         Distinction. Thesis: ...
    BSc Software Engineering                                  ← gap
    Imperial College London          BSc Software Engineering
    First-class honours...           Imperial College London
                                     First-class honours...

Baselines
---------

Refreshed all 6 visual-baseline PNGs via the
`-Dgraphcompose.visual.approve=true` workflow established in #52 —
this commit is the first deliberate consumer of that workflow,
demonstrating it works as designed.

Tests
-----

- 3/3 CvV2VisualParityTest pass against refreshed baselines
- All existing v2 tests still green (CvNameTest, CvContactTest,
  CvDecorationTest, CvDocumentSlotTest, three preset smoke tests,
  WidgetSmokeTest)
- No engine / v1 surface edits
DemchaAV added a commit that referenced this pull request May 24, 2026
… rows

Reader feedback: Education / Experience / Projects sections rendered
as a "wall of text" — consecutive university entries, jobs, and
projects shared the same line spacing as their internal lines
(title → subtitle → body), so the boundary between one entry and
the next was visually invisible.

Fix
---

- theme/CvSpacing — new field `entrySeparation` (double, points)
  controlling the vertical spacer between consecutive multi-line
  entries.
  - classic preset: 6.0pt
  - modernProfessional preset: 8.0pt (denser overall layout, bigger
    separator stands out more)
- @deprecated 13-arg constructor preserved for backward-compat —
  fills the new field with 6.0pt default so any pre-existing custom
  CvSpacing instance keeps compiling AND benefits from the
  improvement automatically.

- components/SectionDispatcher inserts `host.spacer(0, entrySeparation)`
  between items for:
  - EntriesSection — always (Education, Experience are multi-line)
  - RowsSection with BULLETED_STACKED style — between project items
    (each project is a two-line block — bold name + indented body)
  Single-line styles (PLAIN, BULLETED) are unaffected — they
  already breathe via the per-row paragraphMarginTop.

Visual diff (verified by re-rendered cv-modern-professional-v2.pdf):

  before:                          after:
    MSc Computer Science             MSc Computer Science
    University of Manchester         University of Manchester
    Distinction. Thesis: ...         Distinction. Thesis: ...
    BSc Software Engineering                                  ← gap
    Imperial College London          BSc Software Engineering
    First-class honours...           Imperial College London
                                     First-class honours...

Baselines
---------

Refreshed all 6 visual-baseline PNGs via the
`-Dgraphcompose.visual.approve=true` workflow established in #52 —
this commit is the first deliberate consumer of that workflow,
demonstrating it works as designed.

Tests
-----

- 3/3 CvV2VisualParityTest pass against refreshed baselines
- All existing v2 tests still green (CvNameTest, CvContactTest,
  CvDecorationTest, CvDocumentSlotTest, three preset smoke tests,
  WidgetSmokeTest)
- No engine / v1 surface edits
DemchaAV added a commit that referenced this pull request May 24, 2026
… rows

Reader feedback: Education / Experience / Projects sections rendered
as a "wall of text" — consecutive university entries, jobs, and
projects shared the same line spacing as their internal lines
(title → subtitle → body), so the boundary between one entry and
the next was visually invisible.

Fix
---

- theme/CvSpacing — new field `entrySeparation` (double, points)
  controlling the vertical spacer between consecutive multi-line
  entries.
  - classic preset: 6.0pt
  - modernProfessional preset: 8.0pt (denser overall layout, bigger
    separator stands out more)
- @deprecated 13-arg constructor preserved for backward-compat —
  fills the new field with 6.0pt default so any pre-existing custom
  CvSpacing instance keeps compiling AND benefits from the
  improvement automatically.

- components/SectionDispatcher inserts `host.spacer(0, entrySeparation)`
  between items for:
  - EntriesSection — always (Education, Experience are multi-line)
  - RowsSection with BULLETED_STACKED style — between project items
    (each project is a two-line block — bold name + indented body)
  Single-line styles (PLAIN, BULLETED) are unaffected — they
  already breathe via the per-row paragraphMarginTop.

Visual diff (verified by re-rendered cv-modern-professional-v2.pdf):

  before:                          after:
    MSc Computer Science             MSc Computer Science
    University of Manchester         University of Manchester
    Distinction. Thesis: ...         Distinction. Thesis: ...
    BSc Software Engineering                                  ← gap
    Imperial College London          BSc Software Engineering
    First-class honours...           Imperial College London
                                     First-class honours...

Baselines
---------

Refreshed all 6 visual-baseline PNGs via the
`-Dgraphcompose.visual.approve=true` workflow established in #52 —
this commit is the first deliberate consumer of that workflow,
demonstrating it works as designed.

Tests
-----

- 3/3 CvV2VisualParityTest pass against refreshed baselines
- All existing v2 tests still green (CvNameTest, CvContactTest,
  CvDecorationTest, CvDocumentSlotTest, three preset smoke tests,
  WidgetSmokeTest)
- No engine / v1 surface edits
DemchaAV added a commit that referenced this pull request May 24, 2026
… rows

Reader feedback: Education / Experience / Projects sections rendered
as a "wall of text" — consecutive university entries, jobs, and
projects shared the same line spacing as their internal lines
(title → subtitle → body), so the boundary between one entry and
the next was visually invisible.

Fix
---

- theme/CvSpacing — new field `entrySeparation` (double, points)
  controlling the vertical spacer between consecutive multi-line
  entries.
  - classic preset: 6.0pt
  - modernProfessional preset: 8.0pt (denser overall layout, bigger
    separator stands out more)
- @deprecated 13-arg constructor preserved for backward-compat —
  fills the new field with 6.0pt default so any pre-existing custom
  CvSpacing instance keeps compiling AND benefits from the
  improvement automatically.

- components/SectionDispatcher inserts `host.spacer(0, entrySeparation)`
  between items for:
  - EntriesSection — always (Education, Experience are multi-line)
  - RowsSection with BULLETED_STACKED style — between project items
    (each project is a two-line block — bold name + indented body)
  Single-line styles (PLAIN, BULLETED) are unaffected — they
  already breathe via the per-row paragraphMarginTop.

Visual diff (verified by re-rendered cv-modern-professional-v2.pdf):

  before:                          after:
    MSc Computer Science             MSc Computer Science
    University of Manchester         University of Manchester
    Distinction. Thesis: ...         Distinction. Thesis: ...
    BSc Software Engineering                                  ← gap
    Imperial College London          BSc Software Engineering
    First-class honours...           Imperial College London
                                     First-class honours...

Baselines
---------

Refreshed all 6 visual-baseline PNGs via the
`-Dgraphcompose.visual.approve=true` workflow established in #52 —
this commit is the first deliberate consumer of that workflow,
demonstrating it works as designed.

Tests
-----

- 3/3 CvV2VisualParityTest pass against refreshed baselines
- All existing v2 tests still green (CvNameTest, CvContactTest,
  CvDecorationTest, CvDocumentSlotTest, three preset smoke tests,
  WidgetSmokeTest)
- No engine / v1 surface edits
DemchaAV added a commit that referenced this pull request May 24, 2026
… rows

Reader feedback: Education / Experience / Projects sections rendered
as a "wall of text" — consecutive university entries, jobs, and
projects shared the same line spacing as their internal lines
(title → subtitle → body), so the boundary between one entry and
the next was visually invisible.

Fix
---

- theme/CvSpacing — new field `entrySeparation` (double, points)
  controlling the vertical spacer between consecutive multi-line
  entries.
  - classic preset: 6.0pt
  - modernProfessional preset: 8.0pt (denser overall layout, bigger
    separator stands out more)
- @deprecated 13-arg constructor preserved for backward-compat —
  fills the new field with 6.0pt default so any pre-existing custom
  CvSpacing instance keeps compiling AND benefits from the
  improvement automatically.

- components/SectionDispatcher inserts `host.spacer(0, entrySeparation)`
  between items for:
  - EntriesSection — always (Education, Experience are multi-line)
  - RowsSection with BULLETED_STACKED style — between project items
    (each project is a two-line block — bold name + indented body)
  Single-line styles (PLAIN, BULLETED) are unaffected — they
  already breathe via the per-row paragraphMarginTop.

Visual diff (verified by re-rendered cv-modern-professional-v2.pdf):

  before:                          after:
    MSc Computer Science             MSc Computer Science
    University of Manchester         University of Manchester
    Distinction. Thesis: ...         Distinction. Thesis: ...
    BSc Software Engineering                                  ← gap
    Imperial College London          BSc Software Engineering
    First-class honours...           Imperial College London
                                     First-class honours...

Baselines
---------

Refreshed all 6 visual-baseline PNGs via the
`-Dgraphcompose.visual.approve=true` workflow established in #52 —
this commit is the first deliberate consumer of that workflow,
demonstrating it works as designed.

Tests
-----

- 3/3 CvV2VisualParityTest pass against refreshed baselines
- All existing v2 tests still green (CvNameTest, CvContactTest,
  CvDecorationTest, CvDocumentSlotTest, three preset smoke tests,
  WidgetSmokeTest)
- No engine / v1 surface edits
Phase 4 — defensive infrastructure. After 5 PRs (#45-#51, ~4000
lines) of layered architecture work, there was no automated guard
against silent visual regression. Refactoring widgets / theme
tokens / renderers could change the rendered PDF and nothing would
fail the build. This PR closes that gap.

What's new
----------

- src/test/java/.../cv/v2/presets/CvV2VisualParityTest.java
  Parameterised over the 3 shipped v2 presets (BoxedSections,
  MinimalUnderlined, ModernProfessional). Each renders a canonical
  CvDocument fixture (Jordan Rivera — exercises every section
  subtype: ParagraphSection, RowsSection with all 3 RowStyle
  variants, EntriesSection), rasterises page-by-page via PDFBox,
  and asserts per-pixel diff against a checked-in baseline PNG.

- src/test/resources/visual-baselines/cv-v2-layered/
  6 baseline PNGs — 3 presets × 2 pages each. Captured via
  `-Dgraphcompose.visual.approve=true` against the current
  develop-state rendering.

- Reused PdfVisualRegression harness (already in the codebase —
  used by v1's PresetVisualParityTest). Same budget calibration:
  20 000 mismatched pixels per page at per-channel tolerance 8 —
  calibrated for cross-platform PDFBox font/colour drift.

- Inline canonical document (not pulled from examples module) so
  the test depends only on main + main-test code. Mirrors the
  pattern from v1 PresetVisualParityTest.canonicalCvSpec().

Workflow
--------

After a deliberate visual change (theme rebalance, widget tweak,
etc.):

  ./mvnw test -Dtest=CvV2VisualParityTest -Dgraphcompose.visual.approve=true
  git add src/test/resources/visual-baselines/cv-v2-layered/*.png
  git commit -m "test: refresh visual baselines after <reason>"

Normal CI run (default) just diffs. Failures write <slug>-page-N.actual.png
and <slug>-page-N.diff.png next to the baseline for review.

Docs
----

- docs/templates/v2-layered/contributor-guide.md gains a "Visual
  regression — pixel-diff parity gate" section under "Test
  checklist": explains the workflow, where baselines live, the
  budget calibration rationale, and points contributors at this
  test file as a drop-in template for new template families.

Test results
------------

- 3/3 visual parity tests pass against fresh baselines
- 8/8 CanonicalSurfaceGuardTest still green (no docs surface
  drift from this change)
- No engine, source, or v1 surface edits
@DemchaAV DemchaAV force-pushed the feature/cv-v2-visual-regression branch from e71eff4 to 1eb2500 Compare May 24, 2026 20:17
@DemchaAV DemchaAV merged commit a0db2c8 into develop May 24, 2026
9 checks passed
@DemchaAV DemchaAV deleted the feature/cv-v2-visual-regression branch May 24, 2026 20:21
DemchaAV added a commit that referenced this pull request May 24, 2026
… rows

Reader feedback: Education / Experience / Projects sections rendered
as a "wall of text" — consecutive university entries, jobs, and
projects shared the same line spacing as their internal lines
(title → subtitle → body), so the boundary between one entry and
the next was visually invisible.

Fix
---

- theme/CvSpacing — new field `entrySeparation` (double, points)
  controlling the vertical spacer between consecutive multi-line
  entries.
  - classic preset: 6.0pt
  - modernProfessional preset: 8.0pt (denser overall layout, bigger
    separator stands out more)
- @deprecated 13-arg constructor preserved for backward-compat —
  fills the new field with 6.0pt default so any pre-existing custom
  CvSpacing instance keeps compiling AND benefits from the
  improvement automatically.

- components/SectionDispatcher inserts `host.spacer(0, entrySeparation)`
  between items for:
  - EntriesSection — always (Education, Experience are multi-line)
  - RowsSection with BULLETED_STACKED style — between project items
    (each project is a two-line block — bold name + indented body)
  Single-line styles (PLAIN, BULLETED) are unaffected — they
  already breathe via the per-row paragraphMarginTop.

Visual diff (verified by re-rendered cv-modern-professional-v2.pdf):

  before:                          after:
    MSc Computer Science             MSc Computer Science
    University of Manchester         University of Manchester
    Distinction. Thesis: ...         Distinction. Thesis: ...
    BSc Software Engineering                                  ← gap
    Imperial College London          BSc Software Engineering
    First-class honours...           Imperial College London
                                     First-class honours...

Baselines
---------

Refreshed all 6 visual-baseline PNGs via the
`-Dgraphcompose.visual.approve=true` workflow established in #52 —
this commit is the first deliberate consumer of that workflow,
demonstrating it works as designed.

Tests
-----

- 3/3 CvV2VisualParityTest pass against refreshed baselines
- All existing v2 tests still green (CvNameTest, CvContactTest,
  CvDecorationTest, CvDocumentSlotTest, three preset smoke tests,
  WidgetSmokeTest)
- No engine / v1 surface edits
DemchaAV added a commit that referenced this pull request May 24, 2026
… rows (#53)

Reader feedback: Education / Experience / Projects sections rendered
as a "wall of text" — consecutive university entries, jobs, and
projects shared the same line spacing as their internal lines
(title → subtitle → body), so the boundary between one entry and
the next was visually invisible.

Fix
---

- theme/CvSpacing — new field `entrySeparation` (double, points)
  controlling the vertical spacer between consecutive multi-line
  entries.
  - classic preset: 6.0pt
  - modernProfessional preset: 8.0pt (denser overall layout, bigger
    separator stands out more)
- @deprecated 13-arg constructor preserved for backward-compat —
  fills the new field with 6.0pt default so any pre-existing custom
  CvSpacing instance keeps compiling AND benefits from the
  improvement automatically.

- components/SectionDispatcher inserts `host.spacer(0, entrySeparation)`
  between items for:
  - EntriesSection — always (Education, Experience are multi-line)
  - RowsSection with BULLETED_STACKED style — between project items
    (each project is a two-line block — bold name + indented body)
  Single-line styles (PLAIN, BULLETED) are unaffected — they
  already breathe via the per-row paragraphMarginTop.

Visual diff (verified by re-rendered cv-modern-professional-v2.pdf):

  before:                          after:
    MSc Computer Science             MSc Computer Science
    University of Manchester         University of Manchester
    Distinction. Thesis: ...         Distinction. Thesis: ...
    BSc Software Engineering                                  ← gap
    Imperial College London          BSc Software Engineering
    First-class honours...           Imperial College London
                                     First-class honours...

Baselines
---------

Refreshed all 6 visual-baseline PNGs via the
`-Dgraphcompose.visual.approve=true` workflow established in #52 —
this commit is the first deliberate consumer of that workflow,
demonstrating it works as designed.

Tests
-----

- 3/3 CvV2VisualParityTest pass against refreshed baselines
- All existing v2 tests still green (CvNameTest, CvContactTest,
  CvDecorationTest, CvDocumentSlotTest, three preset smoke tests,
  WidgetSmokeTest)
- No engine / v1 surface edits
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