From 201d4f14d150a51c2305b5829cde88eab64a8546 Mon Sep 17 00:00:00 2001 From: userquin Date: Mon, 23 Feb 2026 04:32:36 +0100 Subject: [PATCH 01/18] feat: add contributors popup card --- app/components/Link/Base.vue | 11 +- app/pages/about.vue | 361 +++++++++++++++++++++++++-------- i18n/locales/en.json | 5 +- i18n/locales/es.json | 3 +- i18n/schema.json | 3 + lunaria/files/en-GB.json | 5 +- lunaria/files/en-US.json | 5 +- lunaria/files/es-419.json | 3 +- lunaria/files/es-ES.json | 3 +- server/api/contributors.get.ts | 91 +++++++-- 10 files changed, 377 insertions(+), 113 deletions(-) diff --git a/app/components/Link/Base.vue b/app/components/Link/Base.vue index 2dfb45ef9..bbac381c7 100644 --- a/app/components/Link/Base.vue +++ b/app/components/Link/Base.vue @@ -36,9 +36,16 @@ const props = withDefaults( /** should only be used for links where the context makes it very clear they are clickable. Don't just use this, because you don't like underlines. */ noUnderline?: boolean + + /** + * should external link icon be displayed?. + * + * @default false + */ + noExternalIcon?: boolean } & NuxtLinkProps >(), - { variant: 'link', size: 'medium' }, + { variant: 'link', size: 'medium', noUnderline: false, noExternalIcon: false }, ) const isLinkExternal = computed( @@ -101,7 +108,7 @@ const isButtonMedium = computed(() => props.size === 'medium' && !isLink.value)
{{ roleLabels[activeContributor.role] }} @@ -371,8 +371,9 @@ function onMouseLeave() { > "{{ activeContributor.bio }}"

+
+ +
+
From 85439ec914526fbdd741c0757fc64a7bd00df25c Mon Sep 17 00:00:00 2001 From: userquin Date: Mon, 23 Feb 2026 05:26:18 +0100 Subject: [PATCH 05/18] chore: apply coderabbitai suggestion for noExternalIcon jsdoc --- app/components/Link/Base.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/Link/Base.vue b/app/components/Link/Base.vue index 7d156f9c4..679810bb3 100644 --- a/app/components/Link/Base.vue +++ b/app/components/Link/Base.vue @@ -37,7 +37,7 @@ const props = withDefaults( noUnderline?: boolean /** - * should external link icon be displayed?. + * When `true`, suppresses the external-link icon even for external `to` URLs. * * @default false */ From 9769b80c8775f328ad3e4e0f0da317186df6f069 Mon Sep 17 00:00:00 2001 From: userquin Date: Mon, 23 Feb 2026 05:32:41 +0100 Subject: [PATCH 06/18] chore: remove `about.team.governance` from json file and add `about.team.sponsor_aria` to sponsor anchor --- app/pages/about.vue | 1 + i18n/locales/en.json | 1 - i18n/locales/es.json | 1 - i18n/schema.json | 3 --- lunaria/files/en-GB.json | 1 - lunaria/files/en-US.json | 1 - lunaria/files/es-419.json | 1 - lunaria/files/es-ES.json | 1 - 8 files changed, 1 insertion(+), 9 deletions(-) diff --git a/app/pages/about.vue b/app/pages/about.vue index d491bcd45..fc6f900ae 100644 --- a/app/pages/about.vue +++ b/app/pages/about.vue @@ -446,6 +446,7 @@ function onMouseLeave() { Date: Mon, 23 Feb 2026 04:34:23 +0000 Subject: [PATCH 07/18] [autofix.ci] apply automated fixes --- i18n/locales/ar.json | 1 - i18n/locales/cs-CZ.json | 1 - i18n/locales/de-DE.json | 1 - i18n/locales/fr-FR.json | 1 - i18n/locales/ja-JP.json | 1 - i18n/locales/pl-PL.json | 1 - i18n/locales/uk-UA.json | 1 - i18n/locales/zh-CN.json | 1 - lunaria/files/ar-EG.json | 1 - lunaria/files/cs-CZ.json | 1 - lunaria/files/de-DE.json | 1 - lunaria/files/fr-FR.json | 1 - lunaria/files/ja-JP.json | 1 - lunaria/files/pl-PL.json | 1 - lunaria/files/uk-UA.json | 1 - lunaria/files/zh-CN.json | 1 - 16 files changed, 16 deletions(-) diff --git a/i18n/locales/ar.json b/i18n/locales/ar.json index cfcde2921..fbff4a8cf 100644 --- a/i18n/locales/ar.json +++ b/i18n/locales/ar.json @@ -820,7 +820,6 @@ }, "team": { "title": "الفريق", - "governance": "الحوكمة", "role_steward": "راعي", "role_maintainer": "مشرف", "sponsor": "راعي", diff --git a/i18n/locales/cs-CZ.json b/i18n/locales/cs-CZ.json index f935947ea..ad0635fe2 100644 --- a/i18n/locales/cs-CZ.json +++ b/i18n/locales/cs-CZ.json @@ -820,7 +820,6 @@ }, "team": { "title": "Tým", - "governance": "Správa", "role_steward": "Vedoucí", "role_maintainer": "Správce", "sponsor": "Sponzor", diff --git a/i18n/locales/de-DE.json b/i18n/locales/de-DE.json index 59ad603c9..ef9908189 100644 --- a/i18n/locales/de-DE.json +++ b/i18n/locales/de-DE.json @@ -821,7 +821,6 @@ }, "team": { "title": "Team", - "governance": "Verwaltung", "role_steward": "Verwalter", "role_maintainer": "Maintainer", "sponsor": "Sponsor", diff --git a/i18n/locales/fr-FR.json b/i18n/locales/fr-FR.json index 9e2555190..bc2072987 100644 --- a/i18n/locales/fr-FR.json +++ b/i18n/locales/fr-FR.json @@ -822,7 +822,6 @@ }, "team": { "title": "Équipe", - "governance": "Gouvernance", "role_steward": "pilote", "role_maintainer": "mainteneur", "sponsor": "sponsor", diff --git a/i18n/locales/ja-JP.json b/i18n/locales/ja-JP.json index 112bd4a70..21a6e332f 100644 --- a/i18n/locales/ja-JP.json +++ b/i18n/locales/ja-JP.json @@ -820,7 +820,6 @@ }, "team": { "title": "チーム", - "governance": "ガバナンス", "role_steward": "スチュワード", "role_maintainer": "メンテナ", "sponsor": "スポンサー", diff --git a/i18n/locales/pl-PL.json b/i18n/locales/pl-PL.json index b30bae2ba..1d0bc9e72 100644 --- a/i18n/locales/pl-PL.json +++ b/i18n/locales/pl-PL.json @@ -821,7 +821,6 @@ }, "team": { "title": "Zespół", - "governance": "Zarządzanie", "role_steward": "steward", "role_maintainer": "maintainer", "sponsor": "sponsor", diff --git a/i18n/locales/uk-UA.json b/i18n/locales/uk-UA.json index 2b1ead44c..71250e7b2 100644 --- a/i18n/locales/uk-UA.json +++ b/i18n/locales/uk-UA.json @@ -820,7 +820,6 @@ }, "team": { "title": "Команда", - "governance": "Управління", "role_steward": "стюард", "role_maintainer": "супроводжувач", "sponsor": "спонсор", diff --git a/i18n/locales/zh-CN.json b/i18n/locales/zh-CN.json index 21af093e7..385634ae3 100644 --- a/i18n/locales/zh-CN.json +++ b/i18n/locales/zh-CN.json @@ -820,7 +820,6 @@ }, "team": { "title": "团队", - "governance": "治理", "role_steward": "管理者", "role_maintainer": "维护者", "sponsor": "赞助者", diff --git a/lunaria/files/ar-EG.json b/lunaria/files/ar-EG.json index 5ff151041..bf00c225b 100644 --- a/lunaria/files/ar-EG.json +++ b/lunaria/files/ar-EG.json @@ -819,7 +819,6 @@ }, "team": { "title": "الفريق", - "governance": "الحوكمة", "role_steward": "راعي", "role_maintainer": "مشرف", "sponsor": "راعي", diff --git a/lunaria/files/cs-CZ.json b/lunaria/files/cs-CZ.json index 5edbf6120..28ea82edc 100644 --- a/lunaria/files/cs-CZ.json +++ b/lunaria/files/cs-CZ.json @@ -819,7 +819,6 @@ }, "team": { "title": "Tým", - "governance": "Správa", "role_steward": "Vedoucí", "role_maintainer": "Správce", "sponsor": "Sponzor", diff --git a/lunaria/files/de-DE.json b/lunaria/files/de-DE.json index 82fb974ea..44b1b40cf 100644 --- a/lunaria/files/de-DE.json +++ b/lunaria/files/de-DE.json @@ -820,7 +820,6 @@ }, "team": { "title": "Team", - "governance": "Verwaltung", "role_steward": "Verwalter", "role_maintainer": "Maintainer", "sponsor": "Sponsor", diff --git a/lunaria/files/fr-FR.json b/lunaria/files/fr-FR.json index 179381c21..3cfba16ec 100644 --- a/lunaria/files/fr-FR.json +++ b/lunaria/files/fr-FR.json @@ -821,7 +821,6 @@ }, "team": { "title": "Équipe", - "governance": "Gouvernance", "role_steward": "pilote", "role_maintainer": "mainteneur", "sponsor": "sponsor", diff --git a/lunaria/files/ja-JP.json b/lunaria/files/ja-JP.json index 3cb054895..bdb8b239c 100644 --- a/lunaria/files/ja-JP.json +++ b/lunaria/files/ja-JP.json @@ -819,7 +819,6 @@ }, "team": { "title": "チーム", - "governance": "ガバナンス", "role_steward": "スチュワード", "role_maintainer": "メンテナ", "sponsor": "スポンサー", diff --git a/lunaria/files/pl-PL.json b/lunaria/files/pl-PL.json index 240b50e0a..8c2546bd4 100644 --- a/lunaria/files/pl-PL.json +++ b/lunaria/files/pl-PL.json @@ -820,7 +820,6 @@ }, "team": { "title": "Zespół", - "governance": "Zarządzanie", "role_steward": "steward", "role_maintainer": "maintainer", "sponsor": "sponsor", diff --git a/lunaria/files/uk-UA.json b/lunaria/files/uk-UA.json index d6428f02d..c4e42eb74 100644 --- a/lunaria/files/uk-UA.json +++ b/lunaria/files/uk-UA.json @@ -819,7 +819,6 @@ }, "team": { "title": "Команда", - "governance": "Управління", "role_steward": "стюард", "role_maintainer": "супроводжувач", "sponsor": "спонсор", diff --git a/lunaria/files/zh-CN.json b/lunaria/files/zh-CN.json index 9e626de72..367b4c943 100644 --- a/lunaria/files/zh-CN.json +++ b/lunaria/files/zh-CN.json @@ -819,7 +819,6 @@ }, "team": { "title": "团队", - "governance": "治理", "role_steward": "管理者", "role_maintainer": "维护者", "sponsor": "赞助者", From c9d5d70c48718b32ebd29ab0c2ce936798d6f22b Mon Sep 17 00:00:00 2001 From: userquin Date: Mon, 23 Feb 2026 05:35:01 +0100 Subject: [PATCH 08/18] chore: fix `http:` without `//` --- server/api/contributors.get.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api/contributors.get.ts b/server/api/contributors.get.ts index fadb8494e..035163d88 100644 --- a/server/api/contributors.get.ts +++ b/server/api/contributors.get.ts @@ -103,7 +103,7 @@ function cleanString(val: string | null, url = false): string | null { if (!url) { return val } - return val.startsWith('https://') || val.startsWith('http:') ? val : null + return val.startsWith('https://') || val.startsWith('http://') ? val : null } /** From 138b1c35b4592bc0b182ba6c129b3c478b38a611 Mon Sep 17 00:00:00 2001 From: userquin Date: Mon, 23 Feb 2026 05:42:53 +0100 Subject: [PATCH 09/18] chore: apply coderabbitai suggestion for useInfo --- server/api/contributors.get.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/server/api/contributors.get.ts b/server/api/contributors.get.ts index 035163d88..521d849f8 100644 --- a/server/api/contributors.get.ts +++ b/server/api/contributors.get.ts @@ -30,6 +30,16 @@ type GitHubAPIContributor = Omit maintainer: Set @@ -246,7 +256,7 @@ export default defineCachedEventHandler( return filtered .map(c => { const { role, order } = getRoleInfo(c.login, teams) - const userInfo = userData.get(c.login) || {} + const userInfo = userData.get(c.login) ?? DEFAULT_USER_INFO const sponsors_url = sponsorable.has(c.login) ? `https://github.com/sponsors/${c.login}` : null From 5d34b0e9c4e78a9b1f4b2e34f5c542b9d32a37f3 Mon Sep 17 00:00:00 2001 From: userquin Date: Mon, 23 Feb 2026 05:55:31 +0100 Subject: [PATCH 10/18] chore: apply coderabbitai suggestion for timers cleanup --- app/pages/about.vue | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/app/pages/about.vue b/app/pages/about.vue index fc6f900ae..448dfb624 100644 --- a/app/pages/about.vue +++ b/app/pages/about.vue @@ -4,17 +4,6 @@ import type { Role, GitHubContributor } from '#server/api/contributors.get' const router = useRouter() const canGoBack = useCanGoBack() -// SSR & Validation Fix -const isMounted = shallowRef(false) -const activeContributor = shallowRef(null) -const openTimer = shallowRef(null) -const closeTimer = shallowRef(null) -const isFlipped = shallowRef(false) - -onMounted(() => { - isMounted.value = true -}) - useSeoMeta({ title: () => `${$t('about.title')} - npmx`, ogTitle: () => `${$t('about.title')} - npmx`, @@ -30,6 +19,21 @@ defineOgImageComponent('Default', { description: 'a fast, modern browser for the **npm registry**', }) +const isMounted = shallowRef(false) +const activeContributor = shallowRef(null) +const openTimer = shallowRef | undefined>() +const closeTimer = shallowRef | undefined>() +const isFlipped = shallowRef(false) + +onMounted(() => { + isMounted.value = true +}) + +onBeforeUnmount(() => { + if (openTimer.value) clearTimeout(openTimer.value) + if (closeTimer.value) clearTimeout(closeTimer.value) +}) + const pmLinks = { npm: 'https://www.npmjs.com/', pnpm: 'https://pnpm.io/', @@ -141,7 +145,7 @@ function onMouseEnter(contributor: GitHubContributor) { function cancelClose() { if (closeTimer.value) { clearTimeout(closeTimer.value) - closeTimer.value = null + closeTimer.value = undefined } } From 5e034fe35e2c66598c22e524a9966d91f140c989 Mon Sep 17 00:00:00 2001 From: userquin Date: Mon, 23 Feb 2026 07:24:08 +0100 Subject: [PATCH 11/18] chore: fix positionPopover called fire-and-forget and openTimer not reset after clearing --- app/pages/about.vue | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/pages/about.vue b/app/pages/about.vue index 448dfb624..bf4ce632d 100644 --- a/app/pages/about.vue +++ b/app/pages/about.vue @@ -129,14 +129,15 @@ function onMouseEnter(contributor: GitHubContributor) { if (!isExpandable(contributor)) return cancelClose() clearTimeout(openTimer.value) + openTimer.value = undefined - const trigger = () => { + const trigger = async () => { activeContributor.value = contributor - positionPopover(`anchor-${contributor.id}`) + await positionPopover(`anchor-${contributor.id}`) } if (activeContributor.value) { - trigger() + void trigger() } else { openTimer.value = setTimeout(trigger, 80) } @@ -151,12 +152,18 @@ function cancelClose() { function onMouseLeave() { clearTimeout(openTimer.value) + openTimer.value = undefined closeTimer.value = setTimeout(() => { const popover = document.getElementById('shared-contributor-popover') if (popover && !popover.matches(':hover')) { try { ;(popover as any).hidePopover() - } catch (e) {} + } catch (e) { + if (import.meta.dev) { + // oxlint-disable-next-line no-console + console.warn('[positionPopover] showPopover failed:', e) + } + } activeContributor.value = null } }, 120) From 950ad1327f932fa941f8bfdbb0a40e4bd6a52ea3 Mon Sep 17 00:00:00 2001 From: userquin Date: Mon, 23 Feb 2026 07:28:39 +0100 Subject: [PATCH 12/18] chore: fix showPopover at positionPopover error --- app/pages/about.vue | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/pages/about.vue b/app/pages/about.vue index bf4ce632d..612b95a00 100644 --- a/app/pages/about.vue +++ b/app/pages/about.vue @@ -88,7 +88,12 @@ async function positionPopover(anchorId: string) { if (!popover.matches(':popover-open')) { try { ;(popover as any).showPopover() - } catch (e) {} + } catch (e) { + if (import.meta.dev) { + // oxlint-disable-next-line no-console + console.warn('[positionPopover] showPopover failed:', e) + } + } } // 3. One more tick to ensure the DOM is actually painted with the content @@ -161,7 +166,7 @@ function onMouseLeave() { } catch (e) { if (import.meta.dev) { // oxlint-disable-next-line no-console - console.warn('[positionPopover] showPopover failed:', e) + console.warn('[onMouseLeave] hidePopover failed:', e) } } activeContributor.value = null From 93d10b586e45ae19cd76f0469238e397f3328506 Mon Sep 17 00:00:00 2001 From: userquin Date: Mon, 23 Feb 2026 08:07:39 +0100 Subject: [PATCH 13/18] chore: add long aria label link description --- app/pages/about.vue | 26 +++++++++++++++++++++++++- i18n/locales/en.json | 7 ++++++- i18n/locales/es.json | 7 ++++++- i18n/schema.json | 15 +++++++++++++++ lunaria/files/en-GB.json | 7 ++++++- lunaria/files/en-US.json | 7 ++++++- lunaria/files/es-419.json | 7 ++++++- lunaria/files/es-ES.json | 7 ++++++- 8 files changed, 76 insertions(+), 7 deletions(-) diff --git a/app/pages/about.vue b/app/pages/about.vue index 612b95a00..6acd7f169 100644 --- a/app/pages/about.vue +++ b/app/pages/about.vue @@ -173,6 +173,26 @@ function onMouseLeave() { } }, 120) } + +// --- Add this helper function --- +function getAriaLabel(c: GitHubContributor): string { + const separator = $t('about.contributors.separator') + const role = roleLabels.value[c.role] + ? $t('about.contributors.role', { separator, role: roleLabels.value[c.role] }) + : '' + const works_at = c.company + ? $t('about.contributors.works_at', { separator, company: c.company }) + : '' + const location = c.location + ? $t('about.contributors.location', { separator, location: c.location }) + : '' + return $t('about.contributors.view_profile_detailed', { + name: c.name || c.login, + role, + works_at, + location, + }) +}