05-Apr-2026 — release-notes: image detection, year headings, and path normalisation on index #219

build 578commit 3508742


release-notes: image detection, year headings, and path normalisation on index and preserve camera markers and never silently drop new notes (#219, #576, #213)

Three related bugs surfaced when auditing the release-notes-for-humans page after the #219 camera-marker work. All three produced drift on the detail index that accumulated silently with each build.

  1. Image detection missed everything except col.imageSource. The one-off fixup script's rowHasImage only recognised column-level imageSource plus nested col.rows. Release-notes pages author images in many shapes — markdown and in contentText, row.carousel with a linked album, row.albumIndex, col.icon. Anything outside the narrow shape was marked image-less, so the humans page's camera-filter dropped most entries. Moved detection into server/lib/release-notes/ image-detection.ts as a proper, tested module (23 new unit tests) and made it strict enough to reject the CMS editor's default-initialised "stub" carousel object (all-null fields attached to plain text rows) that was the source of false positives on e.g. 2025-11-10-issue-33.
  2. Leading-slash paths on the detail index were invisible to round-trip. Older entries were authored as ](https://ngx-ramblers.org.uk/how-to/committee/release-notes/…) and newer ones as ](https://ngx-ramblers.org.uk/how-to/committee/release-notes/…). The fixup scripts' regex required no leading slash, so 54 entries (including every 2025 entry with images, e.g. 2025-05-07) were silently skipped. parseIndexLine now strips a leading slash on parse, so every build normalises legacy paths without losing the entries.
  3. The detail index had no year grouping except a single stray "## 2026". Any year heading between entries was dropped by updateIndexPageContent on round-trip (only the preamble survived). Refactored the generator to emit "## " headings above each year's entries on every write, sorted newest-first. Preamble sanitisation strips any leftover year headings before re-emission. Also exported refreshIndexPageContent() so one-off recovery scripts can re-apply current formatting rules to the live page without merging a new entry. Test suite goes from 519 to 545 passing:

Two independent bugs in the automated release-notes workflow, fixed together.

  1. Camera 📸 markers stripped from the index on every rewrite. The entry line regex in content-generator.ts did not allow any trailing content after the markdown link's closing paren, so index entries with a trailing 📸 marker (used to flag release notes with embedded images) failed parseIndexLine, returned null, and were silently dropped from the re-serialised list on every build.
  1. New release notes silently dropped when the target page already exists. When a page already existed at the resolved path, generateReleaseNote would skip creation entirely (to preserve manual edits like embedded images) and do nothing else — the new commits had no release note anywhere, and the index was rewritten with an entry still pointing at the old, pre-edit page. Build #576 for commit 4b7e274f hit exactly this: three new #213 commits left undocumented.