Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/components/Compare/ReplacementSuggestion.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const docUrl = computed(() => {
class="flex items-start gap-2 px-3 py-2 rounded-lg text-sm"
:class="
variant === 'nodep'
? 'bg-amber-500/10 border border-amber-600/30 text-amber-700 dark:text-amber-400'
? 'bg-amber-500/10 border border-amber-600/30 text-amber-800 dark:text-amber-400'
: 'bg-blue-500/10 border border-blue-600/30 text-blue-700 dark:text-blue-400'
"
>
Expand Down
2 changes: 1 addition & 1 deletion app/components/DependencyPathPopup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ function parsePackageString(pkg: string): { name: string; version: string } {
<!-- Path badge button -->
<button
type="button"
class="path-badge font-mono text-3xs px-1.5 py-0.5 rounded bg-amber-500/10 border border-amber-500/30 text-amber-700 dark:text-amber-400 transition-all duration-200 ease-out whitespace-nowrap flex items-center gap-1 hover:bg-amber-500/20 hover:border-amber-500/50"
class="path-badge font-mono text-3xs px-1.5 py-0.5 rounded bg-amber-500/10 border border-amber-500/30 text-amber-800 dark:text-amber-400 transition-all duration-200 ease-out whitespace-nowrap flex items-center gap-1 hover:bg-amber-500/20 hover:border-amber-500/50"
:aria-expanded="isOpen"
@click.stop="togglePopup"
>
Expand Down
2 changes: 1 addition & 1 deletion app/components/Package/Dependencies.vue
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ const numberFormatter = useNumberFormatter()
<LinkBase
v-if="getDeprecatedDepInfo(dep)"
:to="packageRoute(dep, getDeprecatedDepInfo(dep)!.version)"
class="shrink-0 text-purple-500"
class="shrink-0 text-purple-700 dark:text-purple-500"
:title="getDeprecatedDepInfo(dep)!.message"
classicon="i-carbon:warning-hex"
>
Expand Down
16 changes: 8 additions & 8 deletions app/components/Package/MetricsBadges.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const typesHref = computed(() => {
variant="button-secondary"
size="small"
:to="typesHref"
classicon="i-carbon-checkmark"
classicon="i-carbon:checkmark"
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 8, 2026

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for old-style icon syntax (i-carbon-name pattern)
echo "=== Old-style i-carbon-* syntax (hyphens) ==="
rg "i-carbon-[a-z]" --type vue -g '!node_modules' | wc -l
echo "Count of files/lines with old syntax:"
rg "i-carbon-[a-z]" --type vue -g '!node_modules' -l | wc -l

echo ""
echo "=== New-style i-carbon:* syntax (colons) ==="
rg "i-carbon:[a-z]" --type vue -g '!node_modules' | wc -l
echo "Count of files/lines with new syntax:"
rg "i-carbon:[a-z]" --type vue -g '!node_modules' -l | wc -l

echo ""
echo "=== Detailed breakdown of new-style syntax ==="
rg "i-carbon:[a-z]+" --type vue -g '!node_modules' -o | sort | uniq -c | sort -rn | head -20

echo ""
echo "=== Detailed breakdown of old-style syntax ==="
rg "i-carbon-[a-z]+" --type vue -g '!node_modules' -o | sort | uniq -c | sort -rn | head -20

Repository: npmx-dev/npmx.dev

Length of output: 517


🏁 Script executed:

#!/bin/bash
# Check for old-style icon syntax (i-carbon-name pattern)
echo "=== Old-style i-carbon-* syntax (hyphens) in .vue files ==="
rg "i-carbon-[a-z]" -g '*.vue' | wc -l

echo ""
echo "=== New-style i-carbon:* syntax (colons) in .vue files ==="
rg "i-carbon:[a-z]" -g '*.vue' | wc -l

echo ""
echo "=== Sample of new-style syntax matches ==="
rg "i-carbon:[a-z]+" -g '*.vue' -o | sort | uniq -c | sort -rn | head -20

echo ""
echo "=== Sample of old-style syntax matches ==="
rg "i-carbon-[a-z]+" -g '*.vue' -o | sort | uniq -c | sort -rn | head -20

echo ""
echo "=== Check the specific file mentioned ==="
if [ -f "app/components/Package/MetricsBadges.vue" ]; then
  echo "File exists. Checking line 66:"
  sed -n '60,72p' "app/components/Package/MetricsBadges.vue" | cat -n
fi

Repository: npmx-dev/npmx.dev

Length of output: 3334


Update icon syntax to use colons consistently across all Vue components.

The codebase currently has mixed icon syntax: 202 instances of the new colon-based style (i-carbon:checkmark) and 71 instances of the old hyphen-based style (i-carbon-warning). This file correctly uses the new syntax, but other components still use hyphens. Refactor all remaining i-carbon-* patterns to i-carbon:* for consistency.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is an small optimization for UnoCSS preset-icons: for single icon name (without dash) the result is the same, but for long icon names using : instead - will resolve the collection directly, the pattern is i-<collection>[-:]<icon>, and so for i-carbon:a-b-c-d we skip some combinations.

We need to find a way to fix this, it is hard since we can add more and more collections and ppl should use : instead -.

Copy link
Contributor

Choose a reason for hiding this comment

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

@userquin, thank you for the explanation! That's a valuable optimization detail about UnoCSS preset-icons. The direct collection resolution with : for multi-dash icon names makes sense.

Here are several approaches to enforce this pattern consistently:

1. ESLint Custom Rule

Create a custom ESLint rule that detects i-<collection>-<icon> patterns and suggests using : instead. This would catch violations during development.

2. Pre-commit Hook

Add a Git hook that scans staged files for the old pattern:

# .husky/pre-commit or similar
if git diff --cached --name-only | grep -E '\.(vue|ts|js)$' | xargs grep -E 'i-carbon-[a-z]'; then
  echo "❌ Found old icon syntax (i-carbon-name). Please use i-carbon:name instead."
  exit 1
fi

3. Automated Migration Script

I can help create a script to fix all remaining 71 instances automatically. Would you like me to generate a script that:

  • Finds all i-carbon-* patterns
  • Replaces them with i-carbon:*
  • Shows a diff for review before applying?

4. Documentation

Add this to your contributing guidelines or a dedicated icon usage document explaining:

  • The pattern: i-<collection>:<icon-name>
  • Why colons are preferred (UnoCSS optimization)
  • Common collections used in the project

5. CI Check

Add a CI step that fails if old syntax is detected in new code:

- name: Check icon syntax
  run: |
    if grep -r "i-carbon-[a-z]" app/ --include="*.vue"; then
      echo "Old icon syntax detected"
      exit 1
    fi

Would you like me to help implement any of these solutions? I can generate the migration script or create the linting rules for you.

/ᐠ。ꞈ。ᐟ
( thinking )
/> 📝 <\


✏️ Learnings added
Learnt from: userquin
Repo: npmx-dev/npmx.dev PR: 1170
File: app/components/Package/MetricsBadges.vue:66-66
Timestamp: 2026-02-08T15:02:02.232Z
Learning: In Vue components, use colon syntax for UnoCSS preset-icons (e.g., `i-carbon:checkmark` instead of `i-carbon-checkmark`). For long icon names with dashes, the colon syntax provides a performance optimization by helping UnoCSS resolve the collection directly without trying different combinations.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: alexdln
Repo: npmx-dev/npmx.dev PR: 838
File: app/pages/package/[...package].vue:445-449
Timestamp: 2026-02-03T13:59:33.392Z
Learning: The copy button pattern in app/pages/package/[...package].vue may be made into a reusable component or pattern in the future, but currently it's acceptable to keep it inline with the CSS-only approach for smooth animations.

Learnt from: skaldebane
Repo: npmx-dev/npmx.dev PR: 760
File: app/components/Compare/FacetCard.vue:134-136
Timestamp: 2026-02-06T07:13:58.289Z
Learning: In `app/components/Compare/FacetCard.vue`, the facet values use `dir="auto"` rather than `dir="ltr"` because the values are not always numbers/sizes and can contain mixed content types.

Copy link
Contributor Author

@userquin userquin Feb 8, 2026

Choose a reason for hiding this comment

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

you're killing everyone, there are still a lot of icons using i-<collection>-<icon>, and Windows users won't be able to lint on local (like me)

Copy link
Contributor

Choose a reason for hiding this comment

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

@userquin, I sincerely apologize! You're absolutely right—I got carried away with enforcement suggestions that would be disruptive and not Windows-friendly.

Let me take a much more practical approach:

Gradual, Optional Migration

Instead of breaking builds or forcing everyone to use bash hooks, here are gentler options:

1. One-time migration script (Node.js/JavaScript)

I can create a cross-platform Node.js script that you can run once to migrate all remaining old-syntax icons. This would be:

  • Completely optional
  • Works on Windows, Mac, Linux
  • Shows a diff before applying changes
  • Can be run at your convenience

2. Documentation only

Simply document the preferred syntax in your contributing guide for future reference, without any enforcement. New code naturally adopts it over time.

3. Optional VS Code snippet

Add a VS Code snippet that auto-completes with the colon syntax, making it easier for contributors to use the right pattern without forcing it.

Would you like me to create a Node.js migration script that you can review and run when convenient? It would be a simple, non-intrusive way to update the remaining 71 instances without impacting anyone's workflow.

Again, sorry for the overly aggressive suggestions!

/ᐠ - ˕ -マ
( sorry! )
/> 💨 <\


🧠 Learnings used
Learnt from: userquin
Repo: npmx-dev/npmx.dev PR: 1170
File: app/components/Package/MetricsBadges.vue:66-66
Timestamp: 2026-02-08T15:02:02.232Z
Learning: In Vue components, use colon syntax for UnoCSS preset-icons (e.g., `i-carbon:checkmark` instead of `i-carbon-checkmark`). For long icon names with dashes, the colon syntax provides a performance optimization by helping UnoCSS resolve the collection directly without trying different combinations.

Learnt from: alexdln
Repo: npmx-dev/npmx.dev PR: 838
File: app/pages/package/[...package].vue:445-449
Timestamp: 2026-02-03T13:59:33.392Z
Learning: The copy button pattern in app/pages/package/[...package].vue may be made into a reusable component or pattern in the future, but currently it's acceptable to keep it inline with the CSS-only approach for smooth animations.

Learnt from: skaldebane
Repo: npmx-dev/npmx.dev PR: 760
File: app/components/Compare/FacetCard.vue:134-136
Timestamp: 2026-02-06T07:13:58.289Z
Learning: In `app/components/Compare/FacetCard.vue`, the facet values use `dir="auto"` rather than `dir="ltr"` because the values are not always numbers/sizes and can contain mixed content types.

>
{{ $t('package.metrics.types_label') }}
</LinkBase>
Expand All @@ -73,10 +73,10 @@ const typesHref = computed(() => {
:tabindex="0"
:classicon="
isLoading
? 'i-carbon-circle-dash motion-safe:animate-spin'
? 'i-carbon:circle-dash motion-safe:animate-spin'
: hasTypes
? 'i-carbon-checkmark'
: 'i-carbon-close'
? 'i-carbon:checkmark'
: 'i-carbon:close'
"
>
{{ $t('package.metrics.types_label') }}
Expand All @@ -94,10 +94,10 @@ const typesHref = computed(() => {
:variant="hasEsm && !isLoading ? 'default' : 'ghost'"
:classicon="
isLoading
? 'i-carbon-circle-dash motion-safe:animate-spin'
? 'i-carbon:circle-dash motion-safe:animate-spin'
: hasEsm
? 'i-carbon-checkmark'
: 'i-carbon-close'
? 'i-carbon:checkmark'
: 'i-carbon:close'
"
>
ESM
Expand All @@ -112,7 +112,7 @@ const typesHref = computed(() => {
tabindex="0"
:variant="isLoading ? 'ghost' : 'default'"
:classicon="
isLoading ? 'i-carbon-circle-dash motion-safe:animate-spin' : 'i-carbon-checkmark'
isLoading ? 'i-carbon:circle-dash motion-safe:animate-spin' : 'i-carbon:checkmark'
"
>
CJS
Expand Down
2 changes: 1 addition & 1 deletion app/components/Package/Replacement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const docPath = computed(() => {

<template>
<div
class="border border-amber-600/40 bg-amber-500/10 rounded-lg px-3 py-2 text-base text-amber-700 dark:text-amber-400"
class="border border-amber-600/40 bg-amber-500/10 rounded-lg px-3 py-2 text-base text-amber-800 dark:text-amber-400"
>
<h2 class="font-medium mb-1 flex items-center gap-2">
<span class="i-carbon-idea w-4 h-4" aria-hidden="true" />
Expand Down
24 changes: 18 additions & 6 deletions app/components/Package/Versions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,9 @@ function getTagVersions(tag: string): VersionDisplay[] {
block
class="text-sm"
:class="
row.primaryVersion.deprecated ? 'text-red-400 hover:text-red-300' : undefined
row.primaryVersion.deprecated
? 'text-red-800 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300'
: undefined
"
:title="
row.primaryVersion.deprecated
Expand Down Expand Up @@ -415,7 +417,11 @@ function getTagVersions(tag: string): VersionDisplay[] {
:to="versionRoute(v.version)"
block
class="text-xs"
:class="v.deprecated ? 'text-red-400 hover:text-red-300' : undefined"
:class="
v.deprecated
? 'text-red-800 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300'
: undefined
"
:title="
v.deprecated
? $t('package.versions.deprecated_title', { version: v.version })
Expand Down Expand Up @@ -514,7 +520,9 @@ function getTagVersions(tag: string): VersionDisplay[] {
block
class="text-xs"
:class="
row.primaryVersion.deprecated ? 'text-red-400 hover:text-red-300' : undefined
row.primaryVersion.deprecated
? 'text-red-800 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300'
: undefined
"
:title="
row.primaryVersion.deprecated
Expand Down Expand Up @@ -588,7 +596,7 @@ function getTagVersions(tag: string): VersionDisplay[] {
class="text-xs"
:class="
group.versions[0]?.deprecated
? 'text-red-400 hover:text-red-300'
? 'text-red-800 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300'
: undefined
"
:title="
Expand Down Expand Up @@ -649,7 +657,7 @@ function getTagVersions(tag: string): VersionDisplay[] {
class="text-xs ms-6"
:class="
group.versions[0]?.deprecated
? 'text-red-400 hover:text-red-300'
? 'text-red-800 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300'
: undefined
"
:title="
Expand Down Expand Up @@ -707,7 +715,11 @@ function getTagVersions(tag: string): VersionDisplay[] {
:to="versionRoute(v.version)"
block
class="text-xs"
:class="v.deprecated ? 'text-red-400 hover:text-red-300' : undefined"
:class="
v.deprecated
? 'text-red-800 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300'
: undefined
"
:title="
v.deprecated
? $t('package.versions.deprecated_title', { version: v.version })
Expand Down
4 changes: 2 additions & 2 deletions app/components/Package/VulnerabilityTree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const hasVulnerabilities = computed(
)
// Banner - amber for better light mode contrast
const bannerColor = 'border-amber-600/40 bg-amber-500/10 text-amber-700 dark:text-amber-400'
const bannerColor = 'border-amber-600/40 bg-amber-500/10 text-amber-800 dark:text-amber-400'
const severityLabels = computed(() => ({
critical: $t('package.vulnerabilities.severity.critical'),
Expand Down Expand Up @@ -100,7 +100,7 @@ function getDepthStyle(depth: string | undefined) {
</span>
</span>
<span class="flex items-center gap-2 shrink-0">
<span class="text-xs opacity-80 hidden sm:inline">{{ summaryText }}</span>
<span class="text-xs op-90 dark:op-80 hidden sm:inline">{{ summaryText }}</span>
<span
class="i-carbon:chevron-down w-4 h-4 transition-transform duration-200"
:class="{ 'rotate-180': isExpanded }"
Expand Down
4 changes: 2 additions & 2 deletions app/components/Tag/Static.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ const props = withDefaults(
<template>
<component
:is="as"
class="bg-bg-muted text-fg-muted inline-flex gap-x-1 items-center justify-center font-mono border border-transparent rounded-md text-xs px-2 py-0.5"
:class="{ 'opacity-40': variant === 'ghost' }"
class="bg-bg-muted text-fg-muted inline-flex gap-x-1 items-center justify-center font-mono border border-gray-400 dark:border-gray-500 rounded-md text-xs px-2 py-0.5"
:class="{ 'opacity-80 border-dashed': variant === 'ghost' }"
>
<span v-if="classicon" class="size-[1em]" :class="classicon" aria-hidden="true" />
<slot />
Expand Down
1 change: 0 additions & 1 deletion app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ defineOgImageComponent('Default', {
>
{{ $t('tagline') }}
</p>

<search
class="w-full max-w-xl motion-safe:animate-slide-up motion-safe:animate-fill-both"
style="animation-delay: 0.2s"
Expand Down
4 changes: 2 additions & 2 deletions app/pages/package/[[org]]/[name].vue
Original file line number Diff line number Diff line change
Expand Up @@ -801,7 +801,7 @@ onKeyStroke(

<div
v-if="deprecationNotice"
class="border border-red-400 bg-red-400/10 rounded-lg px-3 py-2 text-base text-red-400"
class="border border-red-700 dark:border-red-400 bg-red-400/10 rounded-lg px-3 py-2 text-base text-red-700 dark:text-red-400"
>
<h2 class="font-medium mb-2">
{{
Expand Down Expand Up @@ -952,7 +952,7 @@ onKeyStroke(
/>
</span>
<span v-else-if="vulnTreeStatus === 'success'">
<span v-if="hasVulnerabilities" class="text-amber-500">
<span v-if="hasVulnerabilities" class="text-amber-700 dark:text-amber-500">
{{ numberFormatter.format(vulnCount) }}
</span>
<span v-else class="inline-flex items-center gap-1 text-fg-muted">
Expand Down
9 changes: 5 additions & 4 deletions app/utils/npm/outdated-dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,13 @@ export function getVersionClass(info: OutdatedDependencyInfo | undefined): strin
if (!info) return 'text-fg-subtle'
// Green for up-to-date (e.g. "latest" constraint)
if (info.majorsBehind === 0 && info.minorsBehind === 0 && info.resolved === info.latest) {
return 'text-green-500 cursor-help'
return 'text-green-700 dark:text-green-500 cursor-help'
}
// Red for major versions behind
if (info.majorsBehind > 0) return 'text-red-500 cursor-help'
if (info.majorsBehind > 0) return 'text-red-700 dark:text-red-500 cursor-help'
// if (info.majorsBehind > 0) return 'text-#db0000 dark:text-red-500 cursor-help'
// Orange for minor versions behind
if (info.minorsBehind > 0) return 'text-orange-500 cursor-help'
if (info.minorsBehind > 0) return 'text-orange-700 dark:text-orange-500 cursor-help'
// Yellow for patch versions behind
return 'text-yellow-500 cursor-help'
return 'text-yellow-700 dark:text-yellow-500 cursor-help'
}
26 changes: 25 additions & 1 deletion server/utils/shiki.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
import type { ThemeRegistration } from 'shiki'
import { createHighlighterCore, type HighlighterCore } from 'shiki/core'
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'

let highlighter: HighlighterCore | null = null

function replaceThemeColors(
theme: ThemeRegistration,
replacements: Record<string, string>,
): ThemeRegistration {
let themeString = JSON.stringify(theme)
for (const [oldColor, newColor] of Object.entries(replacements)) {
themeString = themeString.replaceAll(oldColor, newColor)
themeString = themeString.replaceAll(oldColor.toLowerCase(), newColor)
themeString = themeString.replaceAll(oldColor.toUpperCase(), newColor)
}
return JSON.parse(themeString)
}

export async function getShikiHighlighter(): Promise<HighlighterCore> {
if (!highlighter) {
highlighter = await createHighlighterCore({
themes: [import('@shikijs/themes/github-dark'), import('@shikijs/themes/github-light')],
themes: [
import('@shikijs/themes/github-dark'),
import('@shikijs/themes/github-light').then(t =>
replaceThemeColors(t.default ?? t, {
'#22863A': '#227436', // green
'#E36209': '#BA4D02', // orange
'#D73A49': '#CD3443', // red
'#B31D28': '#AC222F', // red
}),
),
],
langs: [
// Core web languages
import('@shikijs/langs/javascript'),
Expand Down
17 changes: 9 additions & 8 deletions shared/utils/severity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@ import { SEVERITY_LEVELS } from '../types'
* Color classes for severity levels (banner style)
*/
export const SEVERITY_COLORS: Record<OsvSeverityLevel, string> = {
critical: 'text-red-500 bg-red-500/10 border-red-500/50',
high: 'text-red-400 bg-red-500/10 border-red-500/30',
moderate: 'text-orange-400 bg-orange-500/10 border-orange-500/30',
low: 'text-yellow-400 bg-yellow-500/10 border-yellow-500/30',
critical: 'text-red-700 dark:text-red-500 bg-red-800/10 dark:bg-red-500/10 border-red-500/50',
high: 'text-red-800 dark:text-red-400 bg-red-800/10 dark:bg-red-500/10 border-red-500/30',
moderate:
'text-orange-800 dark:text-orange-400 bg-orange-600/10 dark:bg-orange-500/10 border-orange-500/30',
low: 'text-yellow-700 dark:text-yellow-400 bg-yellow-500/10 border-yellow-500/30',
unknown: 'text-fg-muted bg-bg-subtle border-border',
}

/**
* Color classes for inline severity indicators
*/
export const SEVERITY_TEXT_COLORS: Record<OsvSeverityLevel, string> = {
critical: 'text-red-500',
high: 'text-orange-500',
moderate: 'text-yellow-500',
low: 'text-blue-500',
critical: 'text-red-700 dark:text-red-500',
high: 'text-orange-700 dark:text-orange-500',
moderate: 'text-yellow-700 dark:text-yellow-500',
low: 'text-blue-700 dark:text-blue-500',
unknown: 'text-fg-subtle',
}

Expand Down
Loading
Loading