Skip to content

fix: change readme md renderer behavior#1776

Open
RYGRIT wants to merge 8 commits intonpmx-dev:mainfrom
RYGRIT:fix/issue-1323
Open

fix: change readme md renderer behavior#1776
RYGRIT wants to merge 8 commits intonpmx-dev:mainfrom
RYGRIT:fix/issue-1323

Conversation

@RYGRIT
Copy link
Contributor

@RYGRIT RYGRIT commented Mar 1, 2026

🔗 Linked issue

resolves #1323

🧭 Context

📚 Description

This PR fixes README rendering issues caused by split processing between marked and sanitizeHtml, and makes heading/link handling more consistent in mixed markdown + raw HTML content.

What changed:

  • Processed headings and links in a more consistent single-pass flow during marked rendering.
  • Fixed TOC/slug ordering for mixed markdown and raw HTML headings.
  • Prevented heading ID collisions when heading text already includes user-content-.
  • Preserved supported attributes during raw HTML rewrites:
    • headings keep supported attrs (e.g. align)
    • anchors preserve allowlisted attrs (including title, even when placed before href)
    • rewritten anchors normalize href and enforce safe rel/target without duplicates
  • Added/updated unit tests for mixed heading order, duplicate slug/ID behavior, and raw HTML anchor/heading attribute preservation (including renderer.html-path cases).

@vercel
Copy link

vercel bot commented Mar 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Mar 1, 2026 2:33pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Mar 1, 2026 2:33pm
npmx-lunaria Ignored Ignored Mar 1, 2026 2:33pm

Request Review

@codecov
Copy link

codecov bot commented Mar 1, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 1, 2026

📝 Walkthrough

Walkthrough

Refactors readme rendering to a single-pass pipeline: adds user-content prefixing utilities, centralises heading processing to compute semantic levels and generate prefixed slugs, consolidates link resolution and security attributes via a new processLink, and updates the renderer to intercept Markdown and raw HTML headings and anchors. Also unifies playground link collection with de-duplication and adjusts transform/tag handling and tests to cover mixed HTML/Markdown ordering, slug collisions, ID consistency and URL resolution.

Possibly related PRs

Suggested reviewers

  • danielroe
  • alexdln
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR successfully addresses all coding requirements from issue #1323: single-pass headings processing, correct TOC ordering, proper duplicate ID handling, and raw HTML link collection.
Out of Scope Changes check ✅ Passed All changes directly support the stated objectives of fixing heading/link processing through single-pass rendering; no out-of-scope modifications detected.
Description check ✅ Passed The pull request description is directly related to the changeset, explaining the single-pass rendering approach and specific fixes for TOC ordering, ID collisions, and HTML attribute preservation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
test/unit/server/utils/readme.spec.ts (1)

579-584: Add a regression case for user-content- slug collisions.

This duplicate-slug test only covers identical heading text. Please add a case like # Title plus # user-content-title to ensure generated IDs remain unique.

🧪 Suggested additional test
   it('handles duplicate raw HTML heading slugs', async () => {
     const md = '<h2>API</h2>\n\n<h2>API</h2>'
     const result = await renderReadmeHtml(md, 'test-pkg')
     expect(result.html).toContain('id="user-content-api"')
     expect(result.html).toContain('id="user-content-api-1"')
   })
+
+  it('keeps IDs unique when a heading slug already starts with user-content-', async () => {
+    const md = '# Title\n\n# user-content-title'
+    const result = await renderReadmeHtml(md, 'test-pkg')
+    const ids = result.toc.map(t => t.id)
+    expect(new Set(ids).size).toBe(ids.length)
+  })
 })

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f18d64e and a4d07f7.

📒 Files selected for processing (2)
  • server/utils/readme.ts
  • test/unit/server/utils/readme.spec.ts

@RYGRIT RYGRIT marked this pull request as draft March 1, 2026 07:37
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4d07f7 and dcda67f.

📒 Files selected for processing (2)
  • server/utils/readme.ts
  • test/unit/server/utils/readme.spec.ts

Comment on lines +480 to +489
const htmlHeadingRe = /<h([1-6])(\s[^>]*)?>([\s\S]*?)<\/h\1>/gi
const htmlAnchorRe = /<a(\s[^>]*?)href=(["'])([^"']*)\2([^>]*)>([\s\S]*?)<\/a>/gi
renderer.html = function ({ text }: Tokens.HTML) {
let result = text.replace(htmlHeadingRe, (_, level, attrs, inner) => {
const depth = parseInt(level)
const plainText = decodeHtmlEntities(stripHtmlTags(inner).trim())
const align = /\balign=(["'])(.*?)\1/i.exec(attrs)?.[2]
const preservedAttrs = align ? ` align="${align}"` : ''
return processHeading(depth, plainText, preservedAttrs).trimEnd()
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against undefined attrs before regex execution.

On Line 486, if the HTML heading has no attributes (e.g., <h1>Title</h1>), the attrs capture group will be undefined. Calling .exec(attrs) on undefined will coerce it to the string "undefined", which could match unintended content.

🛡️ Proposed fix
     let result = text.replace(htmlHeadingRe, (_, level, attrs, inner) => {
       const depth = parseInt(level)
       const plainText = decodeHtmlEntities(stripHtmlTags(inner).trim())
-      const align = /\balign=(["'])(.*?)\1/i.exec(attrs)?.[2]
+      const align = attrs ? /\balign=(["'])(.*?)\1/i.exec(attrs)?.[2] : undefined
       const preservedAttrs = align ? ` align="${align}"` : ''
       return processHeading(depth, plainText, preservedAttrs).trimEnd()
     })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: change markdown renderer behavior

1 participant