feat(ui): add health score widget powered by npm Pulse#2333
feat(ui): add health score widget powered by npm Pulse#2333hamdibenjarrar wants to merge 10 commits intonpmx-dev:mainfrom
Conversation
Co-authored-by: Philippe Serhal <philippe.serhal@gmail.com>
Adds a new PackageHealthScore component to the package sidebar that displays an overall health score (0-100) with letter grade (A-F) and four dimension bars: Maintenance (30%), Quality (25%), Security (25%), and Popularity (20%). Data is fetched client-side from https://npm-pulse.vercel.app/api/v1/score/{package} and rendered lazily to avoid blocking page load. - app/components/Package/HealthScore.vue: new sidebar widget - app/pages/package/[[org]]/[name].vue: inject component above download stats - i18n/locales/en.json: add package.health_score translation keys - i18n/locales/ar-EG.json: add Arabic (Egyptian) translations - i18n/locales/fr-FR.json: add French translations Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
Lunaria Status Overview🌕 This pull request will trigger status changes. Learn moreBy default, every PR changing files present in the Lunaria configuration's You can change this by adding one of the keywords present in the Tracked Files
Warnings reference
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a new Vue 3 component app/components/Package/HealthScore.vue that fetches and displays an npm Pulse health score (numeric score, grade badge and four dimension progress bars) for a required Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 1✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
i18n/locales/ar-EG.json (1)
1-1: UTF-8 BOM character detected at start of file.The file begins with a UTF-8 Byte Order Mark (
). While some tools handle this gracefully, BOMs in JSON files can cause parsing failures in certain environments and are generally discouraged per JSON specification (RFC 8259 recommends UTF-8 without BOM).Consider removing the BOM to ensure maximum compatibility.
app/components/Package/HealthScore.vue (3)
1-1: UTF-8 BOM character detected.The file begins with a UTF-8 BOM (
). This is unusual for Vue SFC files and may cause issues with some tooling. Consider removing it.
60-69: Add defensive checks for potentially missing dimension properties.The code accesses
data.value.dimensionsand its nested properties without verifying they exist. If the API response is malformed or changes, this will throw a runtime error.As per coding guidelines, ensure type-safe code by checking when accessing properties that might be undefined.
🛡️ Proposed defensive check
const dimensions = computed(() => { - if (!data.value) return [] - const d = data.value.dimensions + if (!data.value?.dimensions) return [] + const d = data.value.dimensions return [ - { key: 'maintenance', label: $t('package.health_score.dimension_maintenance'), score: d.maintenance.score, weight: d.maintenance.weight }, - { key: 'quality', label: $t('package.health_score.dimension_quality'), score: d.quality.score, weight: d.quality.weight }, - { key: 'security', label: $t('package.health_score.dimension_security'), score: d.security.score, weight: d.security.weight }, - { key: 'popularity', label: $t('package.health_score.dimension_popularity'), score: d.popularity.score, weight: d.popularity.weight }, + { key: 'maintenance', label: $t('package.health_score.dimension_maintenance'), score: d.maintenance?.score ?? 0, weight: d.maintenance?.weight ?? 0 }, + { key: 'quality', label: $t('package.health_score.dimension_quality'), score: d.quality?.score ?? 0, weight: d.quality?.weight ?? 0 }, + { key: 'security', label: $t('package.health_score.dimension_security'), score: d.security?.score ?? 0, weight: d.security?.weight ?? 0 }, + { key: 'popularity', label: $t('package.health_score.dimension_popularity'), score: d.popularity?.score ?? 0, weight: d.popularity?.weight ?? 0 }, ] })
149-157: Footer link uses rawpackageNameinstead ofprops.packageName.While this works due to Vue's template context, using
props.packageName(as done in line 28) would be more consistent. This is a minor nitpick.♻️ Consistency improvement
<a - :href="`https://npm-pulse.vercel.app/api/v1/score/${packageName}`" + :href="`https://npm-pulse.vercel.app/api/v1/score/${props.packageName}`" target="_blank"app/pages/package/[[org]]/[name].vue (1)
1-1: UTF-8 BOM character added to file.A UTF-8 BOM (
) has been added to the start of this file. This is inconsistent with typical Vue/TypeScript file conventions and may cause issues with some tooling. Consider removing it.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4c6cb7da-0ec1-412e-9a39-935c94a1d017
📒 Files selected for processing (5)
app/components/Package/HealthScore.vueapp/pages/package/[[org]]/[name].vuei18n/locales/ar-EG.jsoni18n/locales/en.jsoni18n/locales/fr-FR.json
…-score # Conflicts: # i18n/locales/ar-EG.json # i18n/locales/fr-FR.json
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 87891e0e-5a96-4709-b93b-cb08a4f02b4b
📒 Files selected for processing (5)
app/components/Package/HealthScore.vueapp/pages/package/[[org]]/[name].vuei18n/locales/ar-EG.jsoni18n/locales/fr-FR.jsonmodules/security-headers.ts
✅ Files skipped from review due to trivial changes (3)
- modules/security-headers.ts
- app/pages/package/[[org]]/[name].vue
- i18n/locales/fr-FR.json
🚧 Files skipped from review as they are similar to previous changes (1)
- i18n/locales/ar-EG.json
| const dimensions = computed(() => { | ||
| if (!data.value?.dimensions) return [] | ||
| const d = data.value.dimensions | ||
| return [ | ||
| { key: 'maintenance', label: $t('package.health_score.dimension_maintenance'), score: d.maintenance?.score ?? 0, weight: d.maintenance?.weight ?? 0 }, | ||
| { key: 'quality', label: $t('package.health_score.dimension_quality'), score: d.quality?.score ?? 0, weight: d.quality?.weight ?? 0 }, | ||
| { key: 'security', label: $t('package.health_score.dimension_security'), score: d.security?.score ?? 0, weight: d.security?.weight ?? 0 }, | ||
| { key: 'popularity', label: $t('package.health_score.dimension_popularity'), score: d.popularity?.score ?? 0, weight: d.popularity?.weight ?? 0 }, | ||
| ] |
There was a problem hiding this comment.
Clamp score values to 0–100 before binding to progress UI.
Scores from a remote API are trusted as-is. If a value is out of range, aria-valuenow and width can become invalid (>100 or <0), causing accessibility and rendering issues.
Suggested fix
+function clampScore(score: number): number {
+ if (!Number.isFinite(score))
+ return 0
+ return Math.max(0, Math.min(100, score))
+}
+
const dimensions = computed(() => {
if (!data.value?.dimensions) return []
const d = data.value.dimensions
return [
- { key: 'maintenance', label: $t('package.health_score.dimension_maintenance'), score: d.maintenance?.score ?? 0, weight: d.maintenance?.weight ?? 0 },
- { key: 'quality', label: $t('package.health_score.dimension_quality'), score: d.quality?.score ?? 0, weight: d.quality?.weight ?? 0 },
- { key: 'security', label: $t('package.health_score.dimension_security'), score: d.security?.score ?? 0, weight: d.security?.weight ?? 0 },
- { key: 'popularity', label: $t('package.health_score.dimension_popularity'), score: d.popularity?.score ?? 0, weight: d.popularity?.weight ?? 0 },
+ { key: 'maintenance', label: $t('package.health_score.dimension_maintenance'), score: clampScore(d.maintenance?.score ?? 0), weight: d.maintenance?.weight ?? 0 },
+ { key: 'quality', label: $t('package.health_score.dimension_quality'), score: clampScore(d.quality?.score ?? 0), weight: d.quality?.weight ?? 0 },
+ { key: 'security', label: $t('package.health_score.dimension_security'), score: clampScore(d.security?.score ?? 0), weight: d.security?.weight ?? 0 },
+ { key: 'popularity', label: $t('package.health_score.dimension_popularity'), score: clampScore(d.popularity?.score ?? 0), weight: d.popularity?.weight ?? 0 },
]
})Also applies to: 134-143
There was a problem hiding this comment.
A few preliminary remarks:
-
This feature should be transparent on the algorithm it uses. We wanted originally to have a custom algorithm that we control, and which we can be fully transparent about. How would you provide more transparency about the way the score is calculated ?
-
The link opens a json file (is this intentional ?)
-
The block containing the score component is not aligned horizontally in the side bar, and should be treated like other sections (inside a dropdown).
- Can you please add tests ?
- Wrap component in CollapsibleSection for proper sidebar alignment - Fix footer link to point to npm Pulse homepage (not raw JSON API) - Add algorithm subtitle showing weights inline for transparency - Add a11y test for PackageHealthScore - Fix formatting to pass vp fmt check - Remove BOM from locale files to fix i18n validator
|
Thanks for the thorough review @graphieros! Here's what I've addressed in the latest commit (88bc6b1): 1. Transparency of the algorithm If you'd prefer a fully self-hosted algorithm built entirely on data already in npmx.dev (downloads, last publish date, vulnerability count, etc.), I'm happy to reimplement it that way — just let me know. 2. Link fixed 3. Alignment 4. Tests |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
test/nuxt/a11y.spec.ts (1)
1231-1239: LGTM!The accessibility test for
PackageHealthScorefollows the established patterns in this file. The test correctly mounts the component with the requiredpackageNameprop and validates there are no axe violations in the loading state.Consider adding test cases for improved coverage in a follow-up:
- Test with the optional
versionprop:props: { packageName: 'vue', version: '3.5.0' }- If API mocking is feasible, tests for the loaded state with score data and the error state would strengthen accessibility coverage further
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: cb72eeaf-c34c-419c-9437-cc6dacc625a8
📒 Files selected for processing (6)
app/components/Package/HealthScore.vueapp/pages/package/[[org]]/[name].vuei18n/locales/ar-EG.jsoni18n/locales/en.jsoni18n/locales/fr-FR.jsontest/nuxt/a11y.spec.ts
✅ Files skipped from review due to trivial changes (4)
- app/pages/package/[[org]]/[name].vue
- i18n/locales/en.json
- i18n/locales/ar-EG.json
- app/components/Package/HealthScore.vue
🚧 Files skipped from review as they are similar to previous changes (1)
- i18n/locales/fr-FR.json
graphieros
left a comment
There was a problem hiding this comment.
While AI tools can be helpful for coding assistance, our project requires genuine human involvement. We've outlined this clearly in our contribution guidelines.
If you're genuinely interested in contributing to npmx.dev:
- Engage with the community first (issues, discussions)
- Understand the codebase and what we actually need
- Submit thoughtful, individual PRs with your own descriptions
- Follow up on feedback and participate in reviews
- Use AI as a tool to assist you, not replace you
As for the actual solution proposed, we would really prefer to opt for an algorithm we control, instead of linking to a paid solution.
Should you be interested in this challenge, while contributing to npmx following our guidelines, you are most welcome to improve your solution.
|
Hey @graphieros fair feedback, and I appreciate the directness. Start by engaging on the existing issues and understanding what the team actually wants from a health score Would it make sense to open a discussion issue first to align on the algorithm before writing code? |
|
Sounds good, as long as it's with your own words. You are welcome to participate. |
Summary
Adds a Package Health Score sidebar widget to every package page, powered by npm Pulse.
CollapsibleSectionmatching the existing sidebar layoutChanges
app/components/Package/HealthScore.vueCollapsibleSectionapp/pages/package/[[org]]/[name].vue<PackageHealthScore>above download statsmodules/security-headers.tshttps://npm-pulse.vercel.apptoconnectSrcCSPi18n/locales/en.jsonpackage.health_scoretranslation keysi18n/locales/ar-EG.jsoni18n/locales/fr-FR.jsoni18n/schema.jsonhealth_scorekeystest/nuxt/a11y.spec.tsPackageHealthScorein loading stateHow it works
The component calls
https://npm-pulse.vercel.app/api/v1/score/{package}and renders:Follows existing patterns: UnoCSS,
var(--fg)/var(--border)CSS custom properties,$t()i18n,useFetchcomposable,CollapsibleSectionwrapper, Lucide icons.