Skip to content

fix: show version-specific license and highlight license changes#2215

Closed
thealxlabs wants to merge 5 commits intonpmx-dev:mainfrom
thealxlabs:fix/version-specific-license
Closed

fix: show version-specific license and highlight license changes#2215
thealxlabs wants to merge 5 commits intonpmx-dev:mainfrom
thealxlabs:fix/version-specific-license

Conversation

@thealxlabs
Copy link

🔗 Linked issue

resolves #2163
resolves #2168
resolves #2190
resolves #1826

🧭 Context

Several related issues on the package page: (1) the license shown was always from the latest version, not the currently viewed version; (2) when a non-latest version has a different license than the current latest, no visual indicator was shown; (3) the hasDependencies computed was unused causing a lint error; (4) the LicenseDisplay prop was receiving a union type that TypeScript rejected.

📚 Description

  • Fixed licenseChanged computed to compare displayVersion.value?.license against pkg.value?.license (the package-level license, which reflects the latest) — SlimVersion doesn't carry license so using latestVersion.value?.license was always undefined.
  • Added an amber "changed" badge shown via v-if="licenseChanged" with aria-label tooltip text listing the latest license.
  • Removed the unused hasDependencies computed that was causing a lint/type error.
  • Added type cast as string for LicenseDisplay :license prop to satisfy TypeScript.
  • Added changed_badge and changed keys to the i18n schema for the package.license object.
  • Added Package/Dependencies.vue empty-state slots for no-deps display.

Unit tests in test/unit/app/utils/license-change.spec.ts verify the licenseChanged detection logic including null handling, object-shaped license normalization, and real-world cases.

@vercel
Copy link

vercel bot commented Mar 22, 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 22, 2026 7:53pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Mar 22, 2026 7:53pm
npmx-lunaria Ignored Ignored Mar 22, 2026 7:53pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5a8e947d-8593-4fcc-aa0e-0703e0c62087

📥 Commits

Reviewing files that changed from the base of the PR and between 25409b6 and c5b4cf2.

📒 Files selected for processing (2)
  • i18n/locales/en.json
  • i18n/schema.json
✅ Files skipped from review due to trivial changes (1)
  • i18n/locales/en.json

📝 Walkthrough

Walkthrough

Adds an empty-state message to the Dependencies component when there are no dependencies; updates the package page to prefer the modern clipboard API (navigator.clipboard.write + ClipboardItem) with a fallback for copying the README; changes license display to prefer the selected version’s license and adds a licenseChanged indicator/tooltip; simplifies dependency rendering conditions; adjusts tablet grid CSS to remove extra row and align the sidebar to the top; extends i18n schema and English strings for the new license and dependency messages; and adds unit tests for license-change detection logic.

Possibly related PRs

Suggested reviewers

  • alexdln
  • graphieros
  • danielroe
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The PR description is detailed and directly addresses the changeset, explaining fixes for license display, dependencies section visibility, grid layout, and new unit tests.
Linked Issues check ✅ Passed All four linked issues are addressed: version-specific license display (#2163), dependencies section always visible (#2168), grid layout alignment (#2190), and license change highlighting (#1826).
Out of Scope Changes check ✅ Passed All changes are directly related to the objectives in linked issues. No out-of-scope modifications were introduced.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

🧹 Nitpick comments (2)
test/unit/app/utils/license-change.spec.ts (1)

13-21: Tests validate a local copy of the logic, not the production code.

The normalize and licenseChanged functions are duplicated here rather than imported from the production code in app/pages/package/[[org]]/[name].vue (lines 325-333). If the production implementation ever diverges, these tests would continue to pass while the actual feature could be broken.

Consider extracting the logic to a shared utility (e.g., app/utils/license.ts) and importing it in both the component and this test file. This would ensure the tests validate the actual production code.

♻️ Suggested approach

Create a shared utility:

// app/utils/license.ts
export type LicenseValue = string | { type?: string } | undefined | null

export function normalizeLicense(l: LicenseValue): string {
  if (!l) return ''
  return typeof l === 'string' ? l : (l.type ?? '')
}

export function hasLicenseChanged(
  currentLicense: LicenseValue,
  packageLicense: LicenseValue,
): boolean {
  if (!currentLicense || !packageLicense) return false
  return normalizeLicense(currentLicense) !== normalizeLicense(packageLicense)
}

Then import in both the component and tests.

app/pages/package/[[org]]/[name].vue (1)

324-332: Consider extracting normalize to module scope.

The static analysis tool flags that normalize doesn't capture any variables from its parent scope. Moving it outside the computed (or to a shared utility as suggested in the test file review) would address this lint warning and improve reusability.

♻️ Suggested refactor
+// Module-level helper for license normalisation
+function normalizeLicense(l: unknown): string {
+  if (!l) return ''
+  return typeof l === 'string' ? l : ((l as { type?: string })?.type ?? '')
+}
+
 // SlimVersion does not carry license, so we compare against the package-level license
 const licenseChanged = computed(() => {
   const currentLicense = displayVersion.value?.license
   const latestLicense = pkg.value?.license
   if (!currentLicense || !latestLicense) return false
-  const normalize = (l: unknown): string =>
-    typeof l === 'string' ? l : ((l as { type?: string })?.type ?? '')
-  return normalize(currentLicense) !== normalize(latestLicense)
+  return normalizeLicense(currentLicense) !== normalizeLicense(latestLicense)
 })

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e835a82f-0997-48e4-986c-6932dc80b5f6

📥 Commits

Reviewing files that changed from the base of the PR and between 7f2fc1a and 25409b6.

📒 Files selected for processing (4)
  • app/components/Package/Dependencies.vue
  • app/pages/package/[[org]]/[name].vue
  • i18n/schema.json
  • test/unit/app/utils/license-change.spec.ts

Comment on lines 110 to 133
async function copyReadmeHandler() {
await fetchReadmeMarkdown()
// Safari requires navigator.clipboard.write() to be called synchronously within
// the user gesture, but accepts a Promise inside ClipboardItem.
// This pattern works in Safari 13.1+, Chrome, and Firefox.
if (typeof ClipboardItem !== 'undefined' && navigator.clipboard?.write) {
const blobPromise = (async () => {
await fetchReadmeMarkdown()
const markdown = readmeMarkdownData.value?.markdown ?? ''
return new Blob([markdown], { type: 'text/plain' })
})()
try {
await navigator.clipboard.write([new ClipboardItem({ 'text/plain': blobPromise })])
return
} catch {
// Fall through to legacy approach
}
}

// Legacy fallback (non-Safari, or older browsers)
await fetchReadmeMarkdown()
const markdown = readmeMarkdownData.value?.markdown
if (!markdown) return

await copyReadme(markdown)
}
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

Missing UI feedback for the modern clipboard path.

When navigator.clipboard.write() succeeds (Safari path), the function returns without updating the copiedReadme state. The user won't see the "Copied" feedback that the legacy path provides via copyReadme().

🐛 Proposed fix
 async function copyReadmeHandler() {
   // Safari requires navigator.clipboard.write() to be called synchronously within
   // the user gesture, but accepts a Promise inside ClipboardItem.
   // This pattern works in Safari 13.1+, Chrome, and Firefox.
   if (typeof ClipboardItem !== 'undefined' && navigator.clipboard?.write) {
     const blobPromise = (async () => {
       await fetchReadmeMarkdown()
       const markdown = readmeMarkdownData.value?.markdown ?? ''
       return new Blob([markdown], { type: 'text/plain' })
     })()
     try {
       await navigator.clipboard.write([new ClipboardItem({ 'text/plain': blobPromise })])
+      // Manually trigger copied state for UI feedback
+      await copyReadme(readmeMarkdownData.value?.markdown ?? '')
       return
     } catch {
       // Fall through to legacy approach
     }
   }
 
   // Legacy fallback (non-Safari, or older browsers)
   await fetchReadmeMarkdown()
   const markdown = readmeMarkdownData.value?.markdown
   if (!markdown) return
   await copyReadme(markdown)
 }

Alternatively, if calling copyReadme again feels redundant, you could expose the copied ref setter from useClipboard or use a separate shallowRef to track copied state.

@github-actions
Copy link

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
i18n/locales/en.json Source changed, localizations will be marked as outdated.
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 Mar 22, 2026

Codecov Report

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

Files with missing lines Patch % Lines
app/pages/package/[[org]]/[name].vue 0.00% 7 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Contributor

@ghostdevv ghostdevv left a comment

Choose a reason for hiding this comment

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

I think this is a duplicate of a couple of PRs unfortunately, and is groups a few too many changes together 😅

@ghostdevv ghostdevv closed this Mar 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants