Problem
webjs.dev/changelog flattens a changelog entry and its body into one undifferentiated bullet list. Every continuation paragraph becomes a sibling <li> of the entry title rather than body text belonging to it.
Measured against changelog/server/0.8.57.md, which has 3 entries: the website renders 23 peer bullets. Entry titles, explanatory paragraphs, and a bolded "Embedding note:" all appear at the same level, so a reader cannot tell which prose belongs to which change.
The cause is in website/modules/changelog/utils/render-entry.ts around L60-70. A blank line calls flushItem(), which closes the current <li> but leaves inList true. The next indented paragraph then re-matches the inList && /^ {2,}\S/ branch and opens a fresh <li>:
} else if (inList && /^ {2,}\S/.test(line)) {
curItem.push(line.trim());
} else if (line.trim() === '') {
flushItem(); // closes the item, but stays "in list"
}
The limitation is pre-existing and the auto-generated entries hit it too, but their continuation text is one or two lines, so it read as slightly loose spacing rather than as wrong. Hand-written release notes with several paragraphs per entry make it obvious.
GitHub Releases renders the same markdown correctly (a 2-space-indented paragraph after a blank line is a valid list-item continuation per CommonMark), so the two surfaces disagree about the same file. Only the website is wrong.
Design / approach
Treat a blank line inside a list as a paragraph break within the current item, not as the end of the item. The item stays open until a line appears that is neither a new - bullet nor an indented continuation.
That means flushItem() on a blank line is the wrong action while inList; the blank should instead mark that the next indented run starts a new <p> inside the same <li>. The item then needs to hold a list of paragraphs rather than a flat array of lines, and flushItem emits one <p> per paragraph.
Worth checking while in here: the same renderer is used for the /changelog cards and nothing else, so the blast radius is that one page. Verify against both a hand-written entry (changelog/server/0.8.57.md, multi-paragraph) and an auto-generated one (changelog/server/0.8.56.md, short continuations) so the fix does not regress the common case.
Because the website reads changelog markdown from the repo at request time, this is fixable at any point after a release publishes. Nothing needs republishing.
Implementation notes (for the implementing agent)
Where to edit:
website/modules/changelog/utils/render-entry.ts, the line loop around L55-72 (flushItem / startList / endList and the four-way branch). flushItem is defined just above, around L63 of the same file.
- The page that renders it is
website/app/changelog/page.ts, and the entries are read by website/modules/changelog/queries/list-entries.server.ts. Neither should need changing.
Landmines / gotchas:
- The nested-bullet case: an entry body can itself contain a
- sub-list (the auto-generated entries do, from squashed commit bodies). A naive "keep the item open until a non-indented line" rule must not swallow a following top-level entry as a continuation. Distinguish by indentation depth, not merely by the presence of - .
changelog/server/0.8.57.md contains an indented block that starts with - inside an entry body. Use it as a fixture; it exercises the ambiguous case directly.
- The repo has changelog files in both shapes (hand-written multi-paragraph, and generator output with a
*-prefixed commit-body dump). Both must render sensibly.
- Invariant 9 applies to any
html template touched here: no backticks inside the template body, including in comments.
Tests + docs surfaces:
website/test/ has no renderer test today; website/test/highlight/highlight.test.ts is the closest shape to copy for a pure-function test of the renderer.
- Assert entry-count vs rendered-
<li>-count against a real multi-paragraph changelog file, since that count mismatch is the bug (3 entries rendering as 23 bullets).
- No docs surface: this is website-internal rendering with no public API.
Acceptance criteria
Problem
webjs.dev/changelogflattens a changelog entry and its body into one undifferentiated bullet list. Every continuation paragraph becomes a sibling<li>of the entry title rather than body text belonging to it.Measured against
changelog/server/0.8.57.md, which has 3 entries: the website renders 23 peer bullets. Entry titles, explanatory paragraphs, and a bolded "Embedding note:" all appear at the same level, so a reader cannot tell which prose belongs to which change.The cause is in
website/modules/changelog/utils/render-entry.tsaround L60-70. A blank line callsflushItem(), which closes the current<li>but leavesinListtrue. The next indented paragraph then re-matches theinList && /^ {2,}\S/branch and opens a fresh<li>:The limitation is pre-existing and the auto-generated entries hit it too, but their continuation text is one or two lines, so it read as slightly loose spacing rather than as wrong. Hand-written release notes with several paragraphs per entry make it obvious.
GitHub Releases renders the same markdown correctly (a 2-space-indented paragraph after a blank line is a valid list-item continuation per CommonMark), so the two surfaces disagree about the same file. Only the website is wrong.
Design / approach
Treat a blank line inside a list as a paragraph break within the current item, not as the end of the item. The item stays open until a line appears that is neither a new
-bullet nor an indented continuation.That means
flushItem()on a blank line is the wrong action whileinList; the blank should instead mark that the next indented run starts a new<p>inside the same<li>. The item then needs to hold a list of paragraphs rather than a flat array of lines, andflushItememits one<p>per paragraph.Worth checking while in here: the same renderer is used for the
/changelogcards and nothing else, so the blast radius is that one page. Verify against both a hand-written entry (changelog/server/0.8.57.md, multi-paragraph) and an auto-generated one (changelog/server/0.8.56.md, short continuations) so the fix does not regress the common case.Because the website reads changelog markdown from the repo at request time, this is fixable at any point after a release publishes. Nothing needs republishing.
Implementation notes (for the implementing agent)
Where to edit:
website/modules/changelog/utils/render-entry.ts, the line loop around L55-72 (flushItem/startList/endListand the four-way branch).flushItemis defined just above, around L63 of the same file.website/app/changelog/page.ts, and the entries are read bywebsite/modules/changelog/queries/list-entries.server.ts. Neither should need changing.Landmines / gotchas:
-sub-list (the auto-generated entries do, from squashed commit bodies). A naive "keep the item open until a non-indented line" rule must not swallow a following top-level entry as a continuation. Distinguish by indentation depth, not merely by the presence of-.changelog/server/0.8.57.mdcontains an indented block that starts with-inside an entry body. Use it as a fixture; it exercises the ambiguous case directly.*-prefixed commit-body dump). Both must render sensibly.htmltemplate touched here: no backticks inside the template body, including in comments.Tests + docs surfaces:
website/test/has no renderer test today;website/test/highlight/highlight.test.tsis the closest shape to copy for a pure-function test of the renderer.<li>-count against a real multi-paragraph changelog file, since that count mismatch is the bug (3 entries rendering as 23 bullets).Acceptance criteria
<li>count for a changelog file equals its-entry count-sub-list inside an entry body renders as a nested list, and a following top-level entry is not absorbed into it