{"id":"69d2c6f0bebdf562b66db8e4","title":"2026 04 05 Issue 219","path":"how-to/committee/release-notes/2026-04-05-issue-219","contentMarkdown":"# 05-Apr-2026 — release-notes: image detection, year headings, and path normalisation on index [#219](https://github.com/nbarrett/ngx-ramblers/issues/219)\n\n## [build 578](https://github.com/nbarrett/ngx-ramblers/actions/runs/24009695966) — [commit 3508742](https://github.com/nbarrett/ngx-ramblers/commit/35087421ad7bfadc62b37b9eb134be41b60fe69d)\n\n_____\n\n### **release-notes**: image detection, year headings, and path normalisation on index and preserve camera markers and never silently drop new notes ([#219](https://github.com/nbarrett/ngx-ramblers/issues/219), [#576](https://github.com/nbarrett/ngx-ramblers/issues/576), [#213](https://github.com/nbarrett/ngx-ramblers/issues/213))\n\nThree related bugs surfaced when auditing the release-notes-for-humans page\nafter the #219 camera-marker work. All three produced drift on the detail\nindex that accumulated silently with each build.\n1. Image detection missed everything except col.imageSource.\nThe one-off fixup script's rowHasImage only recognised column-level\nimageSource plus nested col.rows. Release-notes pages author images in\nmany shapes — markdown ![](https://ngx-ramblers.org.uk/…) and <img> in contentText, row.carousel\nwith a linked album, row.albumIndex, col.icon. Anything outside the\nnarrow shape was marked image-less, so the humans page's camera-filter\ndropped most entries. Moved detection into server/lib/release-notes/\nimage-detection.ts as a proper, tested module (23 new unit tests) and\nmade it strict enough to reject the CMS editor's default-initialised\n\"stub\" carousel object (all-null fields attached to plain text rows)\nthat was the source of false positives on e.g. 2025-11-10-issue-33.\n2. Leading-slash paths on the detail index were invisible to round-trip.\nOlder entries were authored as ](https://ngx-ramblers.org.uk/how-to/committee/release-notes/…)\nand newer ones as ](https://ngx-ramblers.org.uk/how-to/committee/release-notes/…). The fixup\nscripts' regex required no leading slash, so 54 entries (including\nevery 2025 entry with images, e.g. 2025-05-07) were silently skipped.\nparseIndexLine now strips a leading slash on parse, so every build\nnormalises legacy paths without losing the entries.\n3. The detail index had no year grouping except a single stray \"## 2026\".\nAny year heading between entries was dropped by updateIndexPageContent\non round-trip (only the preamble survived). Refactored the generator\nto emit \"## <year>\" headings above each year's entries on every write,\nsorted newest-first. Preamble sanitisation strips any leftover year\nheadings before re-emission. Also exported refreshIndexPageContent()\nso one-off recovery scripts can re-apply current formatting rules to\nthe live page without merging a new entry.\nTest suite goes from 519 to 545 passing:\n- 23 new tests in image-detection.spec.ts covering stub carousel rejection,\nmarkdown/HTML image detection, nested rows, and the specific 2025-11-10\nfalse positive and 2025-01-17 true positive shapes\n- 3 new tests in content-generator.spec.ts covering year heading emission,\nleading-slash normalisation on round-trip, and refreshIndexPageContent\n\nTwo independent bugs in the automated release-notes workflow, fixed together.\n1. Camera 📸 markers stripped from the index on every rewrite.\nThe entry line regex in content-generator.ts did not allow any trailing\ncontent after the markdown link's closing paren, so index entries with a\ntrailing 📸 marker (used to flag release notes with embedded images)\nfailed parseIndexLine, returned null, and were silently dropped from the\nre-serialised list on every build.\n- Extend entryLineRegex to capture an optional trailing \" 📸\"\n- Add hasCamera to IndexEntry; parseIndexLine records it and\nformatIndexLine re-emits it\n- updateIndexPageContent preserves the existing hasCamera when\nre-adding an entry at the same key\n- New regression tests in content-generator.spec.ts\n2. New release notes silently dropped when the target page already exists.\nWhen a page already existed at the resolved path, generateReleaseNote\nwould skip creation entirely (to preserve manual edits like embedded\nimages) and do nothing else — the new commits had no release note\nanywhere, and the index was rewritten with an entry still pointing at\nthe old, pre-edit page. Build #576 for commit 4b7e274f hit exactly\nthis: three new #213 commits left undocumented.\n- When a collision is detected, write the new note at a distinct\nbuild-specific path suffix (-build-<buildNumber>, or -commit-<short>\nwhen no build number is available)\n- Collision-proof the suffix with a recursive counter fallback\n- Always create — never silently skip\n- Skip removeLegacyReleasePages when we wrote to a suffixed path","contentHtml":"<h1>05-Apr-2026 — release-notes: image detection, year headings, and path normalisation on index <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/219\">#219</a></h1>\n<h2><a href=\"https://github.com/nbarrett/ngx-ramblers/actions/runs/24009695966\">build 578</a> — <a href=\"https://github.com/nbarrett/ngx-ramblers/commit/35087421ad7bfadc62b37b9eb134be41b60fe69d\">commit 3508742</a></h2>\n<hr>\n<h3><strong>release-notes</strong>: image detection, year headings, and path normalisation on index and preserve camera markers and never silently drop new notes (<a href=\"https://github.com/nbarrett/ngx-ramblers/issues/219\">#219</a>, <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/576\">#576</a>, <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/213\">#213</a>)</h3>\n<p>Three related bugs surfaced when auditing the release-notes-for-humans page\nafter the #219 camera-marker work. All three produced drift on the detail\nindex that accumulated silently with each build.</p>\n<ol>\n<li>Image detection missed everything except col.imageSource.\nThe one-off fixup script&#39;s rowHasImage only recognised column-level\nimageSource plus nested col.rows. Release-notes pages author images in\nmany shapes — markdown <img src=\"https://ngx-ramblers.org.uk/%E2%80%A6\" alt=\"\"> and <img> in contentText, row.carousel\nwith a linked album, row.albumIndex, col.icon. Anything outside the\nnarrow shape was marked image-less, so the humans page&#39;s camera-filter\ndropped most entries. Moved detection into server/lib/release-notes/\nimage-detection.ts as a proper, tested module (23 new unit tests) and\nmade it strict enough to reject the CMS editor&#39;s default-initialised\n&quot;stub&quot; carousel object (all-null fields attached to plain text rows)\nthat was the source of false positives on e.g. 2025-11-10-issue-33.</li>\n<li>Leading-slash paths on the detail index were invisible to round-trip.\nOlder entries were authored as ](<a href=\"https://ngx-ramblers.org.uk/how-to/committee/release-notes/%E2%80%A6\">https://ngx-ramblers.org.uk/how-to/committee/release-notes/…</a>)\nand newer ones as ](<a href=\"https://ngx-ramblers.org.uk/how-to/committee/release-notes/%E2%80%A6\">https://ngx-ramblers.org.uk/how-to/committee/release-notes/…</a>). The fixup\nscripts&#39; regex required no leading slash, so 54 entries (including\nevery 2025 entry with images, e.g. 2025-05-07) were silently skipped.\nparseIndexLine now strips a leading slash on parse, so every build\nnormalises legacy paths without losing the entries.</li>\n<li>The detail index had no year grouping except a single stray &quot;## 2026&quot;.\nAny year heading between entries was dropped by updateIndexPageContent\non round-trip (only the preamble survived). Refactored the generator\nto emit &quot;## <year>&quot; headings above each year&#39;s entries on every write,\nsorted newest-first. Preamble sanitisation strips any leftover year\nheadings before re-emission. Also exported refreshIndexPageContent()\nso one-off recovery scripts can re-apply current formatting rules to\nthe live page without merging a new entry.\nTest suite goes from 519 to 545 passing:</li>\n</ol>\n<ul>\n<li>23 new tests in image-detection.spec.ts covering stub carousel rejection,\nmarkdown/HTML image detection, nested rows, and the specific 2025-11-10\nfalse positive and 2025-01-17 true positive shapes</li>\n<li>3 new tests in content-generator.spec.ts covering year heading emission,\nleading-slash normalisation on round-trip, and refreshIndexPageContent</li>\n</ul>\n<p>Two independent bugs in the automated release-notes workflow, fixed together.</p>\n<ol>\n<li>Camera 📸 markers stripped from the index on every rewrite.\nThe entry line regex in content-generator.ts did not allow any trailing\ncontent after the markdown link&#39;s closing paren, so index entries with a\ntrailing 📸 marker (used to flag release notes with embedded images)\nfailed parseIndexLine, returned null, and were silently dropped from the\nre-serialised list on every build.</li>\n</ol>\n<ul>\n<li>Extend entryLineRegex to capture an optional trailing &quot; 📸&quot;</li>\n<li>Add hasCamera to IndexEntry; parseIndexLine records it and\nformatIndexLine re-emits it</li>\n<li>updateIndexPageContent preserves the existing hasCamera when\nre-adding an entry at the same key</li>\n<li>New regression tests in content-generator.spec.ts</li>\n</ul>\n<ol start=\"2\">\n<li>New release notes silently dropped when the target page already exists.\nWhen a page already existed at the resolved path, generateReleaseNote\nwould skip creation entirely (to preserve manual edits like embedded\nimages) and do nothing else — the new commits had no release note\nanywhere, and the index was rewritten with an entry still pointing at\nthe old, pre-edit page. Build #576 for commit 4b7e274f hit exactly\nthis: three new #213 commits left undocumented.</li>\n</ol>\n<ul>\n<li>When a collision is detected, write the new note at a distinct\nbuild-specific path suffix (-build-<buildNumber>, or -commit-<short>\nwhen no build number is available)</li>\n<li>Collision-proof the suffix with a recursive counter fallback</li>\n<li>Always create — never silently skip</li>\n<li>Skip removeLegacyReleasePages when we wrote to a suffixed path</li>\n</ul>\n"}