From e2a5537b36ac0cfd0accd3d203d56a009bd583d6 Mon Sep 17 00:00:00 2001 From: Sukiu <17344148+Sukiiu@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:21:19 +0200 Subject: [PATCH 1/8] feat: compare by likes --- app/composables/usePackageComparison.ts | 18 ++++++++++++++++-- i18n/locales/en.json | 3 +++ lunaria/files/en-GB.json | 3 +++ lunaria/files/en-US.json | 3 +++ shared/types/comparison.ts | 4 ++++ 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/app/composables/usePackageComparison.ts b/app/composables/usePackageComparison.ts index 690bff4da..49fdbd671 100644 --- a/app/composables/usePackageComparison.ts +++ b/app/composables/usePackageComparison.ts @@ -44,6 +44,8 @@ export interface PackageComparisonData { } /** Whether this is a binary-only package (CLI without library entry points) */ isBinaryOnly?: boolean + /** Total likes from atproto */ + totalLikes: number } /** @@ -104,7 +106,7 @@ export function usePackageComparison(packageNames: MaybeRefOrGetter) { if (!latestVersion) return null // Fetch fast additional data in parallel (optional - failures are ok) - const [downloads, analysis, vulns] = await Promise.all([ + const [downloads, analysis, vulns, likes] = await Promise.all([ $fetch<{ downloads: number }>( `https://api.npmjs.org/downloads/point/last-week/${encodePackageName(name)}`, ).catch(() => null), @@ -112,8 +114,11 @@ export function usePackageComparison(packageNames: MaybeRefOrGetter) { $fetch(`/api/registry/vulnerabilities/${name}`).catch( () => null, ), + $fetch(`/api/social/likes/${name}`).catch(() => ({ + totalLikes: 0, + userHasLiked: false, + })), ]) - const versionData = pkgData.versions[latestVersion] const packageSize = versionData?.dist?.unpackedSize @@ -160,6 +165,7 @@ export function usePackageComparison(packageNames: MaybeRefOrGetter) { deprecated: versionData?.deprecated, }, isBinaryOnly: isBinary, + totalLikes: likes.totalLikes, } } catch { return null @@ -384,6 +390,14 @@ function computeFacetValue( status: isDeprecated ? 'bad' : 'good', } } + case 'totalLikes': { + if (data.totalLikes === undefined) return null + return { + raw: data.totalLikes, + display: formatCompactNumber(data.totalLikes), + status: 'neutral', + } + } // Coming soon facets case 'totalDependencies': { if (!data.installSize) return null diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 7518b487c..7f40c2b1c 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -935,6 +935,9 @@ "vulnerabilities": { "label": "Vulnerabilities", "description": "Known security vulnerabilities" + }, + "totalLikes": { + "label": "Likes" } }, "values": { diff --git a/lunaria/files/en-GB.json b/lunaria/files/en-GB.json index 583ca875d..b072674b7 100644 --- a/lunaria/files/en-GB.json +++ b/lunaria/files/en-GB.json @@ -935,6 +935,9 @@ "vulnerabilities": { "label": "Vulnerabilities", "description": "Known security vulnerabilities" + }, + "totalLikes": { + "label": "Likes" } }, "values": { diff --git a/lunaria/files/en-US.json b/lunaria/files/en-US.json index 7518b487c..7f40c2b1c 100644 --- a/lunaria/files/en-US.json +++ b/lunaria/files/en-US.json @@ -935,6 +935,9 @@ "vulnerabilities": { "label": "Vulnerabilities", "description": "Known security vulnerabilities" + }, + "totalLikes": { + "label": "Likes" } }, "values": { diff --git a/shared/types/comparison.ts b/shared/types/comparison.ts index ec3113f0f..6bc25c8a0 100644 --- a/shared/types/comparison.ts +++ b/shared/types/comparison.ts @@ -16,6 +16,7 @@ export type ComparisonFacet = | 'dependencies' | 'totalDependencies' | 'deprecated' + | 'totalLikes' /** Facet metadata for UI display */ export interface FacetInfo { @@ -52,6 +53,9 @@ export const FACET_INFO: Record> = { deprecated: { category: 'health', }, + totalLikes: { + category: 'health', + }, // Compatibility engines: { category: 'compatibility', From b63e1ea623a3cbea8de81c9f03f863e9bb98b675 Mon Sep 17 00:00:00 2001 From: Sukiu <17344148+Sukiiu@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:28:07 +0200 Subject: [PATCH 2/8] fix: move next to down/weeks --- app/composables/usePackageComparison.ts | 20 ++++++++++---------- i18n/locales/en.json | 7 ++++--- i18n/locales/fr-FR.json | 4 ++++ lunaria/files/en-GB.json | 7 ++++--- lunaria/files/en-US.json | 7 ++++--- lunaria/files/fr-FR.json | 4 ++++ shared/types/comparison.ts | 6 +++--- 7 files changed, 33 insertions(+), 22 deletions(-) diff --git a/app/composables/usePackageComparison.ts b/app/composables/usePackageComparison.ts index 49fdbd671..345109cdc 100644 --- a/app/composables/usePackageComparison.ts +++ b/app/composables/usePackageComparison.ts @@ -14,6 +14,8 @@ import { getDependencyCount } from '~/utils/npm/dependency-count' export interface PackageComparisonData { package: ComparisonPackage downloads?: number + /** Total likes from atproto */ + totalLikes: number /** Package's own unpacked size (from dist.unpackedSize) */ packageSize?: number /** Number of direct dependencies */ @@ -44,8 +46,6 @@ export interface PackageComparisonData { } /** Whether this is a binary-only package (CLI without library entry points) */ isBinaryOnly?: boolean - /** Total likes from atproto */ - totalLikes: number } /** @@ -275,6 +275,14 @@ function computeFacetValue( status: 'neutral', } } + case 'totalLikes': { + if (data.totalLikes === undefined) return null + return { + raw: data.totalLikes, + display: formatCompactNumber(data.totalLikes), + status: 'neutral', + } + } case 'packageSize': { if (!data.packageSize) return null return { @@ -390,14 +398,6 @@ function computeFacetValue( status: isDeprecated ? 'bad' : 'good', } } - case 'totalLikes': { - if (data.totalLikes === undefined) return null - return { - raw: data.totalLikes, - display: formatCompactNumber(data.totalLikes), - status: 'neutral', - } - } // Coming soon facets case 'totalDependencies': { if (!data.installSize) return null diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 7f40c2b1c..3077eeb83 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -908,6 +908,10 @@ "label": "Downloads/wk", "description": "Weekly download count" }, + "totalLikes": { + "label": "Likes", + "description": "Number of likes" + }, "lastUpdated": { "label": "Published", "description": "When this version was published" @@ -935,9 +939,6 @@ "vulnerabilities": { "label": "Vulnerabilities", "description": "Known security vulnerabilities" - }, - "totalLikes": { - "label": "Likes" } }, "values": { diff --git a/i18n/locales/fr-FR.json b/i18n/locales/fr-FR.json index 6bfaa114e..2b3e34c3b 100644 --- a/i18n/locales/fr-FR.json +++ b/i18n/locales/fr-FR.json @@ -891,6 +891,10 @@ "label": "Téléch./semaine", "description": "Nombre de téléchargements par semaine" }, + "totalLikes": { + "label": "Likes", + "description": "Nombre de likes" + }, "lastUpdated": { "label": "Publié", "description": "Quand cette version a été publiée" diff --git a/lunaria/files/en-GB.json b/lunaria/files/en-GB.json index b072674b7..d84d57e08 100644 --- a/lunaria/files/en-GB.json +++ b/lunaria/files/en-GB.json @@ -908,6 +908,10 @@ "label": "Downloads/wk", "description": "Weekly download count" }, + "totalLikes": { + "label": "Likes", + "description": "Number of likes" + }, "lastUpdated": { "label": "Published", "description": "When this version was published" @@ -935,9 +939,6 @@ "vulnerabilities": { "label": "Vulnerabilities", "description": "Known security vulnerabilities" - }, - "totalLikes": { - "label": "Likes" } }, "values": { diff --git a/lunaria/files/en-US.json b/lunaria/files/en-US.json index 7f40c2b1c..3077eeb83 100644 --- a/lunaria/files/en-US.json +++ b/lunaria/files/en-US.json @@ -908,6 +908,10 @@ "label": "Downloads/wk", "description": "Weekly download count" }, + "totalLikes": { + "label": "Likes", + "description": "Number of likes" + }, "lastUpdated": { "label": "Published", "description": "When this version was published" @@ -935,9 +939,6 @@ "vulnerabilities": { "label": "Vulnerabilities", "description": "Known security vulnerabilities" - }, - "totalLikes": { - "label": "Likes" } }, "values": { diff --git a/lunaria/files/fr-FR.json b/lunaria/files/fr-FR.json index 6bfaa114e..2b3e34c3b 100644 --- a/lunaria/files/fr-FR.json +++ b/lunaria/files/fr-FR.json @@ -891,6 +891,10 @@ "label": "Téléch./semaine", "description": "Nombre de téléchargements par semaine" }, + "totalLikes": { + "label": "Likes", + "description": "Nombre de likes" + }, "lastUpdated": { "label": "Publié", "description": "Quand cette version a été publiée" diff --git a/shared/types/comparison.ts b/shared/types/comparison.ts index 6bc25c8a0..9dfe229eb 100644 --- a/shared/types/comparison.ts +++ b/shared/types/comparison.ts @@ -47,13 +47,13 @@ export const FACET_INFO: Record> = { downloads: { category: 'health', }, - lastUpdated: { + totalLikes: { category: 'health', }, - deprecated: { + lastUpdated: { category: 'health', }, - totalLikes: { + deprecated: { category: 'health', }, // Compatibility From f87f35abf60ad1cdcd5760878f4e64e22ff0948b Mon Sep 17 00:00:00 2001 From: Sukiu <17344148+Sukiiu@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:44:47 +0200 Subject: [PATCH 3/8] fix: missing import --- app/composables/usePackageComparison.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/composables/usePackageComparison.ts b/app/composables/usePackageComparison.ts index 345109cdc..8cc4d9d75 100644 --- a/app/composables/usePackageComparison.ts +++ b/app/composables/usePackageComparison.ts @@ -5,6 +5,7 @@ import type { Packument, VulnerabilityTreeResult, } from '#shared/types' +import type { PackageLikes } from '#shared/types/social' import { encodePackageName } from '#shared/utils/npm' import type { PackageAnalysisResponse } from './usePackageAnalysis' import { isBinaryOnlyPackage } from '#shared/utils/binary-detection' From 6960685b6af71ea9af981edfd1c7c7a6dd072f81 Mon Sep 17 00:00:00 2001 From: Sukiu <17344148+Sukiiu@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:52:29 +0200 Subject: [PATCH 4/8] fix: follow the same failure pattern --- app/composables/usePackageComparison.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/composables/usePackageComparison.ts b/app/composables/usePackageComparison.ts index 8cc4d9d75..444914650 100644 --- a/app/composables/usePackageComparison.ts +++ b/app/composables/usePackageComparison.ts @@ -16,7 +16,7 @@ export interface PackageComparisonData { package: ComparisonPackage downloads?: number /** Total likes from atproto */ - totalLikes: number + totalLikes?: number /** Package's own unpacked size (from dist.unpackedSize) */ packageSize?: number /** Number of direct dependencies */ @@ -115,10 +115,7 @@ export function usePackageComparison(packageNames: MaybeRefOrGetter) { $fetch(`/api/registry/vulnerabilities/${name}`).catch( () => null, ), - $fetch(`/api/social/likes/${name}`).catch(() => ({ - totalLikes: 0, - userHasLiked: false, - })), + $fetch(`/api/social/likes/${name}`).catch(() => null), ]) const versionData = pkgData.versions[latestVersion] const packageSize = versionData?.dist?.unpackedSize @@ -166,7 +163,7 @@ export function usePackageComparison(packageNames: MaybeRefOrGetter) { deprecated: versionData?.deprecated, }, isBinaryOnly: isBinary, - totalLikes: likes.totalLikes, + totalLikes: likes?.totalLikes, } } catch { return null From acbb010bd992daf6eb8d39dcdd716b7ff2dd90f2 Mon Sep 17 00:00:00 2001 From: Sukiu <17344148+Sukiiu@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:53:25 +0200 Subject: [PATCH 5/8] fix(test): add totalLikes facet label --- test/nuxt/components/compare/FacetSelector.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/nuxt/components/compare/FacetSelector.spec.ts b/test/nuxt/components/compare/FacetSelector.spec.ts index 6d98e87ae..9dc2757e6 100644 --- a/test/nuxt/components/compare/FacetSelector.spec.ts +++ b/test/nuxt/components/compare/FacetSelector.spec.ts @@ -31,6 +31,7 @@ const facetLabels: Record = { From 3526acf21a11f7472eb38882e881aa8f6c13cf81 Mon Sep 17 00:00:00 2001 From: Sukiu <17344148+Sukiiu@users.noreply.github.com> Date: Fri, 6 Feb 2026 00:41:53 +0200 Subject: [PATCH 6/8] fix: add likes to noDepData --- app/composables/usePackageComparison.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/composables/usePackageComparison.ts b/app/composables/usePackageComparison.ts index 07616e7a0..4d4217acb 100644 --- a/app/composables/usePackageComparison.ts +++ b/app/composables/usePackageComparison.ts @@ -303,6 +303,7 @@ function createNoDependencyData(): PackageComparisonData { }, isNoDependency: true, downloads: undefined, + totalLikes: undefined, packageSize: 0, directDeps: 0, installSize: { From 5171b7ee9ad1efbb4c8a5726cc458f2251f5f9af Mon Sep 17 00:00:00 2001 From: Sukiu <17344148+Sukiiu@users.noreply.github.com> Date: Sat, 7 Feb 2026 12:08:27 +0200 Subject: [PATCH 7/8] fix: add totalLikes to useFacetSelection --- app/composables/useFacetSelection.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/composables/useFacetSelection.ts b/app/composables/useFacetSelection.ts index 31d503119..ebb79f5ae 100644 --- a/app/composables/useFacetSelection.ts +++ b/app/composables/useFacetSelection.ts @@ -28,6 +28,10 @@ export function useFacetSelection(queryParam = 'facets') { label: t(`compare.facets.items.downloads.label`), description: t(`compare.facets.items.downloads.description`), }, + totalLikes: { + label: t(`compare.facets.items.totalLikes.label`), + description: t(`compare.facets.items.totalLikes.description`), + }, packageSize: { label: t(`compare.facets.items.packageSize.label`), description: t(`compare.facets.items.packageSize.description`), From aafd1406f05c78318602c2c2e1cccb81a8c3de95 Mon Sep 17 00:00:00 2001 From: Sukiu <17344148+Sukiiu@users.noreply.github.com> Date: Sat, 7 Feb 2026 12:17:47 +0200 Subject: [PATCH 8/8] fix: add type to faceLabels --- app/composables/useFacetSelection.ts | 110 ++++++++++++++------------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/app/composables/useFacetSelection.ts b/app/composables/useFacetSelection.ts index ebb79f5ae..ecb014b60 100644 --- a/app/composables/useFacetSelection.ts +++ b/app/composables/useFacetSelection.ts @@ -23,60 +23,62 @@ export interface FacetInfoWithLabels extends Omit { export function useFacetSelection(queryParam = 'facets') { const { t } = useI18n() - const facetLabels = computed(() => ({ - downloads: { - label: t(`compare.facets.items.downloads.label`), - description: t(`compare.facets.items.downloads.description`), - }, - totalLikes: { - label: t(`compare.facets.items.totalLikes.label`), - description: t(`compare.facets.items.totalLikes.description`), - }, - packageSize: { - label: t(`compare.facets.items.packageSize.label`), - description: t(`compare.facets.items.packageSize.description`), - }, - installSize: { - label: t(`compare.facets.items.installSize.label`), - description: t(`compare.facets.items.installSize.description`), - }, - moduleFormat: { - label: t(`compare.facets.items.moduleFormat.label`), - description: t(`compare.facets.items.moduleFormat.description`), - }, - types: { - label: t(`compare.facets.items.types.label`), - description: t(`compare.facets.items.types.description`), - }, - engines: { - label: t(`compare.facets.items.engines.label`), - description: t(`compare.facets.items.engines.description`), - }, - vulnerabilities: { - label: t(`compare.facets.items.vulnerabilities.label`), - description: t(`compare.facets.items.vulnerabilities.description`), - }, - lastUpdated: { - label: t(`compare.facets.items.lastUpdated.label`), - description: t(`compare.facets.items.lastUpdated.description`), - }, - license: { - label: t(`compare.facets.items.license.label`), - description: t(`compare.facets.items.license.description`), - }, - dependencies: { - label: t(`compare.facets.items.dependencies.label`), - description: t(`compare.facets.items.dependencies.description`), - }, - totalDependencies: { - label: t(`compare.facets.items.totalDependencies.label`), - description: t(`compare.facets.items.totalDependencies.description`), - }, - deprecated: { - label: t(`compare.facets.items.deprecated.label`), - description: t(`compare.facets.items.deprecated.description`), - }, - })) + const facetLabels = computed( + (): Record => ({ + downloads: { + label: t(`compare.facets.items.downloads.label`), + description: t(`compare.facets.items.downloads.description`), + }, + totalLikes: { + label: t(`compare.facets.items.totalLikes.label`), + description: t(`compare.facets.items.totalLikes.description`), + }, + packageSize: { + label: t(`compare.facets.items.packageSize.label`), + description: t(`compare.facets.items.packageSize.description`), + }, + installSize: { + label: t(`compare.facets.items.installSize.label`), + description: t(`compare.facets.items.installSize.description`), + }, + moduleFormat: { + label: t(`compare.facets.items.moduleFormat.label`), + description: t(`compare.facets.items.moduleFormat.description`), + }, + types: { + label: t(`compare.facets.items.types.label`), + description: t(`compare.facets.items.types.description`), + }, + engines: { + label: t(`compare.facets.items.engines.label`), + description: t(`compare.facets.items.engines.description`), + }, + vulnerabilities: { + label: t(`compare.facets.items.vulnerabilities.label`), + description: t(`compare.facets.items.vulnerabilities.description`), + }, + lastUpdated: { + label: t(`compare.facets.items.lastUpdated.label`), + description: t(`compare.facets.items.lastUpdated.description`), + }, + license: { + label: t(`compare.facets.items.license.label`), + description: t(`compare.facets.items.license.description`), + }, + dependencies: { + label: t(`compare.facets.items.dependencies.label`), + description: t(`compare.facets.items.dependencies.description`), + }, + totalDependencies: { + label: t(`compare.facets.items.totalDependencies.label`), + description: t(`compare.facets.items.totalDependencies.description`), + }, + deprecated: { + label: t(`compare.facets.items.deprecated.label`), + description: t(`compare.facets.items.deprecated.description`), + }, + }), + ) // Helper to build facet info with i18n labels function buildFacetInfo(facet: ComparisonFacet): FacetInfoWithLabels {