Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
02ad831
fix(i18n): add missing pt-BR translations for "shortcuts" (#2532)
tcelestino Apr 15, 2026
0ca1692
fix: set default smoothing to 0 (#2535)
graphieros Apr 15, 2026
5fc9e99
fix(i18n): format compare sparkline data labels (#2537)
graphieros Apr 15, 2026
609a391
chore(i18n): fix lunaria pre-commit hook with force: true (#2528)
romansp Apr 15, 2026
32bf8bb
feat: add timeline tab to package page (#2245)
43081j Apr 15, 2026
75bce20
chore: add issue types and missing label to issue templates (#2544)
mootari Apr 15, 2026
c441377
fix(i18n): add missing translations to Brazilian Portuguese (#2542)
tcelestino Apr 16, 2026
6db03a6
feat(i18n): update French translations (#2547)
Limerio Apr 16, 2026
ca5e399
fix: use fast-npm-meta in timeline (#2546)
43081j Apr 16, 2026
b541ee2
docs(ui): add stories for Settings page (#2545)
cylewaitforit Apr 16, 2026
da0897b
fix: show integers for values below 1k in trends chart tooltip (#2550)
graphieros Apr 16, 2026
791ce70
fix: resolve readme copy functionality in Safari (#2554)
MatteoGabriele Apr 17, 2026
b9c3a1d
feat: new og images (#2292)
harlan-zw Apr 17, 2026
cbcdc54
fix: avoid showing "No README is available" during README load (#2473)
akadotsh Apr 17, 2026
b7f71a6
fix: round chart numbers in `applyDataCorrection` for consistent roun…
ulrichstark Apr 18, 2026
ca5c9b4
fix(i18n): update French localizations (#2560)
WarningImHack3r Apr 18, 2026
d9f8d56
feat: add stale workflow (#2555)
MatteoGabriele Apr 18, 2026
e365dd4
chore: increase operation per run in stale workflow (#2564)
MatteoGabriele Apr 18, 2026
5f30d25
chore: remove stale bot (#2566)
MatteoGabriele Apr 18, 2026
400ddf8
feat(i18n): update hi-IN and mr-IN translation for npmx tagline (#2567)
trivikr Apr 19, 2026
5cfe58f
chore: add workflow for stale prs and issue type bug (#2580)
MatteoGabriele Apr 19, 2026
aaaec6a
chore(deps): update dependency nuxt to v4.4.2 (#2047)
renovate[bot] Apr 19, 2026
4e7eb1d
chore(deps): update nuxt core (#2585)
renovate[bot] Apr 19, 2026
fecb36c
chore: clean up `.env.example` comment formatting + empty values (#2581)
iiio2 Apr 20, 2026
213462e
feat: version history page display download count (#2178)
btea Apr 20, 2026
d8aae4b
fix: filter out security holding packages from Algolia results (#2026)
shuuji3 Apr 20, 2026
2b89cee
chore(deps): lock file maintenance (#2592)
renovate[bot] Apr 20, 2026
13ac6c2
fix(i18n): update language switch immediately without refresh (#2525)
BittuBarnwal7479 Apr 20, 2026
67b1322
ci: disable e18e action duplicate dep PR comments (#2593)
serhalp Apr 20, 2026
6e9a66a
chore: remove storybook directory from chromatic externals (#2598)
cylewaitforit Apr 20, 2026
94ef76d
fix(ui): add loading state for translation status generated time (#2589)
cylewaitforit Apr 20, 2026
2dcc3de
fix: download correct variant and fix layout shift on brand page (#2…
Adebesin-Cell Apr 20, 2026
076dde1
perf: prevent duplicates in file tree sprite (#2586)
ghostdevv Apr 20, 2026
5131302
docs(ui): move non-config files out of .storybook directory (#2597)
cylewaitforit Apr 20, 2026
8117059
fix(ui): inline headings inside collapsible summaries (#2599)
Adebesin-Cell Apr 20, 2026
8b5edf2
docs(ui): add stories for Translation Status page (#2559)
cylewaitforit Apr 20, 2026
d12559c
docs(ui): add stories for Error page (#2601)
cylewaitforit Apr 20, 2026
189a568
chore(deps): update dependency @nuxt/scripts to v1 (#2591)
renovate[bot] Apr 20, 2026
de26d13
fix(ui): bail on fetchMore recursion when no new items (#2606)
jonchurch Apr 21, 2026
4565f23
fix(i18n): add missing Russian translations (#2607)
dragomano Apr 21, 2026
c8fcd11
feat: module replacements v3 (#2068)
gameroman Apr 21, 2026
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
6 changes: 3 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#secure password, can use openssl rand -hex 32
# secure password, can use `openssl rand -hex 32`
NUXT_SESSION_PASSWORD=""

#HMAC secret for image proxy URL signing, can use openssl rand -hex 32
NUXT_IMAGE_PROXY_SECRET=""
# HMAC secret for image-proxy and OG image URL signing, can use `openssl rand -hex 32`
NUXT_IMAGE_PROXY_SECRET=""
2 changes: 2 additions & 0 deletions .github/ISSUE_TEMPLATE/bug-report.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
name: "\U0001F41E Bug report"
description: Create a report to help us improve npmx
type: bug
labels: ['pending triage']
body:
- type: markdown
attributes:
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/feature-request.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: '🚀 Feature request'
description: Suggest a feature that will improve npmx
type: feature
labels: ['pending triage']
body:
- type: markdown
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: 👑 Fix Git ownership
run: git config --global --add safe.directory /__w/npmx.dev/npmx.dev

- uses: voidzero-dev/setup-vp@8ecb39174989ce55af90f45cf55b02738599831d # v1
with:
node-version: lts/*
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/dependency-diff.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ jobs:
with:
mode: artifact
detect-replacements: 'true'
duplicate-threshold: '4'
# Too noisy. Disabling until this can report on duplicate CHANGES in this PR.
duplicate-threshold: '999'
dependency-threshold: '15'
size-threshold: '200000'

Expand Down
41 changes: 41 additions & 0 deletions .github/workflows/stale.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Stale Issues and PRs

on:
schedule:
# Run daily at 2 AM UTC
- cron: '0 2 * * *'
workflow_dispatch: # Allow manual trigger

permissions:
issues: write
pull-requests: write

jobs:
stale-bugs:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f
with:
days-before-issue-stale: 30
days-before-issue-close: 7
days-before-pr-stale: -1
days-before-pr-close: -1
remove-stale-when-updated: true
only-issue-types: 'bug'
stale-issue-label: 'stale'
close-issue-label: 'stale'
operations-per-run: 500

stale-prs:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f
with:
days-before-issue-stale: -1
days-before-issue-close: -1
days-before-pr-stale: 30
days-before-pr-close: 7
remove-stale-when-updated: true
stale-pr-label: 'stale'
close-pr-label: 'stale'
operations-per-run: 500
2 changes: 1 addition & 1 deletion .nuxtrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
setups.@nuxt/test-utils="4.0.0"
setups.@nuxt/test-utils="4.0.2"
3 changes: 1 addition & 2 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { resolve } from 'node:path'
const config = {
stories: [
// List welcome first in sidebar
'../.storybook/docs/welcome.mdx',
'../.storybook/docs/*.mdx',
'../app/storybook/welcome.mdx',
'../app/**/*.@(mdx|stories.@(js|ts))',
],
addons: [
Expand Down
16 changes: 16 additions & 0 deletions .storybook/preview-head.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,19 @@
background-color: var(--bg, oklch(0.171 0 0)) !important;
}
</style>
<script>
// related: https://github.com/npmx-dev/npmx.dev/blob/1431d24be555bca5e1ae6264434d49ca15173c43/test/nuxt/setup.ts#L12-L26
// Stub Nuxt specific globals
// @nuxtjs/color-mode's plugin.client.js reads window[globalName] at module
// evaluation time — before any Storybook setup() callback runs — so the
// global must exist in the HTML head, not in preview.ts.
window.__NUXT_COLOR_MODE__ ??= {
preference: 'system',
value: 'dark',
getColorScheme: function () {
return 'dark'
},
addColorScheme: function () {},
removeColorScheme: function () {},
}
</script>
12 changes: 1 addition & 11 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,8 @@ import npmxDark from './theme'

initialize()

// related: https://github.com/npmx-dev/npmx.dev/blob/1431d24be555bca5e1ae6264434d49ca15173c43/test/nuxt/setup.ts#L12-L26
// Stub Nuxt specific globals
// @ts-expect-error - dynamic global name
globalThis['__NUXT_COLOR_MODE__'] ??= {
preference: 'system',
value: 'dark',
getColorScheme: fn(() => 'dark'),
addColorScheme: fn(),
removeColorScheme: fn(),
}
// @ts-expect-error - dynamic global name
globalThis.defineOgImageComponent = fn()
globalThis.defineOgImage = fn()

// Subscribe to locale changes from storybook-i18n addon (once, outside decorator)
let currentI18nInstance: any = null
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ Ideally, extract utilities into separate files so they can be unit tested. 🙏

### Internal linking

Always use **object syntax with named routes** for internal navigation. This makes links resilient to URL structure changes and provides type safety via `unplugin-vue-router`.
Always use **object syntax with named routes** for internal navigation. This makes links resilient to URL structure changes and provides type safety with the [typedPages Nuxt option](https://nuxt.com/docs/4.x/guide/going-further/experimental-features#typedpages).

```vue
<!-- Good: named route -->
Expand Down
4 changes: 4 additions & 0 deletions app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ if (import.meta.client) {
useEventListener(document, 'click', handleModalLightDismiss)
}
}

// title and description will be inferred
// this will be overridden by upstream pages that use different templates
defineOgImage('Page.takumi', {}, { alt: 'npmx — a fast, modern browser for the npm registry' })
</script>

<template>
Expand Down
4 changes: 4 additions & 0 deletions app/components/AppFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ const closeModal = () => modalRef.value?.close?.()
<kbd class="kbd">f</kbd>
<span>{{ $t('shortcuts.open_diff') }}</span>
</li>
<li class="flex gap-2 items-center">
<kbd class="kbd">t</kbd>
<span>{{ $t('shortcuts.open_timeline') }}</span>
</li>
<li class="flex gap-2 items-center">
<kbd class="kbd">c</kbd>
<span>{{ $t('shortcuts.compare_from_package') }}</span>
Expand Down
85 changes: 41 additions & 44 deletions app/components/Brand/Customize.vue
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@ async function downloadCustomPng() {
if (!svg) return
pngLoading.value = true

const blob = new Blob([svg], { type: 'image/svg+xml' })
const url = URL.createObjectURL(blob)
const url = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`

try {
await document.fonts.ready
Expand Down Expand Up @@ -108,7 +107,6 @@ async function downloadCustomPng() {
}, 'image/png')
})
} finally {
URL.revokeObjectURL(url)
pngLoading.value = false
}
}
Expand Down Expand Up @@ -145,24 +143,30 @@ async function downloadCustomPng() {
<span class="text-sm font-mono text-fg-muted shrink-0">{{
$t('brand.customize.accent_label')
}}</span>
<div class="flex items-center gap-1.5" role="radiogroup">
<ButtonBase
<div class="flex items-center gap-1.5">
<label
v-for="color in pickerColors"
:key="color.id"
role="radio"
:aria-checked="activeAccentId === color.id"
:aria-label="color.label"
class="!w-6 !h-6 !rounded-full !border-2 !p-0 !min-w-0 transition-all duration-150 motion-reduce:transition-none"
class="relative w-6 h-6 rounded-full border-2 cursor-pointer duration-150 motion-reduce:transition-none focus-within:ring-2 focus-within:ring-fg focus-within:ring-offset-2 focus-within:ring-offset-bg"
:class="
activeAccentId === color.id
? '!border-fg scale-110'
? 'border-fg scale-110'
: color.id === 'neutral'
? '!border-border hover:!border-border-hover'
: '!border-transparent hover:!border-border-hover'
? 'border-border hover:border-border-hover'
: 'border-transparent hover:border-border-hover'
"
:style="{ backgroundColor: color.value }"
@click="customAccent = color.id"
/>
>
<input
type="radio"
name="brand-customize-accent"
:value="color.id"
:checked="activeAccentId === color.id"
:aria-label="color.label"
class="sr-only"
@change="customAccent = color.id"
/>
</label>
</div>
</fieldset>

Expand All @@ -172,40 +176,33 @@ async function downloadCustomPng() {
<span class="text-sm font-mono text-fg-muted">{{
$t('brand.customize.bg_label')
}}</span>
<div
class="flex items-center border border-border rounded-md overflow-hidden"
role="radiogroup"
>
<ButtonBase
size="md"
role="radio"
:aria-checked="customBgDark"
:aria-label="$t('brand.logos.on_dark')"
class="!border-none !rounded-none motion-reduce:transition-none"
:class="
customBgDark
? 'bg-bg-muted text-fg'
: 'bg-transparent text-fg-muted hover:text-fg'
"
@click="customBgDark = true"
<div class="flex items-center border border-border rounded-md overflow-hidden">
<label
class="px-3 py-1.5 text-sm font-mono cursor-pointer motion-reduce:transition-none focus-within:bg-fg/10"
:class="customBgDark ? 'bg-bg-muted text-fg' : 'text-fg-muted hover:text-fg'"
>
<input
v-model="customBgDark"
type="radio"
name="brand-customize-bg"
:value="true"
class="sr-only"
/>
{{ $t('brand.logos.on_dark') }}
</ButtonBase>
<ButtonBase
size="md"
role="radio"
:aria-checked="!customBgDark"
:aria-label="$t('brand.logos.on_light')"
class="!border-none !rounded-none border-is border-is-border motion-reduce:transition-none"
:class="
!customBgDark
? 'bg-bg-muted text-fg'
: 'bg-transparent text-fg-muted hover:text-fg'
"
@click="customBgDark = false"
</label>
<label
class="px-3 py-1.5 text-sm font-mono cursor-pointer border-is border-is-border motion-reduce:transition-none focus-within:bg-fg/10"
:class="!customBgDark ? 'bg-bg-muted text-fg' : 'text-fg-muted hover:text-fg'"
>
<input
v-model="customBgDark"
type="radio"
name="brand-customize-bg"
:value="false"
class="sr-only"
/>
{{ $t('brand.logos.on_light') }}
</ButtonBase>
</label>
</div>
</div>

Expand Down
4 changes: 4 additions & 0 deletions app/components/Chart/SplitSparkline.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const props = defineProps<{

const { locale } = useI18n()
const colorMode = useColorMode()
const numberFormatter = useNumberFormatter()
const resolvedMode = shallowRef<'light' | 'dark'>('light')
const rootEl = shallowRef<HTMLElement | null>(null)
const palette = getPalette('')
Expand Down Expand Up @@ -153,6 +154,9 @@ const configs = computed(() => {
fontSize: 24,
bold: false,
color: colors.value.fg,
formatter: ({ value }) => {
return numberFormatter.value.format(value)
},
datetimeFormatter: {
enable: true,
locale: locale.value,
Expand Down
Loading
Loading