Skip to content

feat(templates): add the EditorialProposal structured proposal preset - #612

Merged
DemchaAV merged 1 commit into
feature/template-promotionfrom
feat/editorial-proposal-preset
Aug 30, 2026
Merged

DemchaAV merged 1 commit into
feature/template-promotionfrom
feat/editorial-proposal-preset

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

Why

The structured proposal model landed with one consumer, NorthlineProposal. A model shaped around one document is a model nobody has tested — and the second look it was meant to carry existed only as a published standalone template pinned to graph-compose:2.2.0, with its assets on the filesystem.

What changed

  • proposal/presets/EditorialProposal — a DocumentTemplate<StructuredProposalDocumentSpec> via create(), split for the 500-LOC rule into package-private EditorialStyles (measured geometry, the Spectral/Lato scale), EditorialIcons, EditorialWidgets (masthead, drawn mark, heading, bullet, stacked-line box) and EditorialPageOne / EditorialPageTwo. It renders the same document as its sibling in a different hand: section headings set in the display serif over short accent rules rather than in the body sans inside icon badges, a brand mark drawn from four vector paths rather than set as a monogram letter, an untitled fact card, a scope ordinal in plain accent text, and a hairline page foot instead of a filled band.
  • The model needed no change — that is the result this PR reports. StructuredProposalData carried the second design as authored, including the parts the orange look does not use (section icon tokens, the fact-card heading, the monogram), because they normalize to their empty forms.
  • Moving a document between the two presets is a one-line change plus two data checks, now stated on the class and pinned by a test: badge and goal icon tokens are preset-scoped vocabulary while the four fact-* tokens are named alike in both packaged sets, and headings are drawn as authored, so the sibling's tracked capitals stay capitals here.
  • A goal cell takes the goal's own icon token and falls back to the band's — which is how this design marks every cell alike, since its goals name none. The ported original read only the band's token; reading the field the shared model defines for the job is what lets a document that names per-goal marks render.
  • Each icon node is named after its token, so a swapped token reaches the layout snapshot as a changed node path rather than only as moved pixels.
  • The five SVG icons ship inside the templates artifact under templates/proposal/editorial/icons/. package-info now describes both preset shapes the package holds — the narrative one on BrandTheme, and the structured one on preset-local tokens — which had drifted when the first structured preset landed.

Verification

Full reactor gate (the eight CI modules) → BUILD SUCCESS; proposal qa suite 27/27; examples suite 75/75 including CommittedAssetDriftTest with the new preview; javadoc:javadoc → 0 warnings.

Port parity, verified on one machine against the published template itself: the preset fed the template's own fixture is pixel-identical to the standalone render — 0 / 2,173,720 differing pixels on each of the two pages. The bundle's geometry is identical on its pinned 2.2.0 and on this branch (177/177 nodes, zero deltas > 0.01pt); the two engines differ only by the released ligature fix (#601), so the current-engine render is the parity target and the approved preview differs from it by that fix alone.

Notes

  • The two proposal presets share ~68% of their band codegoalColumns, wrappingCell, the money band, the phase grid (including a verbatim-copied header-count message), the investment row loop, the glance fact box. That duplication is the price of porting with parity; extracting the composition helpers into templates.core.* with style and geometry as parameters is the natural follow-up, and it should happen before a third structured preset lands rather than after.
  • An empty document draws the chrome its bands own — heading rules with no headings, an empty investment header and total band, a bare fact card. That is inherited from the family rather than introduced here; both presets behave the same way.

Lane: canonical (templates.proposal.presets + qa + examples) — no engine changes, no model changes.

The structured proposal model had one consumer. EditorialProposal is the
second, and it renders the same document in a different hand: an orange
accent, section headings set in the display serif over short accent rules
rather than in the body sans inside icon badges, a brand mark drawn from
vector paths rather than set as a monogram letter, an untitled fact card,
a scope ordinal in plain accent text, and a hairline page foot instead of
a filled band. It ports the rendered layout of the published standalone
northline-proposal-orange template and needed no change to the model -
which is what the model's second consumer was meant to prove.

The preset owns its session geometry and draws the brand line and page
numbers as footer chrome; it issues no explicit page break, because the
page-two masthead is keepWithNext. Its SVG icon set ships inside the
artifact, and each icon node is named after its token so a swapped token
reaches the layout snapshot rather than only the pixels. A goal cell
takes the goal's own icon token and falls back to the band's, which is
how this design marks every cell alike.

Moving a document between the two proposal presets is a one-line change
plus two data checks, both now stated in the class documentation and
pinned by a test: the badge and goal tokens are preset-scoped vocabulary
while the four fact tokens are shared, and headings are drawn as
authored.

qa: one fixtures class feeds the smoke, the exact two-page layout
snapshot and the pixel gate; the smoke drives full layout and render,
asserts the table content on the text layer, and names both data
contracts. Examples gain EditorialProposalV2Example
(proposal-editorial-v2) with its preview.
Comment on lines +120 to +121
row.verticalAlign(RowVerticalAlign.TOP)
.gap(SUMMARY_GAP)
double dividerHeight = GOAL_TEXT_LINES * lineHeight(TABLE_SIZE);
page.addRow("GoalCells", row -> {
row.margin(DocumentInsets.top(BAND_TOP));
row.verticalAlign(RowVerticalAlign.TOP).gap(GOAL_GAP);
Comment on lines +288 to +289
row.verticalAlign(RowVerticalAlign.TOP)
.gap(SCOPE_GAP)
Comment on lines +94 to +95
row.verticalAlign(RowVerticalAlign.TOP)
.gap(0)
Comment on lines +206 to +207
row.verticalAlign(RowVerticalAlign.TOP)
.gap(MONEY_GAP)
Comment on lines +337 to +338
row.verticalAlign(RowVerticalAlign.BOTTOM)
.gap(SIGNATURE_LABEL_GAP)
Comment on lines +74 to +75
row.verticalAlign(RowVerticalAlign.CENTER)
.gap(LOGO_GAP)
@DemchaAV
DemchaAV merged commit b278f11 into feature/template-promotion Aug 30, 2026
12 checks passed
@DemchaAV
DemchaAV deleted the feat/editorial-proposal-preset branch August 30, 2026 19:58
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.

2 participants