{"id":"69afdf6cf8f0bf680691c49b","title":"2026 03 10","path":"how-to/committee/release-notes/2026-03-10","contentMarkdown":"# 10-Mar-2026 — map hang, GPX route parsing, mail API resilience, walk search layout\n\n## [build 515](https://github.com/nbarrett/ngx-ramblers/actions/runs/22894482875) — [commit 0cad242](https://github.com/nbarrett/ngx-ramblers/commit/0cad242aac84cb3cef60ff906d86c1a8304cddd0)\n\n_____\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","contentHtml":"<h1>10-Mar-2026 — map hang, GPX route parsing, mail API resilience, walk search layout</h1>\n<h2><a href=\"https://github.com/nbarrett/ngx-ramblers/actions/runs/22894482875\">build 515</a> — <a href=\"https://github.com/nbarrett/ngx-ramblers/commit/0cad242aac84cb3cef60ff906d86c1a8304cddd0\">commit 0cad242</a></h2>\n<hr>\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"}