Skip to content

Commit e42403c

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix/payload-fallback
2 parents 69b15bb + f8ab6cb commit e42403c

File tree

177 files changed

+13386
-2038
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

177 files changed

+13386
-2038
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,8 @@ jobs:
235235

236236
- name: 🌐 Check for missing or dynamic i18n keys
237237
run: pnpm i18n:report
238+
239+
- name: 🌐 Check i18n schema is up to date
240+
run: |
241+
pnpm i18n:schema
242+
git diff --exit-code i18n/schema.json
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: mirror
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
tags:
8+
- '*'
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
mirror:
15+
name: 🕸️ Mirror to Tangled
16+
runs-on: ubuntu-24.04-arm
17+
18+
steps:
19+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
20+
with:
21+
fetch-depth: 0
22+
23+
- name: 🔑 Configure SSH
24+
env:
25+
TANGLED_SSH_KEY: ${{ secrets.TANGLED_SSH_KEY }}
26+
run: |
27+
mkdir -p ~/.ssh
28+
echo "$TANGLED_SSH_KEY" > ~/.ssh/id_ed25519
29+
chmod 600 ~/.ssh/id_ed25519
30+
ssh-keyscan -t ed25519 tangled.org >> ~/.ssh/known_hosts 2>/dev/null
31+
32+
- name: ⬆︎ Push to Tangled
33+
run: |
34+
git remote add tangled git@tangled.org:npmx.dev/npmx.dev
35+
git push tangled main --force
36+
git push tangled --tags --force

.lighthouserc.cjs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,18 @@ module.exports = {
3535
chromePath: findChrome(),
3636
puppeteerScript: './lighthouse-setup.cjs',
3737
settings: {
38-
onlyCategories: ['accessibility'],
38+
onlyCategories: process.env.LH_PERF ? ['performance'] : ['accessibility'],
3939
skipAudits: ['valid-source-maps'],
4040
},
4141
},
4242
assert: {
43-
assertions: {
44-
'categories:accessibility': ['error', { minScore: 1 }],
45-
},
43+
assertions: process.env.LH_PERF
44+
? {
45+
'cumulative-layout-shift': ['error', { maxNumericValue: 0 }],
46+
}
47+
: {
48+
'categories:accessibility': ['error', { minScore: 1 }],
49+
},
4650
},
4751
upload: {
4852
target: 'temporary-public-storage',

CONTRIBUTING.md

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ This focus helps guide our project decisions as a community and what we choose t
5454
- [Unit tests](#unit-tests)
5555
- [Component accessibility tests](#component-accessibility-tests)
5656
- [Lighthouse accessibility tests](#lighthouse-accessibility-tests)
57+
- [Lighthouse performance tests](#lighthouse-performance-tests)
5758
- [End to end tests](#end-to-end-tests)
5859
- [Test fixtures (mocking external APIs)](#test-fixtures-mocking-external-apis)
5960
- [Submitting changes](#submitting-changes)
@@ -114,6 +115,7 @@ pnpm test:unit # Unit tests only
114115
pnpm test:nuxt # Nuxt component tests
115116
pnpm test:browser # Playwright E2E tests
116117
pnpm test:a11y # Lighthouse accessibility audits
118+
pnpm test:perf # Lighthouse performance audits (CLS)
117119
```
118120

119121
### Project structure
@@ -641,18 +643,38 @@ pnpm test:a11y:prebuilt
641643

642644
# Or run a single color mode manually
643645
pnpm build:test
644-
LIGHTHOUSE_COLOR_MODE=dark ./scripts/lighthouse-a11y.sh
646+
LIGHTHOUSE_COLOR_MODE=dark ./scripts/lighthouse.sh
645647
```
646648

647649
This requires Chrome or Chromium to be installed. The script will auto-detect common installation paths. Results are printed to the terminal and saved in `.lighthouseci/`.
648650

649651
#### Configuration
650652

651-
| File | Purpose |
652-
| ---------------------------- | --------------------------------------------------------- |
653-
| `.lighthouserc.cjs` | Lighthouse CI config (URLs, assertions, Chrome path) |
654-
| `lighthouse-setup.cjs` | Puppeteer script for color mode + client-side API mocking |
655-
| `scripts/lighthouse-a11y.sh` | Shell wrapper that runs the audit for a given color mode |
653+
| File | Purpose |
654+
| ----------------------- | --------------------------------------------------------- |
655+
| `.lighthouserc.cjs` | Lighthouse CI config (URLs, assertions, Chrome path) |
656+
| `lighthouse-setup.cjs` | Puppeteer script for color mode + client-side API mocking |
657+
| `scripts/lighthouse.sh` | Shell wrapper that runs the audit for a given color mode |
658+
659+
### Lighthouse performance tests
660+
661+
The project also runs Lighthouse performance audits to enforce zero Cumulative Layout Shift (CLS). These run separately from the accessibility audits and test the same set of URLs.
662+
663+
#### How it works
664+
665+
The same `.lighthouserc.cjs` config is shared between accessibility and performance audits. When the `LH_PERF` environment variable is set, the config switches from the `accessibility` category to the `performance` category and asserts that CLS is exactly 0.
666+
667+
#### Running locally
668+
669+
```bash
670+
# Build + run performance audit
671+
pnpm test:perf
672+
673+
# Or against an existing test build
674+
pnpm test:perf:prebuilt
675+
```
676+
677+
Unlike the accessibility audits, performance audits do not run in separate light/dark modes.
656678

657679
### End to end tests
658680

app/components/AppFooter.vue

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ const showModal = () => modalRef.value?.showModal?.()
1414
>
1515
<div>
1616
<p class="font-mono text-balance m-0 hidden sm:block">{{ $t('tagline') }}</p>
17-
<BuildEnvironment v-if="!isHome" footer />
1817
</div>
1918
<!-- Desktop: Show all links. Mobile: Links are in MobileMenu -->
2019
<div class="hidden sm:flex items-center gap-6 min-h-11 text-xs">
@@ -24,22 +23,9 @@ const showModal = () => modalRef.value?.showModal?.()
2423
<LinkBase :to="{ name: 'privacy' }">
2524
{{ $t('privacy_policy.title') }}
2625
</LinkBase>
27-
<LinkBase to="https://docs.npmx.dev">
28-
{{ $t('footer.docs') }}
29-
</LinkBase>
30-
<LinkBase to="https://repo.npmx.dev">
31-
{{ $t('footer.source') }}
32-
</LinkBase>
33-
<LinkBase to="https://social.npmx.dev">
34-
{{ $t('footer.social') }}
35-
</LinkBase>
36-
<LinkBase to="https://chat.npmx.dev">
37-
{{ $t('footer.chat') }}
38-
</LinkBase>
39-
4026
<button
4127
type="button"
42-
class="group inline-flex gap-x-1 items-center justify-center underline-offset-[0.2rem] underline decoration-1 decoration-fg/30 font-mono text-fg hover:(decoration-accent text-accent) focus-visible:(decoration-accent text-accent) transition-colors duration-200"
28+
class="cursor-pointer group inline-flex gap-x-1 items-center justify-center underline-offset-[0.2rem] underline decoration-1 decoration-fg/30 font-mono text-fg hover:(decoration-accent text-accent) focus-visible:(decoration-accent text-accent) transition-colors duration-200"
4329
@click.prevent="showModal"
4430
aria-haspopup="dialog"
4531
>
@@ -103,8 +89,21 @@ const showModal = () => modalRef.value?.showModal?.()
10389
</li>
10490
</ul>
10591
</Modal>
92+
<LinkBase to="https://docs.npmx.dev">
93+
{{ $t('footer.docs') }}
94+
</LinkBase>
95+
<LinkBase to="https://repo.npmx.dev">
96+
{{ $t('footer.source') }}
97+
</LinkBase>
98+
<LinkBase to="https://social.npmx.dev">
99+
{{ $t('footer.social') }}
100+
</LinkBase>
101+
<LinkBase to="https://chat.npmx.dev">
102+
{{ $t('footer.chat') }}
103+
</LinkBase>
106104
</div>
107105
</div>
106+
<BuildEnvironment v-if="!isHome" footer />
108107
<p class="text-xs text-fg-muted text-center sm:text-start m-0">
109108
<span class="sm:hidden">{{ $t('non_affiliation_disclaimer') }}</span>
110109
<span class="hidden sm:inline">{{ $t('trademark_disclaimer') }}</span>

app/components/AppHeader.vue

Lines changed: 119 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
22
import { LinkBase } from '#components'
3+
import type { NavigationConfig, NavigationConfigWithGroups } from '~/types'
34
import { isEditableElement } from '~/utils/input'
45
56
withDefaults(
@@ -13,6 +14,106 @@ withDefaults(
1314
1415
const { isConnected, npmUser } = useConnector()
1516
17+
const desktopLinks = computed<NavigationConfig>(() => [
18+
{
19+
name: 'Compare',
20+
label: $t('nav.compare'),
21+
to: { name: 'compare' },
22+
keyshortcut: 'c',
23+
type: 'link',
24+
external: false,
25+
iconClass: 'i-carbon:compare',
26+
},
27+
{
28+
name: 'Settings',
29+
label: $t('nav.settings'),
30+
to: { name: 'settings' },
31+
keyshortcut: ',',
32+
type: 'link',
33+
external: false,
34+
iconClass: 'i-carbon:settings',
35+
},
36+
])
37+
38+
const mobileLinks = computed<NavigationConfigWithGroups>(() => [
39+
{
40+
name: 'Desktop Links',
41+
type: 'group',
42+
items: [...desktopLinks.value],
43+
},
44+
{
45+
type: 'separator',
46+
},
47+
{
48+
name: 'About & Policies',
49+
type: 'group',
50+
items: [
51+
{
52+
name: 'About',
53+
label: $t('footer.about'),
54+
to: { name: 'about' },
55+
type: 'link',
56+
external: false,
57+
iconClass: 'i-carbon:information',
58+
},
59+
{
60+
name: 'Privacy Policy',
61+
label: $t('privacy_policy.title'),
62+
to: { name: 'privacy' },
63+
type: 'link',
64+
external: false,
65+
iconClass: 'i-carbon:security',
66+
},
67+
],
68+
},
69+
{
70+
type: 'separator',
71+
},
72+
{
73+
name: 'External Links',
74+
type: 'group',
75+
label: $t('nav.links'),
76+
items: [
77+
{
78+
name: 'Docs',
79+
label: $t('footer.docs'),
80+
href: 'https://docs.npmx.dev',
81+
target: '_blank',
82+
type: 'link',
83+
external: true,
84+
iconClass: 'i-carbon:document',
85+
},
86+
{
87+
name: 'Source',
88+
label: $t('footer.source'),
89+
href: 'https://repo.npmx.dev',
90+
target: '_blank',
91+
type: 'link',
92+
external: true,
93+
iconClass: 'i-carbon:logo-github',
94+
},
95+
{
96+
name: 'Social',
97+
label: $t('footer.social'),
98+
href: 'https://social.npmx.dev',
99+
target: '_blank',
100+
type: 'link',
101+
external: true,
102+
iconClass: 'i-simple-icons:bluesky',
103+
},
104+
{
105+
name: 'Chat',
106+
label: $t('footer.chat'),
107+
href: 'https://chat.npmx.dev',
108+
target: '_blank',
109+
type: 'link',
110+
external: true,
111+
iconClass: 'i-carbon:chat',
112+
},
113+
],
114+
},
115+
])
116+
16117
const showFullSearch = shallowRef(false)
17118
const showMobileMenu = shallowRef(false)
18119
@@ -63,23 +164,18 @@ function handleSearchFocus() {
63164
}
64165
65166
onKeyStroke(
66-
e => isKeyWithoutModifiers(e, ',') && !isEditableElement(e.target),
67167
e => {
68-
e.preventDefault()
69-
navigateTo({ name: 'settings' })
70-
},
71-
{ dedupe: true },
72-
)
168+
if (isEditableElement(e.target)) {
169+
return
170+
}
73171
74-
onKeyStroke(
75-
e =>
76-
isKeyWithoutModifiers(e, 'c') &&
77-
!isEditableElement(e.target) &&
78-
// Allow more specific handlers to take precedence
79-
!e.defaultPrevented,
80-
e => {
81-
e.preventDefault()
82-
navigateTo({ name: 'compare' })
172+
for (const link of desktopLinks.value) {
173+
if (link.to && link.keyshortcut && isKeyWithoutModifiers(e, link.keyshortcut)) {
174+
e.preventDefault()
175+
navigateTo(link.to.name)
176+
break
177+
}
178+
}
83179
},
84180
{ dedupe: true },
85181
)
@@ -156,24 +252,16 @@ onKeyStroke(
156252

157253
<!-- End: Desktop nav items + Mobile menu button -->
158254
<div class="hidden sm:flex flex-shrink-0">
159-
<!-- Desktop: Compare link -->
160-
<LinkBase
161-
class="border-none"
162-
variant="button-secondary"
163-
:to="{ name: 'compare' }"
164-
keyshortcut="c"
165-
>
166-
{{ $t('nav.compare') }}
167-
</LinkBase>
168-
169-
<!-- Desktop: Settings link -->
255+
<!-- Desktop: Explore link -->
170256
<LinkBase
257+
v-for="link in desktopLinks"
258+
:key="link.name"
171259
class="border-none"
172260
variant="button-secondary"
173-
:to="{ name: 'settings' }"
174-
keyshortcut=","
261+
:to="link.to"
262+
:aria-keyshortcuts="link.keyshortcut"
175263
>
176-
{{ $t('nav.settings') }}
264+
{{ link.label }}
177265
</LinkBase>
178266

179267
<HeaderAccountMenu />
@@ -182,7 +270,7 @@ onKeyStroke(
182270
<!-- Mobile: Menu button (always visible, click to open menu) -->
183271
<ButtonBase
184272
type="button"
185-
class="sm:hidden flex"
273+
class="sm:hidden"
186274
:aria-label="$t('nav.open_menu')"
187275
:aria-expanded="showMobileMenu"
188276
@click="showMobileMenu = !showMobileMenu"
@@ -191,6 +279,6 @@ onKeyStroke(
191279
</nav>
192280

193281
<!-- Mobile menu -->
194-
<HeaderMobileMenu v-model:open="showMobileMenu" />
282+
<HeaderMobileMenu :links="mobileLinks" v-model:open="showMobileMenu" />
195283
</header>
196284
</template>

0 commit comments

Comments
 (0)