Skip to content

feat(templates): add the Payments paginating invoice preset - #638

Merged
DemchaAV merged 2 commits into
feature/template-promotionfrom
feat/payments-invoice-preset
Sep 1, 2026
Merged

DemchaAV merged 2 commits into
feature/template-promotionfrom
feat/payments-invoice-preset

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Why

stripe-invoice is the richest bundle in the promotion queue and the first that
paginates: the design shows six service lines, a real billing month brings dozens,
and no built-in invoice preset flows today.

It is also the first bundle whose source carries a real company's identity, so this PR
is the pattern for the thirteen like it: the layout is ported in full, the identity
is not
.

What

invoice.presets.PaymentsInvoice on the existing StructuredInvoiceData model.
Split as the family is: PaymentsInvoice routes, PaymentsStyles holds the tokens,
PaymentsText the figure formatting, and Masthead / Parties / Lines /
Settlement / Closing / Widgets / Icons draw.

De-branded, surgically

The bundle's branding turned out to sit in its data and in exactly one asset:

  • brand-lockup.svg was the real Stripe wordmark (logos:stripe). Not ported. The
    preset reserves the design's measured 136 × 55.3 px lockup box and fills it from
    InvoiceBrand — a logo drawn to that height, or the brand's name as a wordmark. The
    templates artifact carries no mark of its own.
  • The other twelve glyphs are stock Material and Lucide icons; only their token names
    were product-flavoured (item-atlas, item-radar, item-terminal), so they are
    renamed to what they draw: globe, shield, mobile.
  • The legal entity, the product names and the note text were always data. The shipped
    sample is neutral.

It flows

The table's header repeats on every page it reaches, a continuation page reserves a
deeper bottom margin than page one, and every page carries its number — a financial
record that runs over has to make a missing page detectable.

One column, not five. The design shows an outer box and a rule between rows with no
interior verticals, which is exactly the topology a single-column table draws: a cell
strokes all four of its own edges and a one-column table has no interior vertical. The
five columns are a row inside each cell. Built the other way round — the box from a
stroked section wrapped round the table — every continuation page would end in an empty
bordered strip, because a section's box fills its page fragment rather than hugging its
rows.

The locale is stated, never inherited

Both the grouping of a number and the symbol for a currency code change with the locale
USD is $ in English and US$ in the root locale — so a preset that let the JVM
decide would render a different sheet on a different machine and its baselines would
move with the machine rather than with the design. PaymentsText pins Locale.ENGLISH.

Tests

  • Pixel parity against the bundle: 5 060 of 2 173 720 differing pixels, and the heat
    map has exactly one band
    x 63..198, y 46..102, which is the lockup box measured
    off the design (63..198, 46.8..101.2). The diagonal band, the title, the issuer
    block, the metadata grid and its divider, both parties, all six marked table rows,
    the payment card, the totals, the due panel, the notes, the footer rule and both
    footer cells are pixel-identical; 177 layout nodes against the bundle's 177. The
    only thing that differs is the mark deliberately not shipped, and it differs inside
    its own box without moving anything around it.
  • The bundle also freezes to 0 against its own published preview on both its pinned
    engine (2.2.2) and 2.2.3-SNAPSHOT, so there is no engine drift to account for.
  • PaymentsInvoiceSmokeTest — 15 cases: the packaged marks, the unknown-mark data
    error, a line with no mark, the wordmark fallback, the currency named once and
    carried, the uppercased due line, a trunk prefix printed but not dialled, the support
    contacts as annotations, a missing ship-to, a missing note block, an empty document,
    and a thirty-line invoice that runs on, repeats its column names on page two and
    numbers its pages.
  • PaymentsInvoiceLayoutSnapshotTest — exact geometry, one page.
  • PaymentsInvoiceVisualParityTest — pixel baseline; recorded without disturbing any
    other baseline in the suite.
  • Full reactor gate green:
    ./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 -am
  • The showcase gains invoice-payments-v2; assets/readme/examples/invoice-payments-v2.pdf
    is regenerated from the committed code.

A lavender-and-navy invoice: a diagonal band crossing the masthead, a
half-split issuer and metadata header, two addressed parties on discs, a
marked service-line table, a settlement row pairing bank details against
the totals, a note block and a two-cell document footer.

Ships as invoice.presets.PaymentsInvoice on the existing
StructuredInvoiceData model. It is the first preset in the family built
to flow: the table's header repeats on every page it reaches, a
continuation page reserves a deeper bottom margin than page one, and
every page carries its number.

The table is one column, not five. The design shows an outer box and a
rule between rows with no interior verticals, which is exactly what a
single-column table draws; the five columns are a row inside each cell.
The other construction would leave every continuation page ending in an
empty bordered strip, because a section's box fills its page fragment
rather than hugging its rows.

The lockup beside the title is the caller's — a logo drawn to the
design's measured height, or the brand's name as a wordmark. Figures are
written with the locale stated rather than inherited, because both the
grouping of a number and the symbol for a currency code change with it.

Guarded by a 15-case smoke test, an exact layout snapshot and a
pixel-parity gate.
…build

The committed preview was rendered against jars in the local repository
that did not match this branch, so the catalogue's drift test failed on
CI while passing locally — and the eleven files that appeared to drift
locally were the same staleness seen from the other side.

Built from source, the local drift list narrows to exactly the file CI
named. Re-rendered, both lists are empty.
@DemchaAV
DemchaAV merged commit 13e9473 into feature/template-promotion Sep 1, 2026
12 checks passed
@DemchaAV
DemchaAV deleted the feat/payments-invoice-preset branch September 1, 2026 07:09
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