From 0eb5292ca437b4a8953de4ce48c288a040b6717d Mon Sep 17 00:00:00 2001 From: Maurice Date: Sat, 24 Oct 2026 17:11:43 +0200 Subject: [PATCH] feat(admin): release notes show a #### heading as the feature it names The release notes in Admin read ## as the version and ### as a section, and printed a #### line as plain text. A release that heads each feature inside a section with #### (Tours, TREK Studio under Added) now shows it as a feature heading between the section and its text, on desktop and on the phone. --- client/src/components/Admin/GitHubPanel.tsx | 7 +++++++ client/src/mobile/screens/admin/MAdminGitHubPanel.tsx | 7 +++++++ client/src/utils/releaseNotes.test.ts | 4 ++-- client/src/utils/releaseNotes.ts | 11 +++++++---- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/client/src/components/Admin/GitHubPanel.tsx b/client/src/components/Admin/GitHubPanel.tsx index 2657f5d34..dda191525 100644 --- a/client/src/components/Admin/GitHubPanel.tsx +++ b/client/src/components/Admin/GitHubPanel.tsx @@ -102,6 +102,13 @@ export default function GitHubPanel({ isPrerelease = false }: { isPrerelease?: b ); } + if (block.kind === 'h5') { + return ( +
+ {block.text} +
+ ); + } if (block.kind === 'h4') { return (

diff --git a/client/src/mobile/screens/admin/MAdminGitHubPanel.tsx b/client/src/mobile/screens/admin/MAdminGitHubPanel.tsx index dab18b102..a545bac42 100644 --- a/client/src/mobile/screens/admin/MAdminGitHubPanel.tsx +++ b/client/src/mobile/screens/admin/MAdminGitHubPanel.tsx @@ -83,6 +83,13 @@ export default function MAdminGitHubPanel({ isPrerelease = false }: { isPrerelea ) } + if (block.kind === 'h5') { + return ( +

+ {block.text} +
+ ); + } if (block.kind === 'h4') { return (

diff --git a/client/src/utils/releaseNotes.test.ts b/client/src/utils/releaseNotes.test.ts index 4a5d99c9a..aba447092 100644 --- a/client/src/utils/releaseNotes.test.ts +++ b/client/src/utils/releaseNotes.test.ts @@ -22,11 +22,11 @@ describe('parseReleaseNotes', () => { ]); }); - it('FE-UTIL-RELNOTES-003: lines are trimmed and markers need their space', () => { + it('FE-UTIL-RELNOTES-003: lines are trimmed, markers need their space and #### heads a feature', () => { expect(parseReleaseNotes(' ## Spaced \n-no space\n#### deep\n\n\n')).toEqual([ { kind: 'h3', text: 'Spaced' }, { kind: 'p', text: '-no space' }, - { kind: 'p', text: '#### deep' }, + { kind: 'h5', text: 'deep' }, ]); }); diff --git a/client/src/utils/releaseNotes.ts b/client/src/utils/releaseNotes.ts index c9f5de0a8..57ee4cb2a 100644 --- a/client/src/utils/releaseNotes.ts +++ b/client/src/utils/releaseNotes.ts @@ -1,5 +1,5 @@ /** One block of a release body: a heading, a paragraph, or a run of list items. */ -export type ReleaseNoteBlock = { kind: 'h3' | 'h4' | 'p'; text: string } | { kind: 'ul'; items: string[] }; +export type ReleaseNoteBlock = { kind: 'h3' | 'h4' | 'h5' | 'p'; text: string } | { kind: 'ul'; items: string[] }; /** Escapes the characters that would let release text break out of the HTML it is placed in. */ export function escapeReleaseHtml(str: string): string { @@ -7,8 +7,8 @@ export function escapeReleaseHtml(str: string): string { } /** - * Splits a GitHub release body into the blocks both admin shells render: `### ` and `## ` - * headings, `- ` or `* ` list items gathered into one list until anything else breaks the + * Splits a GitHub release body into the blocks both admin shells render: `## `, `### ` and + * `#### ` headings (the version, a section such as Added, a feature inside it), `- ` or `* ` list items gathered into one list until anything else breaks the * run, and every other non-empty line as a paragraph. Blank lines only end a list. The * text stays raw; each shell formats it inline itself. */ @@ -30,7 +30,10 @@ export function parseReleaseNotes(body: string): ReleaseNoteBlock[] { continue; } - if (trimmed.startsWith('### ')) { + if (trimmed.startsWith('#### ')) { + flushList(); + blocks.push({ kind: 'h5', text: trimmed.slice(5) }); + } else if (trimmed.startsWith('### ')) { flushList(); blocks.push({ kind: 'h4', text: trimmed.slice(4) }); } else if (trimmed.startsWith('## ')) {