Skip to content

feat: adding gh changelog/releases within npmx#1233

Open
WilcoSp wants to merge 57 commits intonpmx-dev:mainfrom
WilcoSp:feat/changelog-1
Open

feat: adding gh changelog/releases within npmx#1233
WilcoSp wants to merge 57 commits intonpmx-dev:mainfrom
WilcoSp:feat/changelog-1

Conversation

@WilcoSp
Copy link
Contributor

@WilcoSp WilcoSp commented Feb 8, 2026

This pr will add the possibility to view the changelog.md & releases from a package's github repo within npmx.
This will make it easier to see the changelogs while not needing to leave npmx and allowing quicker access.

This pr is the first pr of #501

Preview here

While I was making this PR antfu also made this pr #1368 and here my comment from his pr

@vercel
Copy link

vercel bot commented Feb 8, 2026

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

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Feb 27, 2026 10:35pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Feb 27, 2026 10:35pm
npmx-lunaria Ignored Ignored Feb 27, 2026 10:35pm

Request Review

@github-actions
Copy link

github-actions bot commented Feb 8, 2026

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

File Note
lunaria/files/ar-EG.json Localization changed, will be marked as complete. 🔄️
lunaria/files/az-AZ.json Localization changed, will be marked as complete. 🔄️
lunaria/files/bg-BG.json Localization changed, will be marked as complete. 🔄️
lunaria/files/bn-IN.json Localization changed, will be marked as complete. 🔄️
lunaria/files/cs-CZ.json Localization changed, will be marked as complete. 🔄️
lunaria/files/de-DE.json Localization changed, will be marked as complete. 🔄️
lunaria/files/en-GB.json Localization changed, will be marked as complete. 🔄️
lunaria/files/en-US.json Source changed, localizations will be marked as outdated.
lunaria/files/es-419.json Localization changed, will be marked as complete. 🔄️
lunaria/files/es-ES.json Localization changed, will be marked as complete. 🔄️
lunaria/files/fr-FR.json Localization changed, will be marked as complete. 🔄️
lunaria/files/hi-IN.json Localization changed, will be marked as complete. 🔄️
lunaria/files/hu-HU.json Localization changed, will be marked as complete. 🔄️
lunaria/files/id-ID.json Localization changed, will be marked as complete. 🔄️
lunaria/files/it-IT.json Localization changed, will be marked as complete. 🔄️
lunaria/files/ja-JP.json Localization changed, will be marked as complete. 🔄️
lunaria/files/kn-IN.json Localization changed, will be marked as complete. 🔄️
lunaria/files/mr-IN.json Localization changed, will be marked as complete. 🔄️
lunaria/files/nb-NO.json Localization changed, will be marked as complete. 🔄️
lunaria/files/ne-NP.json Localization changed, will be marked as complete. 🔄️
lunaria/files/pl-PL.json Localization changed, will be marked as complete. 🔄️
lunaria/files/pt-BR.json Localization changed, will be marked as complete. 🔄️
lunaria/files/ru-RU.json Localization changed, will be marked as complete. 🔄️
lunaria/files/ta-IN.json Localization changed, will be marked as complete. 🔄️
lunaria/files/te-IN.json Localization changed, will be marked as complete. 🔄️
lunaria/files/uk-UA.json Localization changed, will be marked as complete. 🔄️
lunaria/files/zh-CN.json Localization changed, will be marked as complete. 🔄️
lunaria/files/zh-TW.json Localization changed, will be marked as complete. 🔄️
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

@codecov
Copy link

codecov bot commented Feb 8, 2026

Codecov Report

❌ Patch coverage is 46.66667% with 16 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/composables/usePackageChangelog.ts 0.00% 5 Missing and 1 partial ⚠️
app/composables/useProviderIcon.ts 16.66% 3 Missing and 2 partials ⚠️
app/pages/package/[[org]]/[name].vue 0.00% 2 Missing and 1 partial ⚠️
app/components/Changelog/Card.vue 80.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@WilcoSp WilcoSp marked this pull request as ready for review February 25, 2026 13:31
@WilcoSp WilcoSp changed the title feat: gh changelog/releases within npmx feat: adding gh changelog/releases within npmx Feb 25, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a changelog feature and supporting infrastructure: new Vue components under app/components/Changelog (Card.vue, Markdown.vue, Releases.vue); composables usePackageChangelog and useProviderIcon; a new page app/pages/package-changes/[...path].vue and package page changes to surface changelogs; server APIs for changelog info, markdown and releases (server/api/changelog/{info,md,releases}); markdown rendering/sanitisation and detection utilities (server/utils/changelog/{markdown.ts,detectChangelog.ts}); adjustments to server/utils/readme.ts exports; new types and schemas (shared/types/changelog.ts, shared/schemas/changelog/release.ts); wide i18n key rename from common.view_on_npm → common.view_on; tests and a11y test updates.

Possibly related PRs

Suggested reviewers

  • danielroe
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly relates to the changeset, detailing the addition of changelog and GitHub releases viewing within npmx.

✏️ 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: 10

🧹 Nitpick comments (8)
server/utils/readme.ts (1)

135-195: Harden exported sanitiser allow-lists against accidental mutation.

Now that these are exported, freezing/readonly typing would prevent downstream code from mutating sanitisation policy at runtime.

🔒 Suggested hardening
-export const ALLOWED_TAGS = [
+export const ALLOWED_TAGS: readonly string[] = Object.freeze([
   'h1',
   'h2',
   'h3',
   'h4',
   'h5',
   'h6',
   'p',
   'br',
   'hr',
   'ul',
   'ol',
   'li',
   'blockquote',
   'pre',
   'code',
   'a',
   'strong',
   'em',
   'del',
   's',
   'table',
   'thead',
   'tbody',
   'tr',
   'th',
   'td',
   'img',
   'picture',
   'source',
   'details',
   'summary',
   'div',
   'span',
   'sup',
   'sub',
   'kbd',
   'mark',
   'button',
-]
+])
 
-export const ALLOWED_ATTR: Record<string, string[]> = {
+export const ALLOWED_ATTR: Readonly<Record<string, readonly string[]>> = Object.freeze({
   '*': ['id'], // Allow id on all tags
   'a': ['href', 'title', 'target', 'rel', 'content-none'],
   'img': ['src', 'alt', 'title', 'width', 'height', 'align'],
   'source': ['src', 'srcset', 'type', 'media'],
   'button': ['class', 'title', 'type', 'aria-label', 'data-copy'],
   'th': ['colspan', 'rowspan', 'align', 'valign', 'width'],
   'td': ['colspan', 'rowspan', 'align', 'valign', 'width'],
   'h3': ['data-level', 'align'],
   'h4': ['data-level', 'align'],
   'h5': ['data-level', 'align'],
   'h6': ['data-level', 'align'],
   'blockquote': ['data-callout'],
   'details': ['open'],
   'code': ['class'],
   'pre': ['class', 'style'],
   'span': ['class', 'style'],
   'div': ['class', 'style', 'align'],
   'p': ['align'],
-}
+})
shared/schemas/changelog/release.ts (1)

3-18: Fix the exported Schama typo before this API spreads further.

The misspelling is now part of exported symbols, so it will propagate to every consumer.

Suggested refactor
-export const GithubReleaseSchama = v.object({
+export const GithubReleaseSchema = v.object({
   id: v.pipe(v.number(), v.integer()),
   name: v.nullable(v.string()),
   tag: v.string(),
   draft: v.boolean(),
   prerelease: v.boolean(),
-  markdown: v.nullable(v.string()), // can be null if no descroption was made
+  markdown: v.nullable(v.string()), // can be null if no description was made
   publishedAt: v.pipe(v.string(), v.isoTimestamp()),
 })
 
-export const GithubReleaseCollectionSchama = v.object({
-  releases: v.array(GithubReleaseSchama),
+export const GithubReleaseCollectionSchema = v.object({
+  releases: v.array(GithubReleaseSchema),
 })
 
-export type GithubRelease = v.InferOutput<typeof GithubReleaseSchama>
-export type GithubReleaseCollection = v.InferOutput<typeof GithubReleaseCollectionSchama>
+export type GithubRelease = v.InferOutput<typeof GithubReleaseSchema>
+export type GithubReleaseCollection = v.InferOutput<typeof GithubReleaseCollectionSchema>
As per coding guidelines "Use clear, descriptive variable and function names".
server/api/changelog/md/[provider]/[owner]/[repo]/[...path].get.ts (1)

22-23: Remove request-time debug logging.

console.log({ provider }) will spam logs in normal traffic and does not add operational value here.

Suggested fix
-      console.log({ provider })
-
       switch (provider as ProviderId) {
server/utils/changelog/detectChangelog.ts (2)

129-149: Avoid sequential HEAD probes for changelog filenames.

This loop performs one network request at a time, so miss-heavy repos pay the full cumulative latency.

Suggested refactor
-  for (const fileName of CHANGELOG_FILENAMES) {
-    const exists = await fetch(`${baseUrl.raw}/${fileName}`, {
-      headers: {
-        // GitHub API requires User-Agent
-        'User-Agent': 'npmx.dev',
-      },
-      method: 'HEAD', // we just need to know if it exists or not
-    })
-      .then(r => r.ok)
-      .catch(() => false)
-    if (exists) {
-      return {
-        type: 'md',
-        provider: ref.provider,
-        path: fileName,
-        repo: `${ref.owner}/${ref.repo}`,
-        link: `${baseUrl.blob}/${fileName}`,
-      } satisfies ChangelogMarkdownInfo
-    }
-  }
-  return false
+  const checks = await Promise.all(
+    CHANGELOG_FILENAMES.map(async fileName => {
+      const exists = await fetch(`${baseUrl.raw}/${fileName}`, {
+        headers: {
+          'User-Agent': 'npmx.dev',
+        },
+        method: 'HEAD',
+      })
+        .then(r => r.ok)
+        .catch(() => false)
+      return { fileName, exists }
+    }),
+  )
+
+  const match = checks.find(c => c.exists)?.fileName
+  if (!match) {
+    return false
+  }
+
+  return {
+    type: 'md',
+    provider: ref.provider,
+    path: match,
+    repo: `${ref.owner}/${ref.repo}`,
+    link: `${baseUrl.blob}/${match}`,
+  } satisfies ChangelogMarkdownInfo
 }

19-20: Remove stale commented-out code.

These commented sections make the flow harder to scan and maintain.

As per coding guidelines "Add comments only to explain complex logic or non-obvious implementations".

Also applies to: 54-73

app/components/Changelog/Card.vue (1)

38-42: Unnecessary optional chaining on required prop.

release is a required prop (non-optional in defineProps), so release?.toc can be simplified to release.toc.

✏️ Suggested simplification
       <ReadmeTocDropdown
-        v-if="release?.toc && release.toc.length > 1"
+        v-if="release.toc && release.toc.length > 1"
         :toc="release.toc"
         class="ms-auto"
       />
app/pages/package-changes/[...path].vue (1)

112-114: Use strict equality for type comparisons.

Consider using === instead of == for string comparisons to maintain consistency with TypeScript best practices.

✏️ Suggested change
-      <LazyChangelogReleases v-if="changelog.type == 'release'" :info="changelog" />
+      <LazyChangelogReleases v-if="changelog.type === 'release'" :info="changelog" />
       <LazyChangelogMarkdown
-        v-else-if="changelog.type == 'md'"
+        v-else-if="changelog.type === 'md'"
app/pages/package/[[org]]/[name].vue (1)

974-981: Simplify the truthiness check.

The double negation !!changelog is unnecessary in a v-if directive since Vue already evaluates truthiness.

✏️ Suggested simplification
-            <li v-if="!!changelog && resolvedVersion">
+            <li v-if="changelog && resolvedVersion">

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa62d11 and 746464f.

📒 Files selected for processing (74)
  • app/components/Changelog/Card.vue
  • app/components/Changelog/Markdown.vue
  • app/components/Changelog/Releases.vue
  • app/components/Readme.vue
  • app/composables/usePackageChangelog.ts
  • app/composables/useProviderIcon.ts
  • app/pages/org/[org].vue
  • app/pages/package-changes/[...path].vue
  • app/pages/package/[[org]]/[name].vue
  • app/pages/~[username]/index.vue
  • i18n/locales/ar.json
  • i18n/locales/az-AZ.json
  • i18n/locales/bg-BG.json
  • i18n/locales/bn-IN.json
  • i18n/locales/cs-CZ.json
  • i18n/locales/de-DE.json
  • i18n/locales/en.json
  • i18n/locales/es.json
  • i18n/locales/fr-FR.json
  • i18n/locales/hi-IN.json
  • i18n/locales/hu-HU.json
  • i18n/locales/id-ID.json
  • i18n/locales/it-IT.json
  • i18n/locales/ja-JP.json
  • i18n/locales/mr-IN.json
  • i18n/locales/nb-NO.json
  • i18n/locales/ne-NP.json
  • i18n/locales/pl-PL.json
  • i18n/locales/pt-BR.json
  • i18n/locales/ru-RU.json
  • i18n/locales/ta-IN.json
  • i18n/locales/te-IN.json
  • i18n/locales/uk-UA.json
  • i18n/locales/zh-CN.json
  • i18n/locales/zh-TW.json
  • i18n/schema.json
  • lunaria/files/ar-EG.json
  • lunaria/files/az-AZ.json
  • lunaria/files/bg-BG.json
  • lunaria/files/bn-IN.json
  • lunaria/files/cs-CZ.json
  • lunaria/files/de-DE.json
  • lunaria/files/en-GB.json
  • lunaria/files/en-US.json
  • lunaria/files/es-419.json
  • lunaria/files/es-ES.json
  • lunaria/files/fr-FR.json
  • lunaria/files/hi-IN.json
  • lunaria/files/hu-HU.json
  • lunaria/files/id-ID.json
  • lunaria/files/it-IT.json
  • lunaria/files/ja-JP.json
  • lunaria/files/mr-IN.json
  • lunaria/files/nb-NO.json
  • lunaria/files/ne-NP.json
  • lunaria/files/pl-PL.json
  • lunaria/files/pt-BR.json
  • lunaria/files/ru-RU.json
  • lunaria/files/ta-IN.json
  • lunaria/files/te-IN.json
  • lunaria/files/uk-UA.json
  • lunaria/files/zh-CN.json
  • lunaria/files/zh-TW.json
  • server/api/changelog/info/[...pkg].get.ts
  • server/api/changelog/md/[provider]/[owner]/[repo]/[...path].get.ts
  • server/api/changelog/releases/[provider]/[owner]/[repo].get.ts
  • server/utils/changelog/detectChangelog.ts
  • server/utils/changelog/markdown.ts
  • server/utils/readme.ts
  • shared/schemas/changelog/release.ts
  • shared/types/changelog.ts
  • shared/utils/constants.ts
  • test/nuxt/a11y.spec.ts
  • test/unit/a11y-component-coverage.spec.ts

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

🧹 Nitpick comments (1)
server/utils/changelog/markdown.ts (1)

32-34: Simplify the template literal.

The template literal on line 32 has redundant string concatenation with an empty string.

♻️ Suggested simplification
-    const intermediateTitleAttr = `${` data-title-intermediate="${plainText || title}"`}`
+    const intermediateTitleAttr = ` data-title-intermediate="${plainText || title}"`

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 746464f and 750072c.

📒 Files selected for processing (8)
  • app/components/Changelog/Card.vue
  • app/components/Changelog/Markdown.vue
  • app/composables/useProviderIcon.ts
  • app/pages/package-changes/[...path].vue
  • i18n/locales/en.json
  • lunaria/files/en-GB.json
  • lunaria/files/en-US.json
  • server/utils/changelog/markdown.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/composables/useProviderIcon.ts
  • i18n/locales/en.json
  • lunaria/files/en-GB.json
  • app/components/Changelog/Markdown.vue

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.

🧹 Nitpick comments (2)
app/pages/package-changes/[...path].vue (2)

44-44: Consider removing commented-out code.

The commented-out filePathOrig computed property appears unused. If it's not needed for upcoming work, consider removing it to reduce noise.


95-102: Consider adding optional chaining for changelog.provider.

The v-if guards on changelog?.link, but changelog.provider is accessed without optional chaining on lines 99 and 101. If the type guarantees provider exists whenever link does, this is fine; otherwise, adding changelog?.provider would be more defensive.

🛡️ Suggested defensive change
           <LinkBase
             v-if="changelog?.link"
             :to="changelog?.link"
             :classicon="repoProviderIcon"
-            :title="$t('common.view_on', { site: changelog.provider })"
+            :title="$t('common.view_on', { site: changelog?.provider })"
           >
-            {{ changelog.provider }}
+            {{ changelog?.provider }}
           </LinkBase>

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 750072c and 6e6ee3a.

📒 Files selected for processing (1)
  • app/pages/package-changes/[...path].vue

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: 2

🧹 Nitpick comments (1)
server/utils/changelog/markdown.ts (1)

263-266: Remove commented-out code.

This commented code is superseded by the cleaner implementation at lines 274-277. Dead comments reduce readability.

🧹 Proposed fix
   const baseUrl = isMarkdownFile ? repoInfo.blobBaseUrl : repoInfo.rawBaseUrl

-  // if (url.startsWith('./') || url.startsWith('../')) {
-  //   // url constructor handles relative paths
-  //   return checkResolvedUrl(new URL(url, `${baseUrl}/${repoInfo.path ?? ''}`).href, baseUrl)
-  // }
-
   if (url.startsWith('/')) {

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e6ee3a and 4de0348.

📒 Files selected for processing (2)
  • app/pages/package-changes/[...path].vue
  • server/utils/changelog/markdown.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/pages/package-changes/[...path].vue

Comment on lines +23 to +35
renderer.link = function ({ href, title, tokens }: Tokens.Link) {
const text = this.parser.parseInline(tokens)
const titleAttr = title ? ` title="${title}"` : ''
const plainText = text.replace(/<[^>]*>/g, '').trim()

if (href.startsWith('mailto:') && !EMAIL_REGEX.test(plainText)) {
return text
}

const intermediateTitleAttr = `data-title-intermediate="${plainText || title}"`

return `<a href="${href}"${titleAttr}${intermediateTitleAttr} target="_blank">${text}</a>`
}
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 | 🟠 Major

Anchor links will incorrectly open in a new tab.

The renderer unconditionally adds target="_blank" to all links, including anchor links like #heading. This breaks same-page navigation — clicking an anchor should scroll to that heading, not open a new tab.

🐛 Proposed fix to conditionally apply target
   renderer.link = function ({ href, title, tokens }: Tokens.Link) {
     const text = this.parser.parseInline(tokens)
     const titleAttr = title ? ` title="${title}"` : ''
     const plainText = text.replace(/<[^>]*>/g, '').trim()

     if (href.startsWith('mailto:') && !EMAIL_REGEX.test(plainText)) {
       return text
     }

-    const intermediateTitleAttr = `data-title-intermediate="${plainText || title}"`
+    // Only open external links in new tab; anchor links should navigate in-page
+    const targetAttr = href.startsWith('#') ? '' : ' target="_blank"'

-    return `<a href="${href}"${titleAttr}${intermediateTitleAttr} target="_blank">${text}</a>`
+    return `<a href="${href}"${titleAttr}${targetAttr}>${text}</a>`
   }

Note: The data-title-intermediate attribute is stripped by sanitize-html since it's not in ALLOWED_ATTR. If this attribute is needed downstream, add it to the allowed list; otherwise, it can be removed from the renderer.

Comment on lines +211 to +216
div: prefixId,
p: prefixId,
span: prefixId,
section: prefixId,
article: prefixId,
},
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 26, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Dead code: section and article transforms will never apply.

The transformTags entries for section and article are never executed because neither tag is included in ALLOWED_TAGS. sanitize-html removes disallowed tags before transforms run.

🧹 Proposed fix to remove dead code
       div: prefixId,
       p: prefixId,
       span: prefixId,
-      section: prefixId,
-      article: prefixId,
     },
   })

If these tags should be supported, add them to ALLOWED_TAGS in server/utils/readme.ts instead.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
div: prefixId,
p: prefixId,
span: prefixId,
section: prefixId,
article: prefixId,
},
div: prefixId,
p: prefixId,
span: prefixId,
},

Copy link
Contributor Author

Choose a reason for hiding this comment

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

rather keep the dead code in case at the readme.ts then allow those tags

Copy link
Contributor

Choose a reason for hiding this comment

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

@WilcoSp, understood! Keeping those transforms in place for future use is a reasonable approach if section and article might be added to ALLOWED_TAGS later.

You might consider adding a brief comment above those lines to clarify they're intentionally included for future compatibility, which would help other reviewers understand the intent:

      div: prefixId,
      p: prefixId,
      span: prefixId,
      // Reserved for future use if section/article are added to ALLOWED_TAGS
      section: prefixId,
      article: prefixId,

This way the code documents the architectural decision. But it's entirely optional—your call!


🧠 Learnings used
Learnt from: CR
Repo: npmx-dev/npmx.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-02-03T10:09:38.777Z
Learning: Applies to **/*.{ts,tsx,vue} : Add comments only to explain complex logic or non-obvious implementations

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.

♻️ Duplicate comments (1)
server/utils/changelog/markdown.ts (1)

23-35: ⚠️ Potential issue | 🟠 Major

Anchor links will incorrectly open in a new tab.

The renderer unconditionally adds target="_blank" to all links at line 34, including anchor links like #heading. This breaks same-page navigation — clicking an anchor should scroll to that heading, not open a new tab. This was flagged in a previous review but appears unaddressed.

🐛 Proposed fix to conditionally apply target
   renderer.link = function ({ href, title, tokens }: Tokens.Link) {
     const text = this.parser.parseInline(tokens)
     const titleAttr = title ? ` title="${title}"` : ''
     const plainText = text.replace(/<[^>]*>/g, '').trim()

     if (href.startsWith('mailto:') && !EMAIL_REGEX.test(plainText)) {
       return text
     }

-    const intermediateTitleAttr = `data-title-intermediate="${plainText || title}"`
-
-    return `<a href="${href}"${titleAttr}${intermediateTitleAttr} target="_blank">${text}</a>`
+    // Only open external links in new tab; anchor links should navigate in-page
+    const targetAttr = href.startsWith('#') ? '' : ' target="_blank"'
+
+    return `<a href="${href}"${titleAttr}${targetAttr}>${text}</a>`
   }

Note: The data-title-intermediate attribute will be stripped by sanitize-html unless it's added to ALLOWED_ATTR. If this attribute is needed downstream, add it to the allowed list; otherwise, it can be removed from the renderer as shown above.

🧹 Nitpick comments (3)
shared/utils/constants.ts (2)

21-21: Inconsistent message casing and grammar.

This message starts with lowercase 'failed' whereas all other error messages in this file use sentence case (e.g., 'Failed to fetch...', 'Package name...'). Additionally, the phrasing 'detect package has changelog' reads awkwardly.

✏️ Suggested fix for consistency
-export const ERROR_PACKAGE_DETECT_CHANGELOG = 'failed to detect package has changelog'
+export const ERROR_PACKAGE_DETECT_CHANGELOG = 'Failed to detect changelog for package.'

39-43: Consider grouping changelog constants and aligning naming pattern.

Two minor observations:

  1. Organisation: ERROR_PACKAGE_DETECT_CHANGELOG (line 21) is separated from the other changelog-related constants here. Consider grouping them together for maintainability.

  2. Naming: ERROR_THROW_INCOMPLETE_PARAM uses "THROW" which is an implementation detail. Other constants follow noun-phrase patterns like ERROR_*_FAILED or ERROR_*_NOT_FOUND. A name like ERROR_INCOMPLETE_PARAMETERS or ERROR_MISSING_PARAMETERS would be more consistent.

✏️ Suggested rename
-export const ERROR_THROW_INCOMPLETE_PARAM = "Couldn't do request due to incomplete parameters"
+export const ERROR_INCOMPLETE_PARAMETERS = "Couldn't complete request due to incomplete parameters"
server/utils/changelog/markdown.ts (1)

214-215: Consider adding a comment to document the intentional dead code.

As discussed in a previous review, section and article transforms are currently dead code since these tags aren't in ALLOWED_TAGS. Since you've chosen to keep them for future compatibility, a brief comment would help future reviewers understand this is intentional.

📝 Suggested documentation
       div: prefixId,
       p: prefixId,
       span: prefixId,
+      // Reserved for future use if section/article are added to ALLOWED_TAGS
       section: prefixId,
       article: prefixId,

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4de0348 and 6d0edfd.

📒 Files selected for processing (14)
  • app/pages/package/[[org]]/[name].vue
  • i18n/locales/de-DE.json
  • i18n/locales/en.json
  • i18n/locales/fr-FR.json
  • i18n/locales/kn-IN.json
  • i18n/schema.json
  • lunaria/files/de-DE.json
  • lunaria/files/en-GB.json
  • lunaria/files/en-US.json
  • lunaria/files/fr-FR.json
  • lunaria/files/kn-IN.json
  • server/utils/changelog/markdown.ts
  • shared/utils/constants.ts
  • test/nuxt/a11y.spec.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • i18n/locales/de-DE.json
  • test/nuxt/a11y.spec.ts
  • i18n/locales/fr-FR.json
  • lunaria/files/de-DE.json
  • lunaria/files/en-US.json
  • lunaria/files/en-GB.json
  • lunaria/files/fr-FR.json

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.

2 participants