11-Jul-2026 — Resolvable walk links, upload attribution, email image hosts, walk export improvements #313
build 761 — commit d865a56
Walk links no longer 404 and edit no longer opens an empty walk
A walk with no stored url (an awaiting-leader slot, or a walk whose title had been cleared) still produced a title-or-date based slug for its links. That fabricated slug matched nothing on lookup, so clicking a walk title went to "Page not found", and the Edit button silently rendered a blank default walk dated today - closing which dropped the real walk from the in-memory programme until a refresh.
The slug logic was duplicated across six link builders (walkSlug, groupEventLink, event-contact, email-composer, committee-query, the walk-notification email footer), each fabricating slightly differently, so any single fix left the rest broken. Separately, clearing a title blanked the walk's url, and the copy-walk action never regenerated one.
- New single reader
eventSlug(event)infunctions/walks/event-slug.ts= storedurl, elsegroupEvent.id, elseid. It never fabricates a title slug: the clientkebabCaseand the serverconvertTitleToSlugdiffer (stop-words), so a client-fabricated slug cannot be relied on to match a walk's own storedurl. All six link builders now call it. - New single generator
WalksAndEventsService.urlFor(walk)= titled walks resolve through the backendurlFromTitleservice (stop-words stripped, uniqueness-Nsuffix); title-less slots get the dated slug. Copy-walk, walk title-change, group-event title-change, and a new save-time backfill all route through it, so a walk is never persisted without a resolvableurl. - Title-change no longer blanks the
urlwhen the title is emptied.
Existing walks with a missing url become reachable again by id and pick up a proper url the next time they are saved; no data migration was needed.
Ramblers walk upload reports who ran it
The Serenity walk-upload scenario hardcoded the actor to "Walks Admin", so the steps could not distinguish Nick from Kerry. The uploader's name already reached the metadata file but was dropped by the extractor and ignored by the scenario.
ramblersUseradded toWalkRequestParametersand the metadata extractor.- The scenario resolves the actor name from the metadata, falling back to "Walks Admin".
- The client sends the uploader's first name.
- The Ramblers login username always comes from system config, so the display name cannot leak into the login.
Welcome / new-joiner email images now load when sent
Notification image overrides are stored as relative paths (api/aws/s3/site-content/...jpeg). The batch send rewrote these to absolute URLs, but the generic transactional path - which sends the welcome email - passed the raw relative path to Brevo. In the in-app preview a relative src resolves against the browser page origin so it looked fine; a real email client has no base href, so the images failed (as seen in the new-joiner welcomes).
- Image-host normalisation moved into
renderBrandedTemplate, which every branded send and the preview go through, using theAPP_URLalready in params. - The now-redundant per-send normalisation was removed from the batch path.
- Regression tests added (relative override becomes absolute; absolute override unchanged). Server suite green at 844 passing.
Quieter cookie handling on the walks upload
The cookie-banner dismissal ran on every navigation, cluttering the upload report even when there was nothing to clear. It now runs only when a banner is actually present (a Check performs the clearing in its branch), so its dismissal steps appear only when there is something to dismiss. Fully hiding the check's own line (the IsHidden/IsSilent reporting markers) lives in serenity-js itself, on the feat/hidden-and-silent-activities fork branch, rather than as a patch here.
A single headless switch
Every Chromium launch (walks upload, site migration, integration worker) now goes through one resolveHeadless() / launchChromium() in server/lib/shared/playwright-browser.ts, with the default in a clearly-named DEFAULT_HEADLESS constant and overridable by PLAYWRIGHT_HEADLESS via the shared asBoolean() helper. The CLI --no-headless flag feeds the same one place.
Angular 21
Documentation updated to reflect the Angular 21 upgrade: AGENTS.md, README.md, codebase-evolution-stats.html and docs/contributing/agentic-development.md moved from Angular 20 to Angular 21.