# 13-Jun-2026 — per-event email overrides with ContentBlockEditor, merge field fixes, and walk pipeline... [#273](https://github.com/nbarrett/ngx-ramblers/issues/273)

## [build 691](https://github.com/nbarrett/ngx-ramblers/actions/runs/27463649147) — [commit cc5c688](https://github.com/nbarrett/ngx-ramblers/commit/cc5c688a5c83456b835c34908b6f9ee33d9f57b9)

_____

## What's new

### Booking email overrides now use ContentBlockEditor

- Rewrote the per-event email override section in the Bookings admin page to use
the same ContentBlockEditor component used by site-level email templates
- Removed the old markdown editor with override/revert buttons
- Added section-toggle tabs (Standard / Customise / Leave Out) matching the
site-level template editor experience
- Removed the disabled booking-default-preview showing the raw default text
- Preview toggle button redesigned with sunrise colour scheme
- ContentBlockEditor gained new inputs to support this reuse:
- `omitAllowed` — controls whether the "Leave out" tab appears
- `stateQueryParamKey` — persists the active tab choice in URL query params
- `frameless` — removes the heading, description, and accordion wrapper for
an inline editing mode
- `blockDefaults` — when a block is in DEFAULT state, renders the default
content in a disabled tiptap editor so the admin can see what would be sent
- MailNotificationTemplateEditor now merges booking block defaults from
DEFAULT_BOOKING_EMAIL_BLOCKS into the discovered defaults so the disabled
preview works for booking email types

### Booking merge fields preserved in markdown link URLs

- booking-template-resolver.ts now uses renderMarkdownPreservingTokens instead of
renderMarkdownToHtml when resolving booking email body content (default, site
override, and per-event override)
- Merge field placeholders like {{params.bookingMergeFields.EVENT_LINK}}
inside markdown link URLs are no longer percent-encoded during HTML rendering
- messages.ts template substitution now runs 3 passes (was 1) to resolve
parameters that might contain other parameter references
- Booking merge field values are now included in the test params for the
welcome-to-the-group template spec, confirming they resolve correctly

### Booking reminder email uses correct NGX domain link

- booking-reminder-job.ts passes null instead of event.groupEvent?.url for the
event link parameter, so publicEventLink() constructs the group's own domain
URL rather than the Ramblers national URL

### Walk description diff in publish status

- Added LCS-based word diff that compares the transformed website description
against the Ramblers-stored description in toPublishStatus()
- Words only in the Ramblers version are shown with red strikethrough
- Words only in the website version are shown in green
- Output is trusted HTML rendered via [innerHTML] so coloured spans display
- RamblersEventSummaryResponse interface and the list-events.ts mapper now
include the description field

### CSV export contraction expansion and character cleanup

- replaceSpecialCharacters now:
- Decodes HTML entities &#039; / &#39; back to apostrophes
- Expands 30+ common contractions via lookup map (don't -> do not, etc.)
- Strips apostrophes on unrecognised words (possessive forms)
- Converts & to and (avoids double-encoding by Ramblers CSV import)
- walkLeader() no longer calls replaceSpecialCharacters so names like
"O'Grady" preserve their apostrophe in the CSV export

### Styling: disabled tiptap editor state

- Added .tiptap-editor-shell-disabled and child classes for a visually distinct
disabled appearance
- Activated via [class.tiptap-editor-shell-disabled]="!editable" on the shell

### Code quality

- Removed unused BrevoButtonComponent imports from
mail-notification-template-editor.ts and notification-config-selector.ts
- Added EMAIL_STATE to the StoredValue enum for content block editor state
persistence

## Test changes

- Added booking merge field fixture (ATTENDEE_NAME, EVENT_TITLE, EVENT_DATE,
EVENT_LINK, ATTENDEE_LIST, PLACES_COUNT) to messages.spec.ts params
- Added spec confirming booking merge fields in markdown link URLs are
preserved during template rendering
- Updated resolveBookingBody spec to expect HTML rendering and preserved
merge field hrefs instead of raw DEFAULT_BOOKING_EMAIL_BLOCKS value
- template-diff.ts returns booking block defaults as raw trimmed markdown
(not HTML)

## Ramblers Walks Manager E2E

- DRY: combined disableCookieBannerPermanently, forceDismissCookieBanners,
and cookieBannerIfVisible into a single dismissCookieBanners() method
- Updated all call sites (ClickWhenReady, Login, Start, UploadWalks) to use
the combined method

## Further fixes

- Section-toggle tabs and Preview button now sit on the same row (removed
flex-wrap from the container)
- Preview now refreshes when switching email type tabs while preview is
active (made onEmailTypeTabChange async with await Promise.resolve()
so Angular change detection settles the view before calling preview)
- Batch-loaded events in loadEvents() using a single all() call with the
ids parameter instead of N+1 individual queryById() calls — cuts
group-event/all requests from N (one per unique event referenced in
bookings) to 1