Skip to content

Commit c9b5586

Browse files
committed
fix: address valid CodeRabbit feedback
1 parent 1036612 commit c9b5586

File tree

10 files changed

+136
-35
lines changed

10 files changed

+136
-35
lines changed

app/composables/useCommandPaletteCommands.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,12 @@ export function useCommandPaletteCommands() {
164164
})
165165

166166
const matchedCommands = computed(() => {
167+
const availableGroups = new Set(commands.value.map(command => command.group))
167168
let nextCommands = results.value.map(result => result.item)
168169

169170
queryOverrides.value.forEach(({ scopeId, group }) => {
171+
if (!availableGroups.has(group)) return
172+
170173
const resolve = resolveQueryOverride(scopeId, group)
171174
if (!resolve) return
172175
const overrideCommands = resolve(trimmedQuery.value)

app/composables/useCommandPalettePackageCommands.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,13 @@ export function useCommandPalettePackageCommands(
3131
const resolvedContext = toValue(context)
3232
if (!resolvedContext?.resolvedVersion) return []
3333

34+
const splitName = resolvedContext.packageName.split('/')
3435
const docsLink = {
3536
name: 'docs' as const,
3637
params: {
37-
path: [resolvedContext.packageName, 'v', resolvedContext.resolvedVersion] satisfies [
38-
string,
39-
string,
40-
string,
41-
],
38+
path: [...splitName, 'v', resolvedContext.resolvedVersion] as [string, ...string[]],
4239
},
4340
}
44-
const splitName = resolvedContext.packageName.split('/')
4541
const codeLink = {
4642
name: 'code' as const,
4743
params: {

app/composables/useCommandPalettePackageVersions.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,37 @@ export function useCommandPalettePackageVersions(
55
packageName: MaybeRefOrGetter<string | null | undefined>,
66
) {
77
const versions = shallowRef<string[] | null>(null)
8-
let versionsPromise: Promise<void> | null = null
8+
let pendingLoad: Promise<void> | null = null
9+
let loadToken = 0
910

1011
watch(
1112
() => toValue(packageName),
1213
() => {
1314
versions.value = null
14-
versionsPromise = null
15+
pendingLoad = null
16+
loadToken += 1
1517
},
1618
)
1719

1820
async function ensureLoaded() {
1921
const resolvedPackageName = toValue(packageName)
2022
if (!resolvedPackageName || versions.value) return
21-
if (versionsPromise) return versionsPromise
23+
if (pendingLoad) return pendingLoad
2224

23-
versionsPromise = fetchAllPackageVersions(resolvedPackageName)
25+
const requestToken = ++loadToken
26+
const load = fetchAllPackageVersions(resolvedPackageName)
2427
.then(allVersions => {
25-
if (toValue(packageName) !== resolvedPackageName) return
28+
if (requestToken !== loadToken || toValue(packageName) !== resolvedPackageName) return
2629
versions.value = allVersions.map(version => version.version)
2730
})
2831
.finally(() => {
29-
versionsPromise = null
32+
if (pendingLoad === load) {
33+
pendingLoad = null
34+
}
3035
})
3136

32-
return versionsPromise
37+
pendingLoad = load
38+
return load
3339
}
3440

3541
return {

app/pages/package-code/[[org]]/[packageName]/v/[version]/[...filePath].vue

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -336,9 +336,24 @@ defineOgImageComponent('Default', {
336336
337337
useCommandPaletteContextCommands(
338338
computed((): CommandPaletteContextCommandInput[] => {
339-
if (!isViewingFile.value || isBinaryFile.value || !fileContent.value) return []
339+
if (!isViewingFile.value) return []
340340
341-
const commands: CommandPaletteContextCommandInput[] = [
341+
const commands: CommandPaletteContextCommandInput[] = []
342+
343+
if (filePath.value) {
344+
commands.push({
345+
id: 'code-open-raw',
346+
group: 'links',
347+
label: $t('code.raw'),
348+
keywords: [packageName.value, filePath.value],
349+
iconClass: 'i-lucide:file-output',
350+
href: `https://cdn.jsdelivr.net/npm/${packageName.value}@${version.value}/${filePath.value}`,
351+
})
352+
}
353+
354+
if (isBinaryFile.value || !fileContent.value) return commands
355+
356+
commands.push(
342357
{
343358
id: 'code-copy-link',
344359
group: 'actions',
@@ -359,18 +374,7 @@ useCommandPaletteContextCommands(
359374
copyFileContent()
360375
},
361376
},
362-
]
363-
364-
if (filePath.value) {
365-
commands.push({
366-
id: 'code-open-raw',
367-
group: 'links',
368-
label: $t('code.raw'),
369-
keywords: [packageName.value, filePath.value],
370-
iconClass: 'i-lucide:file-output',
371-
href: `https://cdn.jsdelivr.net/npm/${packageName.value}@${version.value}/${filePath.value}`,
372-
})
373-
}
377+
)
374378
375379
if (fileContent.value.markdownHtml) {
376380
commands.push(

app/pages/profile/[identity]/index.vue

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
22
import { updateProfile as updateProfileUtil } from '~/utils/atproto/profile'
33
import type { CommandPaletteContextCommandInput } from '~/types/command-palette'
4+
import { getSafeHttpUrl } from '#shared/utils/url'
45
56
const route = useRoute('profile-identity')
67
const identity = computed(() => route.params.identity)
@@ -94,6 +95,7 @@ const inviteUrl = computed(() => {
9495
const text = $t('profile.invite.compose_text', { handle: profile.value.handle })
9596
return `https://bsky.app/intent/compose?text=${encodeURIComponent(text)}`
9697
})
98+
const safeProfileWebsiteUrl = computed(() => getSafeHttpUrl(profile.value.website))
9799
98100
useCommandPaletteContextCommands(
99101
computed((): CommandPaletteContextCommandInput[] => {
@@ -112,14 +114,14 @@ useCommandPaletteContextCommands(
112114
})
113115
}
114116
115-
if (profile.value.website) {
117+
if (safeProfileWebsiteUrl.value) {
116118
commands.push({
117119
id: 'profile-website',
118120
group: 'links',
119121
label: $t('profile.website'),
120-
keywords: [profile.value.website, profile.value.handle ?? identity.value],
122+
keywords: [profile.value.website ?? '', profile.value.handle ?? identity.value],
121123
iconClass: 'i-lucide:link',
122-
href: profile.value.website,
124+
href: safeProfileWebsiteUrl.value,
123125
})
124126
}
125127
@@ -207,7 +209,11 @@ defineOgImageComponent('Default', {
207209
<p v-if="profile.description">{{ profile.description }}</p>
208210
<div class="flex gap-4 items-center font-mono text-sm">
209211
<h2>@{{ profile.handle ?? identity }}</h2>
210-
<LinkBase v-if="profile.website" :to="profile.website" classicon="i-lucide:link">
212+
<LinkBase
213+
v-if="safeProfileWebsiteUrl"
214+
:to="safeProfileWebsiteUrl"
215+
classicon="i-lucide:link"
216+
>
211217
{{ profile.website }}
212218
</LinkBase>
213219
<ButtonBase

docs/content/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ links:
3939
:::u-page-feature{icon="i-lucide:link" to="/guide/url-structure" title="Use familiar URLs" description="Replace npmjs.com with npmx.dev in any URL and it just works."}
4040
:::
4141

42-
:::u-page-feature{icon="i-lucide:keyboard" to="/guide/keyboard-shortcuts" title="Navigate with keyboard" description="Open the command palette with ⌘K on macOS or Ctrl+K on Windows and Linux, press / to search, and use arrow keys to move fast."}
42+
:::u-page-feature{icon="i-lucide:keyboard" to="/guide/keyboard-shortcuts" title="Navigate with keyboard" description="Open the command palette with ⌘K on macOS or Ctrl+K on Windows and Linux. Press / to search. Use arrow keys to browse results."}
4343
:::
4444

4545
:::u-page-feature{icon="i-lucide:shield-check" to="/guide/features" title="Check security" description="Vulnerability warnings from OSV database and provenance indicators for verified builds."}

shared/utils/url.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,21 @@ export function areUrlsEquivalent(url1: string, url2: string): boolean {
2727
return normalizeUrlForComparison(url1) === normalizeUrlForComparison(url2)
2828
}
2929

30+
export function getSafeHttpUrl(url?: string | null): string | null {
31+
if (!url) return null
32+
33+
try {
34+
const parsed = new URL(url)
35+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
36+
return null
37+
}
38+
39+
return parsed.href
40+
} catch {
41+
return null
42+
}
43+
}
44+
3045
export function normalizeSearchParam(query?: LocationQueryValue | LocationQueryValue[]): string {
3146
if (!query) return ''
3247

test/nuxt/a11y.spec.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,20 @@ async function runAxe(wrapper: VueWrapper): Promise<AxeResults> {
4848
return axe.run(container, axeRunOptions)
4949
}
5050

51+
async function runAxeElements(elements: Array<Element | null | undefined>): Promise<AxeResults> {
52+
const container = document.createElement('div')
53+
container.id = `test-container-${Date.now()}`
54+
document.body.appendChild(container)
55+
mountedContainers.push(container)
56+
57+
for (const element of elements) {
58+
if (!element) continue
59+
container.appendChild(element.cloneNode(true))
60+
}
61+
62+
return axe.run(container, axeRunOptions)
63+
}
64+
5165
// --- Console warning assertion --------------------------------------------------
5266
// Fail any test that emits unexpected console.warn calls. This catches issues
5367
// like missing/invalid props that would otherwise silently pass.
@@ -504,11 +518,16 @@ describe('component accessibility audits', () => {
504518
})
505519

506520
it('should have no accessibility violations when open', async () => {
507-
const component = await mountSuspended(CommandPaletteHarness)
521+
await mountSuspended(CommandPaletteHarness)
508522
await nextTick()
509523
await nextTick()
510524

511-
const results = await runAxe(component)
525+
const dialog = document.getElementById('command-palette-modal')
526+
const announcer = document.getElementById('command-palette-modal-announcement')
527+
528+
expect(dialog).not.toBeNull()
529+
530+
const results = await runAxeElements([announcer, dialog])
512531
expect(results.violations).toEqual([])
513532
})
514533
})

test/nuxt/composables/use-command-palette-commands.spec.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,24 @@ describe('useCommandPaletteCommands', () => {
399399
wrapper.unmount()
400400
})
401401

402+
it('does not inject version overrides into unrelated palette subviews', async () => {
403+
const { wrapper, groupedCommands, flatCommands } = await captureCommandPalette({
404+
view: 'languages',
405+
query: '^3.4.0',
406+
packageContext: {
407+
packageName: 'vue',
408+
resolvedVersion: '3.4.2',
409+
latestVersion: '4.0.0',
410+
versions: ['4.0.0', '3.5.0', '3.4.2', '3.3.0'],
411+
},
412+
})
413+
414+
expect(groupedCommands.value.map(group => group.id)).not.toContain('versions')
415+
expect(flatCommands.value.some(command => command.id.startsWith('version:'))).toBe(false)
416+
417+
wrapper.unmount()
418+
})
419+
402420
it('shows accent color commands on the accent color subpage', async () => {
403421
const { wrapper, groupedCommands, flatCommands } = await captureCommandPalette({
404422
view: 'accent-colors',
@@ -474,7 +492,7 @@ describe('useCommandPaletteCommands', () => {
474492
expect(flatCommands.value.find(command => command.id === 'package-docs')?.to).toEqual({
475493
name: 'docs',
476494
params: {
477-
path: ['@scope/pkg', 'v', '1.0.0'],
495+
path: ['@scope', 'pkg', 'v', '1.0.0'],
478496
},
479497
})
480498

test/nuxt/composables/use-command-palette-package-versions.spec.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,38 @@ describe('useCommandPalettePackageVersions', () => {
118118

119119
wrapper.unmount()
120120
})
121+
122+
it('keeps deduping the active package while an older request finishes', async () => {
123+
const pendingFetches = new Map<string, (value: Array<{ version: string }>) => void>()
124+
mockFetchAllPackageVersions.mockImplementation(
125+
(packageName: string) =>
126+
new Promise<Array<{ version: string }>>(resolve => {
127+
pendingFetches.set(packageName, resolve)
128+
}),
129+
)
130+
131+
const { wrapper, packageName, result } = await capturePackageVersions('vue')
132+
133+
const vueLoad = result.ensureLoaded()
134+
135+
packageName.value = 'react'
136+
await nextTick()
137+
138+
const firstReactLoad = result.ensureLoaded()
139+
140+
pendingFetches.get('vue')?.([{ version: '3.5.0' }])
141+
await vueLoad
142+
await nextTick()
143+
144+
const secondReactLoad = result.ensureLoaded()
145+
146+
expect(mockFetchAllPackageVersions).toHaveBeenCalledTimes(2)
147+
148+
pendingFetches.get('react')?.([{ version: '19.0.0' }])
149+
await Promise.all([firstReactLoad, secondReactLoad])
150+
151+
expect(result.versions.value).toEqual(['19.0.0'])
152+
153+
wrapper.unmount()
154+
})
121155
})

0 commit comments

Comments
 (0)