{"id":"69b1f54c160f96869833696e","title":"2026 03 11","path":"how-to/committee/release-notes/2026-03-11","contentMarkdown":"# 11-Mar-2026 — Lightweight Booking System, Maps cleanup unused components, mail & walks fixes [#175](https://github.com/nbarrett/ngx-ramblers/issues/175)\n\n## [build 517](https://github.com/nbarrett/ngx-ramblers/actions/runs/22978121001) — [commit 6edae09](https://github.com/nbarrett/ngx-ramblers/commit/6edae09123092ead7878ccdb12c02afeba8e9e35)\n_____\n\nNote that during the development of this ticket, it transpired that there was a sizeable amount of clean up work that was required following issue [#12](https://github.com/nbarrett/ngx-ramblers/issues/12), as navigation from the walk summary → detail → edit and back was broken. The same issues were found with the same social event navigations. Several old components were deleted and the `social-event*` objects were renamed to `group-event*` in order to be more consistent with Ramblers data model.\n\n## Lightweight Booking System\nEvents can now accept bookings directly from any events page. When an event has `bookingEnabled: true` and a `maxCapacity` set on its fields, a **Book a Place** panel appears below the event details showing remaining capacity.\n### Admin — Booking (`/admin/bookings`)\nTo enable this feature, visit the above URL. A **bookings** button has also been added to the admin menu.\n\n\n![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/d60b2793-41a8-42b8-a0d6-5c03cfbc30b4.jpeg)\n\n- **Configuration tab**: Enable Booking on Events and choose which event types are enabled for bookings, default max capacity, max attendees per booking and member priority. \n- **Per-Event Detail** tab  is the event-level booking admin view which lets you:\n  - select an upcoming or existing bookable event\n  - see each booking for that event in a table\n  - view attendee names, email addresses, phone numbers, booking date, status, and places booked\n  - spot waitlisted bookings separately from active ones\n  - delete individual bookings if needed\n  - download the selected event’s booking data as CSV\n  -  It is also used to prepare events before bookings open:\n    - find an upcoming event from the selector\n    - set or update that event’s max booking capacity even if nobody has booked yet\n    - Select an event to see individual bookings with attendee\nnames, emails, phone numbers, and booking dates — also downloadable as CSV\n     - Admin can delete bookings directly from the detail view\n- **Summary tab**: table of all bookable events with booked/capacity counts\n\n### Booking flow\n1. Visitors enter their name and email (required) plus optional phone number\n1. Additional attendees can be added (up to `maxGroupSize`, default 3)\n1. Clicking **Book Now** creates a booking and confirms with a success message\n1. The capacity counter updates in real time — once all places are taken the\npanel shows **Fully booked**\n1. An email is sent to the visitor and optionally the BCC address specified within the email configuration for Bookings.\n\n![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/3c174bb5-16b7-4832-b042-bd9cf74be0b3.jpeg)\n\n![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/23125588-0eb2-4e87-8dc8-ac77937b294e.jpeg)\n\n\n### Cancellation flow\n1. After booking, a **Need to cancel a booking?** link appears\n1. The visitor enters their email to look up active bookings\n1. Each matching booking can be individually cancelled\n1. Cancellation is verified server-side: the email must match an attendee\non the booking, preventing unauthorised cancellations\n1. An email is sent to the visitor and optionally the BCC address specified within the email configuration for Bookings.\n\n![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/437f5b31-ba12-4bd6-a224-756ed3ed3e08.jpeg)\n\n![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/464a11d2-8786-42b5-ad6a-447720df04e2.jpeg)\n\n## Booking Follow-up Enhancements\n### Booking email notifications\n- Booking workflows can now send confirmation, cancellation, waitlisted, and\nrestored emails using the configured booking notification template\n- Booking emails include attendee lists, event dates, salutation, and a direct\nlink back to the website event page\n- The event page link is passed from the current walk/group event view so the\nemail uses the same deep link already exposed in the UI\n### Booking configuration defaults\n- Added booking settings to walks config with defaults for enabled state,\nmaximum capacity, maximum attendees per booking, and member priority days\n- Added the walks config admin tab to manage those defaults centrally\n- Updated the unreleased walks config migration to seed booking defaults and to\nmove notification configuration role copies from `ccRoles` to `bccRoles`\n### Member priority and waitlist handling\n- During member priority periods, authenticated member bookings can displace\nnon-member bookings onto the waiting list when an event is full\n- Waitlisted bookings are restored automatically when places are freed by a\ncancellation and restored attendees are notified by email\n- Eligibility responses now expose member priority state and waiting list totals\n### Booking safeguards and lookup fixes\n- Prevent duplicate active or waitlisted bookings on the same event by attendee\nemail address\n- Fixed booking lookup responses to return API objects with `id`, allowing\ncancellation to work reliably after lookup\n- Added explicit validation for missing booking ids on cancellation requests\n- Cancellation entry points are available whenever an event already has bookings\n### Booking email configuration and workflow alignment\n- Booking notification configurations now behave as workflow-managed email\nconfigs instead of generic mailing-list selections\n- Booking notifications now use BCC recipient roles consistently, with support\nfor migrated legacy `ccRoles` data during the transition\n- Booking emails now send through the shared transactional mail sender logic so\nrecipient handling, BCC behaviour, template substitution, and Brevo request\nconstruction are maintained in one place\n### Walk and event view integration\n- Booking panels now render correctly on standalone deep-link walk pages after\nwalks config has loaded\n- Walk and group event views pass their canonical website links into booking\nworkflows so correspondence uses the same event URLs shown in related links\n- Booking form layout was refined for stacked narrow layouts and single-row wide\nlayouts, with panel spacing aligned to surrounding event panels\n### Migration consistency\n- Corrected booking-related config migrations to use the `config` collection,\nmatching the live mongoose model and runtime code\n### Global booking configuration and admin controls\n- Booking configuration is now stored in its own global `booking` config rather\nthan under `walks`, with environment setup and the unreleased migration moved\nto seed and migrate the new config cleanly before release\n- `/admin/bookings` now owns booking configuration through a dedicated\n**Configuration** tab instead of the walks config area\n- Booking configuration now supports per-event-type enablement so booking can\nbe turned on for walks, group events, and wellbeing walks independently\n### Per-event booking preparation\n- Booking admins can now search upcoming events from the existing autocomplete\npattern without typing before results appear\n- Upcoming event choices now include event type in the label for clarity\n- Admins can set event-specific maximum capacity before any bookings exist,\nallowing events to be prepared ahead of opening booking\n### Booking UX refinements\n- Cancellation lookup guidance now uses the same alert treatment as the rest of\nthe booking panel\n- Booking success and waiting-list messaging now use shared pluralisation\nhelpers for consistent wording\n- Group event and walk views now own their own booking panel spacing so page-\nspecific layout tuning no longer leaks through the shared booking component\n\n## GPX Parser: Support <rte> Route Elements\n- **Root cause**: GPX parser only handled `<trk>/<trkpt>` (track)\nelements but ignored `<rte>/<rtept>` (route) elements. 37 of 39\nSevenoaks walking route GPX files use `<rte>` format, so only 2\nroutes were rendering on the map\n- **Fix**: added `parseRoute()` and `parseRoutePoints()` methods to\nconvert `<rte>/<rtept>` elements into renderable `GpxTrack` objects\nalongside existing `<trk>` parsing\n- **Waypoint dedup**: changed `parseWaypoints()` from selecting\n`\"wpt, rtept\"` to just `\"wpt\"` — `<rtept>` elements are now parsed\nas route tracks, not waypoints, preventing duplicate rendering\n## Map Viewport Hang Fix\n- **Root cause**: Dragging/zooming the map triggered an infinite feedback\nloop — `moveend` → `loadRoutes()` → layer reassignment + `fitBounds`\n→ Leaflet re-render → `moveend` again\n- **Debounced viewport handler**: 300ms debounce on `moveend`/`zoomend`\nprevents rapid-fire reloads during panning\n- **Re-entrancy guard**: `loadRoutesInProgress` flag skips concurrent\n`loadRoutes()` calls\n- **Suppress programmatic events**: `suppressViewportHandler` flag\nprevents layer/bounds assignment from re-triggering the viewport\nhandler (cleared after 200ms)\n- **Skip fitBounds on viewport reloads**: `loadRoutes(skipFitBounds)`\nparameter prevents `calculateFitBounds` on user-initiated pan/zoom,\nwhich was the primary loop trigger\n- **Removed cache eviction on viewport change**: the viewport filtering\nalready uses `currentBounds.intersects()` on cached data, so deleting\nand re-fetching GPX on every drag was unnecessary\n## Mail API Resilience (No API Key / Invalid Key)\n- **Short-circuit when no API key**: `MailMessagingService.initialise()`\nchecks `mailConfig.apiKey` after loading config — if absent, skips all\nBrevo API calls (account, lists, folders, templates) and sets safe\nempty defaults. Zero 401 errors in the console\n- **Short-circuit on account error**: if the account query fails (invalid\nkey), remaining Brevo calls are skipped with empty defaults\n- **Senders list guard**: `MailSendersListComponent` waits for the\n`mailMessagingConfig` via the `events()` observable before calling\nsenders/domain APIs — skips if `accountError` is set\n- **Create-or-amend sender guard**: checks `brevoAccountConfigured()`\nbefore calling `querySenders()`\n- **Mail provider stats guard**: `calculateMailProviderStats()` handles\n`this.list` being undefined when Brevo lists fail to load, showing\n\"No Brevo lists available\" instead of crashing\n- **List key values null safety**: added `?.` and `|| []` fallback when\n`brevo.lists.lists` is undefined\n- **Alert icon**: changed error alert icon from `faCircleCheck` to\n`faExclamationTriangle` for the account error panel\n## CommonDataService Double HTTP Request Fix\n- **Root cause**: `responseFrom()` used `.subscribe()` + `.toPromise()`\non a shared observable, causing two subscriptions and duplicate HTTP\nrequests\n- **Fix**: replaced with single `firstValueFrom()` call — one\nsubscription, one HTTP request, proper error propagation via `throw`\n## Config Save: Allow Clearing Sensitive Fields\n- **Root cause**: `restoreSensitiveFields()` in the backend config\ncontroller treated both missing keys AND falsy values (`\"\"`, `null`)\nas \"restore from DB\", making it impossible to clear the API key\n- **Fix**: changed condition from `!(k in incoming) || !incoming[k]` to\n`!(k in incoming)` — only restores when the key is truly absent\n(redacted), not when explicitly cleared\n## Mail Settings: Prevent Config Overwrite During Editing\n- **Root cause**: `MailMessagingService.initialise()` created new\nsubscriptions to `committeeConfig` and `systemConfigService` on every\ncall, causing accumulated subscriptions that re-emitted config and\noverwrote unsaved edits\n- **Fix**: moved one-time subscriptions to the constructor;\n`acceptNextConfigEmission` flag in `MailSettingsComponent` prevents\nsubscription emissions from overwriting local edits except after\nsave or undo\n## Domain API URL Refactor\n- Changed domain endpoints from path parameters\n(`/domains/:domainName/configuration`) to query parameters\n(`/domains/configuration?domainName=...`) across backend routes and\nfrontend service\n## Walk Search Layout\n- Alert inline when default filtering, own row with pagination/filters\n- Clear filters resets to default preset\n- Bug button hidden by default\n## Events\n- Bug button visibility fix\n\n### Server API for #175\n- `POST /api/database/booking` — create booking\n- `PUT /api/database/booking/:id` — update booking\n- `DELETE /api/database/booking/:id` — delete booking\n- `GET /api/database/booking/all` — list bookings (with query criteria)\n- `GET /api/database/booking/capacity/:eventId` — public capacity check\n- `POST /api/database/booking/lookup` — find bookings by event + email\n- `PUT /api/database/booking/cancel/:id` — cancel with email verification\n## Contact Us Persistence\nContact form submissions are now persisted to the database via\n`ContactInteractionService` and a new `/api/database/contact-interaction`\nendpoint. Each interaction records: name, email, subject, message, whether\nthe sender was anonymous, the recipient role, and a status\n(new/read/archived).\n## Social → Group Events Rename\nAll social event components, services, models, and routes have been renamed\nfrom `social-*` to `group-event-*` to align with the Ramblers API naming\nconvention. Key renames:\n- `SocialEditComponent` → `GroupEventEdit`\n- `SocialDisplayService` → `GroupEventDisplayService`\n- `SocialViewPage` → `GroupEventViewPage`\n- `SocialCard` → `GroupEventCard`\n- `social-events.model.ts` → `group-events.model.ts`\n- `social-auth-guard.ts` → `group-event-auth-guard.ts`\n## Removed Orphaned Code\nDeleted components and services no longer referenced anywhere:\n- `WalkList` (723 lines) — replaced by `EventsFull` CMS component\n- `GroupEventList` + sass\n- `GroupEventViewSelector`\n- `SocialViewSelector`\n- `SocialList` + sass\n- `SocialRoutingModule`\n- `SocialPopulationLocalGuard`\n- Hardcoded `PAGE_HEADER` and `ACTION_BUTTONS` anchors — walks and social\npages are now 100% content-managed\n## Event Navigation Fixes\n### Deep path dispatch (fixes wrong event for generic slugs)\nFor paths with 3+ segments (e.g. `/walks/weekends-away/swanage/day-2`),\npage content is now checked **before** event lookup. This prevents generic\nslugs like `day-2` from regex-matching unrelated events. Two-segment paths\n(e.g. `/walks/some-event-slug`) still try event lookup first.\n### Walk card title links (fixes pre-existing non-navigation)\n- `walkRouterLink()` now generates relative URLs so `routerLinkUrl()` returns\na valid path instead of null (absolute URLs were rejected as \"remote\")\n- Added `stopPropagation` on the title `<a>` to prevent the parent card's\n`toggleView()` handler from intercepting the routerLink click\n### Edit/cancel navigation symmetry\n- `close()` strips `/edit` from the current path instead of reconstructing\nthe URL via `groupEventLink()`, which was reading the still-dirty URL\n- Added explicit `edit` and `new` routes to `walks-routing.module.ts` before\nthe `**` catch-all to prevent `/edit/edit/edit` URL corruption\n### Cleanup\n- `walksArea()` simplified to `urlService.area()` — removed stale fallbacks\nto `pageService.walksPage()?.href` and hardcoded `\"walks\"`\n","contentHtml":"<h1>11-Mar-2026 — Lightweight Booking System, Maps cleanup unused components, mail &amp; walks fixes <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/175\">#175</a></h1>\n<h2><a href=\"https://github.com/nbarrett/ngx-ramblers/actions/runs/22978121001\">build 517</a> — <a href=\"https://github.com/nbarrett/ngx-ramblers/commit/6edae09123092ead7878ccdb12c02afeba8e9e35\">commit 6edae09</a></h2>\n<hr>\n<p>Note that during the development of this ticket, it transpired that there was a sizeable amount of clean up work that was required following issue <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/12\">#12</a>, as navigation from the walk summary → detail → edit and back was broken. The same issues were found with the same social event navigations. Several old components were deleted and the <code>social-event*</code> objects were renamed to <code>group-event*</code> in order to be more consistent with Ramblers data model.</p>\n<h2>Lightweight Booking System</h2>\n<p>Events can now accept bookings directly from any events page. When an event has <code>bookingEnabled: true</code> and a <code>maxCapacity</code> set on its fields, a <strong>Book a Place</strong> panel appears below the event details showing remaining capacity.</p>\n<h3>Admin — Booking (<code>/admin/bookings</code>)</h3>\n<p>To enable this feature, visit the above URL. A <strong>bookings</strong> button has also been added to the admin menu.</p>\n<p><img src=\"https://ngx-ramblers.org.uk/api/aws/s3/site-content/d60b2793-41a8-42b8-a0d6-5c03cfbc30b4.jpeg\" alt=\"\"></p>\n<ul>\n<li><strong>Configuration tab</strong>: Enable Booking on Events and choose which event types are enabled for bookings, default max capacity, max attendees per booking and member priority. </li>\n<li><strong>Per-Event Detail</strong> tab  is the event-level booking admin view which lets you:<ul>\n<li>select an upcoming or existing bookable event</li>\n<li>see each booking for that event in a table</li>\n<li>view attendee names, email addresses, phone numbers, booking date, status, and places booked</li>\n<li>spot waitlisted bookings separately from active ones</li>\n<li>delete individual bookings if needed</li>\n<li>download the selected event’s booking data as CSV</li>\n<li>It is also used to prepare events before bookings open:</li>\n<li>find an upcoming event from the selector</li>\n<li>set or update that event’s max booking capacity even if nobody has booked yet</li>\n<li>Select an event to see individual bookings with attendee\nnames, emails, phone numbers, and booking dates — also downloadable as CSV</li>\n<li>Admin can delete bookings directly from the detail view</li>\n</ul>\n</li>\n<li><strong>Summary tab</strong>: table of all bookable events with booked/capacity counts</li>\n</ul>\n<h3>Booking flow</h3>\n<ol>\n<li>Visitors enter their name and email (required) plus optional phone number</li>\n<li>Additional attendees can be added (up to <code>maxGroupSize</code>, default 3)</li>\n<li>Clicking <strong>Book Now</strong> creates a booking and confirms with a success message</li>\n<li>The capacity counter updates in real time — once all places are taken the\npanel shows <strong>Fully booked</strong></li>\n<li>An email is sent to the visitor and optionally the BCC address specified within the email configuration for Bookings.</li>\n</ol>\n<p><img src=\"https://ngx-ramblers.org.uk/api/aws/s3/site-content/3c174bb5-16b7-4832-b042-bd9cf74be0b3.jpeg\" alt=\"\"></p>\n<p><img src=\"https://ngx-ramblers.org.uk/api/aws/s3/site-content/23125588-0eb2-4e87-8dc8-ac77937b294e.jpeg\" alt=\"\"></p>\n<h3>Cancellation flow</h3>\n<ol>\n<li>After booking, a <strong>Need to cancel a booking?</strong> link appears</li>\n<li>The visitor enters their email to look up active bookings</li>\n<li>Each matching booking can be individually cancelled</li>\n<li>Cancellation is verified server-side: the email must match an attendee\non the booking, preventing unauthorised cancellations</li>\n<li>An email is sent to the visitor and optionally the BCC address specified within the email configuration for Bookings.</li>\n</ol>\n<p><img src=\"https://ngx-ramblers.org.uk/api/aws/s3/site-content/437f5b31-ba12-4bd6-a224-756ed3ed3e08.jpeg\" alt=\"\"></p>\n<p><img src=\"https://ngx-ramblers.org.uk/api/aws/s3/site-content/464a11d2-8786-42b5-ad6a-447720df04e2.jpeg\" alt=\"\"></p>\n<h2>Booking Follow-up Enhancements</h2>\n<h3>Booking email notifications</h3>\n<ul>\n<li>Booking workflows can now send confirmation, cancellation, waitlisted, and\nrestored emails using the configured booking notification template</li>\n<li>Booking emails include attendee lists, event dates, salutation, and a direct\nlink back to the website event page</li>\n<li>The event page link is passed from the current walk/group event view so the\nemail uses the same deep link already exposed in the UI</li>\n</ul>\n<h3>Booking configuration defaults</h3>\n<ul>\n<li>Added booking settings to walks config with defaults for enabled state,\nmaximum capacity, maximum attendees per booking, and member priority days</li>\n<li>Added the walks config admin tab to manage those defaults centrally</li>\n<li>Updated the unreleased walks config migration to seed booking defaults and to\nmove notification configuration role copies from <code>ccRoles</code> to <code>bccRoles</code></li>\n</ul>\n<h3>Member priority and waitlist handling</h3>\n<ul>\n<li>During member priority periods, authenticated member bookings can displace\nnon-member bookings onto the waiting list when an event is full</li>\n<li>Waitlisted bookings are restored automatically when places are freed by a\ncancellation and restored attendees are notified by email</li>\n<li>Eligibility responses now expose member priority state and waiting list totals</li>\n</ul>\n<h3>Booking safeguards and lookup fixes</h3>\n<ul>\n<li>Prevent duplicate active or waitlisted bookings on the same event by attendee\nemail address</li>\n<li>Fixed booking lookup responses to return API objects with <code>id</code>, allowing\ncancellation to work reliably after lookup</li>\n<li>Added explicit validation for missing booking ids on cancellation requests</li>\n<li>Cancellation entry points are available whenever an event already has bookings</li>\n</ul>\n<h3>Booking email configuration and workflow alignment</h3>\n<ul>\n<li>Booking notification configurations now behave as workflow-managed email\nconfigs instead of generic mailing-list selections</li>\n<li>Booking notifications now use BCC recipient roles consistently, with support\nfor migrated legacy <code>ccRoles</code> data during the transition</li>\n<li>Booking emails now send through the shared transactional mail sender logic so\nrecipient handling, BCC behaviour, template substitution, and Brevo request\nconstruction are maintained in one place</li>\n</ul>\n<h3>Walk and event view integration</h3>\n<ul>\n<li>Booking panels now render correctly on standalone deep-link walk pages after\nwalks config has loaded</li>\n<li>Walk and group event views pass their canonical website links into booking\nworkflows so correspondence uses the same event URLs shown in related links</li>\n<li>Booking form layout was refined for stacked narrow layouts and single-row wide\nlayouts, with panel spacing aligned to surrounding event panels</li>\n</ul>\n<h3>Migration consistency</h3>\n<ul>\n<li>Corrected booking-related config migrations to use the <code>config</code> collection,\nmatching the live mongoose model and runtime code</li>\n</ul>\n<h3>Global booking configuration and admin controls</h3>\n<ul>\n<li>Booking configuration is now stored in its own global <code>booking</code> config rather\nthan under <code>walks</code>, with environment setup and the unreleased migration moved\nto seed and migrate the new config cleanly before release</li>\n<li><code>/admin/bookings</code> now owns booking configuration through a dedicated\n<strong>Configuration</strong> tab instead of the walks config area</li>\n<li>Booking configuration now supports per-event-type enablement so booking can\nbe turned on for walks, group events, and wellbeing walks independently</li>\n</ul>\n<h3>Per-event booking preparation</h3>\n<ul>\n<li>Booking admins can now search upcoming events from the existing autocomplete\npattern without typing before results appear</li>\n<li>Upcoming event choices now include event type in the label for clarity</li>\n<li>Admins can set event-specific maximum capacity before any bookings exist,\nallowing events to be prepared ahead of opening booking</li>\n</ul>\n<h3>Booking UX refinements</h3>\n<ul>\n<li>Cancellation lookup guidance now uses the same alert treatment as the rest of\nthe booking panel</li>\n<li>Booking success and waiting-list messaging now use shared pluralisation\nhelpers for consistent wording</li>\n<li>Group event and walk views now own their own booking panel spacing so page-\nspecific layout tuning no longer leaks through the shared booking component</li>\n</ul>\n<h2>GPX Parser: Support <rte> Route Elements</h2>\n<ul>\n<li><strong>Root cause</strong>: GPX parser only handled <code>&lt;trk&gt;/&lt;trkpt&gt;</code> (track)\nelements but ignored <code>&lt;rte&gt;/&lt;rtept&gt;</code> (route) elements. 37 of 39\nSevenoaks walking route GPX files use <code>&lt;rte&gt;</code> format, so only 2\nroutes were rendering on the map</li>\n<li><strong>Fix</strong>: added <code>parseRoute()</code> and <code>parseRoutePoints()</code> methods to\nconvert <code>&lt;rte&gt;/&lt;rtept&gt;</code> elements into renderable <code>GpxTrack</code> objects\nalongside existing <code>&lt;trk&gt;</code> parsing</li>\n<li><strong>Waypoint dedup</strong>: changed <code>parseWaypoints()</code> from selecting\n<code>&quot;wpt, rtept&quot;</code> to just <code>&quot;wpt&quot;</code> — <code>&lt;rtept&gt;</code> elements are now parsed\nas route tracks, not waypoints, preventing duplicate rendering</li>\n</ul>\n<h2>Map Viewport Hang Fix</h2>\n<ul>\n<li><strong>Root cause</strong>: Dragging/zooming the map triggered an infinite feedback\nloop — <code>moveend</code> → <code>loadRoutes()</code> → layer reassignment + <code>fitBounds</code>\n→ Leaflet re-render → <code>moveend</code> again</li>\n<li><strong>Debounced viewport handler</strong>: 300ms debounce on <code>moveend</code>/<code>zoomend</code>\nprevents rapid-fire reloads during panning</li>\n<li><strong>Re-entrancy guard</strong>: <code>loadRoutesInProgress</code> flag skips concurrent\n<code>loadRoutes()</code> calls</li>\n<li><strong>Suppress programmatic events</strong>: <code>suppressViewportHandler</code> flag\nprevents layer/bounds assignment from re-triggering the viewport\nhandler (cleared after 200ms)</li>\n<li><strong>Skip fitBounds on viewport reloads</strong>: <code>loadRoutes(skipFitBounds)</code>\nparameter prevents <code>calculateFitBounds</code> on user-initiated pan/zoom,\nwhich was the primary loop trigger</li>\n<li><strong>Removed cache eviction on viewport change</strong>: the viewport filtering\nalready uses <code>currentBounds.intersects()</code> on cached data, so deleting\nand re-fetching GPX on every drag was unnecessary</li>\n</ul>\n<h2>Mail API Resilience (No API Key / Invalid Key)</h2>\n<ul>\n<li><strong>Short-circuit when no API key</strong>: <code>MailMessagingService.initialise()</code>\nchecks <code>mailConfig.apiKey</code> after loading config — if absent, skips all\nBrevo API calls (account, lists, folders, templates) and sets safe\nempty defaults. Zero 401 errors in the console</li>\n<li><strong>Short-circuit on account error</strong>: if the account query fails (invalid\nkey), remaining Brevo calls are skipped with empty defaults</li>\n<li><strong>Senders list guard</strong>: <code>MailSendersListComponent</code> waits for the\n<code>mailMessagingConfig</code> via the <code>events()</code> observable before calling\nsenders/domain APIs — skips if <code>accountError</code> is set</li>\n<li><strong>Create-or-amend sender guard</strong>: checks <code>brevoAccountConfigured()</code>\nbefore calling <code>querySenders()</code></li>\n<li><strong>Mail provider stats guard</strong>: <code>calculateMailProviderStats()</code> handles\n<code>this.list</code> being undefined when Brevo lists fail to load, showing\n&quot;No Brevo lists available&quot; instead of crashing</li>\n<li><strong>List key values null safety</strong>: added <code>?.</code> and <code>|| []</code> fallback when\n<code>brevo.lists.lists</code> is undefined</li>\n<li><strong>Alert icon</strong>: changed error alert icon from <code>faCircleCheck</code> to\n<code>faExclamationTriangle</code> for the account error panel</li>\n</ul>\n<h2>CommonDataService Double HTTP Request Fix</h2>\n<ul>\n<li><strong>Root cause</strong>: <code>responseFrom()</code> used <code>.subscribe()</code> + <code>.toPromise()</code>\non a shared observable, causing two subscriptions and duplicate HTTP\nrequests</li>\n<li><strong>Fix</strong>: replaced with single <code>firstValueFrom()</code> call — one\nsubscription, one HTTP request, proper error propagation via <code>throw</code></li>\n</ul>\n<h2>Config Save: Allow Clearing Sensitive Fields</h2>\n<ul>\n<li><strong>Root cause</strong>: <code>restoreSensitiveFields()</code> in the backend config\ncontroller treated both missing keys AND falsy values (<code>&quot;&quot;</code>, <code>null</code>)\nas &quot;restore from DB&quot;, making it impossible to clear the API key</li>\n<li><strong>Fix</strong>: changed condition from <code>!(k in incoming) || !incoming[k]</code> to\n<code>!(k in incoming)</code> — only restores when the key is truly absent\n(redacted), not when explicitly cleared</li>\n</ul>\n<h2>Mail Settings: Prevent Config Overwrite During Editing</h2>\n<ul>\n<li><strong>Root cause</strong>: <code>MailMessagingService.initialise()</code> created new\nsubscriptions to <code>committeeConfig</code> and <code>systemConfigService</code> on every\ncall, causing accumulated subscriptions that re-emitted config and\noverwrote unsaved edits</li>\n<li><strong>Fix</strong>: moved one-time subscriptions to the constructor;\n<code>acceptNextConfigEmission</code> flag in <code>MailSettingsComponent</code> prevents\nsubscription emissions from overwriting local edits except after\nsave or undo</li>\n</ul>\n<h2>Domain API URL Refactor</h2>\n<ul>\n<li>Changed domain endpoints from path parameters\n(<code>/domains/:domainName/configuration</code>) to query parameters\n(<code>/domains/configuration?domainName=...</code>) across backend routes and\nfrontend service</li>\n</ul>\n<h2>Walk Search Layout</h2>\n<ul>\n<li>Alert inline when default filtering, own row with pagination/filters</li>\n<li>Clear filters resets to default preset</li>\n<li>Bug button hidden by default</li>\n</ul>\n<h2>Events</h2>\n<ul>\n<li>Bug button visibility fix</li>\n</ul>\n<h3>Server API for #175</h3>\n<ul>\n<li><code>POST /api/database/booking</code> — create booking</li>\n<li><code>PUT /api/database/booking/:id</code> — update booking</li>\n<li><code>DELETE /api/database/booking/:id</code> — delete booking</li>\n<li><code>GET /api/database/booking/all</code> — list bookings (with query criteria)</li>\n<li><code>GET /api/database/booking/capacity/:eventId</code> — public capacity check</li>\n<li><code>POST /api/database/booking/lookup</code> — find bookings by event + email</li>\n<li><code>PUT /api/database/booking/cancel/:id</code> — cancel with email verification</li>\n</ul>\n<h2>Contact Us Persistence</h2>\n<p>Contact form submissions are now persisted to the database via\n<code>ContactInteractionService</code> and a new <code>/api/database/contact-interaction</code>\nendpoint. Each interaction records: name, email, subject, message, whether\nthe sender was anonymous, the recipient role, and a status\n(new/read/archived).</p>\n<h2>Social → Group Events Rename</h2>\n<p>All social event components, services, models, and routes have been renamed\nfrom <code>social-*</code> to <code>group-event-*</code> to align with the Ramblers API naming\nconvention. Key renames:</p>\n<ul>\n<li><code>SocialEditComponent</code> → <code>GroupEventEdit</code></li>\n<li><code>SocialDisplayService</code> → <code>GroupEventDisplayService</code></li>\n<li><code>SocialViewPage</code> → <code>GroupEventViewPage</code></li>\n<li><code>SocialCard</code> → <code>GroupEventCard</code></li>\n<li><code>social-events.model.ts</code> → <code>group-events.model.ts</code></li>\n<li><code>social-auth-guard.ts</code> → <code>group-event-auth-guard.ts</code></li>\n</ul>\n<h2>Removed Orphaned Code</h2>\n<p>Deleted components and services no longer referenced anywhere:</p>\n<ul>\n<li><code>WalkList</code> (723 lines) — replaced by <code>EventsFull</code> CMS component</li>\n<li><code>GroupEventList</code> + sass</li>\n<li><code>GroupEventViewSelector</code></li>\n<li><code>SocialViewSelector</code></li>\n<li><code>SocialList</code> + sass</li>\n<li><code>SocialRoutingModule</code></li>\n<li><code>SocialPopulationLocalGuard</code></li>\n<li>Hardcoded <code>PAGE_HEADER</code> and <code>ACTION_BUTTONS</code> anchors — walks and social\npages are now 100% content-managed</li>\n</ul>\n<h2>Event Navigation Fixes</h2>\n<h3>Deep path dispatch (fixes wrong event for generic slugs)</h3>\n<p>For paths with 3+ segments (e.g. <code>/walks/weekends-away/swanage/day-2</code>),\npage content is now checked <strong>before</strong> event lookup. This prevents generic\nslugs like <code>day-2</code> from regex-matching unrelated events. Two-segment paths\n(e.g. <code>/walks/some-event-slug</code>) still try event lookup first.</p>\n<h3>Walk card title links (fixes pre-existing non-navigation)</h3>\n<ul>\n<li><code>walkRouterLink()</code> now generates relative URLs so <code>routerLinkUrl()</code> returns\na valid path instead of null (absolute URLs were rejected as &quot;remote&quot;)</li>\n<li>Added <code>stopPropagation</code> on the title <code>&lt;a&gt;</code> to prevent the parent card&#39;s\n<code>toggleView()</code> handler from intercepting the routerLink click</li>\n</ul>\n<h3>Edit/cancel navigation symmetry</h3>\n<ul>\n<li><code>close()</code> strips <code>/edit</code> from the current path instead of reconstructing\nthe URL via <code>groupEventLink()</code>, which was reading the still-dirty URL</li>\n<li>Added explicit <code>edit</code> and <code>new</code> routes to <code>walks-routing.module.ts</code> before\nthe <code>**</code> catch-all to prevent <code>/edit/edit/edit</code> URL corruption</li>\n</ul>\n<h3>Cleanup</h3>\n<ul>\n<li><code>walksArea()</code> simplified to <code>urlService.area()</code> — removed stale fallbacks\nto <code>pageService.walksPage()?.href</code> and hardcoded <code>&quot;walks&quot;</code></li>\n</ul>\n"}