···11<script setup lang="ts">
22import { debounce } from 'perfect-debounce'
33+import { normalizeSearchParam } from '#shared/utils/url'
3445withDefaults(
56 defineProps<{
···2223})
23242425// Local input value (updates immediately as user types)
2525-const searchQuery = shallowRef(
2626- (Array.isArray(route.query.q) ? route.query.q[0] : route.query.q) ?? '',
2727-)
2626+const searchQuery = shallowRef(normalizeSearchParam(route.query.q))
28272928// Pages that have their own local filter using ?q
3029const pagesWithLocalFilter = new Set(['~username', 'org'])
···6463 if (pagesWithLocalFilter.has(route.name as string)) {
6564 return
6665 }
6767- const value = (urlQuery as string) ?? ''
6666+ const value = normalizeSearchParam(urlQuery)
6867 if (searchQuery.value !== value) {
6968 searchQuery.value = value
7069 }
+3-2
app/pages/@[org].vue
···11<script setup lang="ts">
22import type { FilterChip, SortOption } from '#shared/types/preferences'
33import { debounce } from 'perfect-debounce'
44+import { normalizeSearchParam } from '#shared/utils/url'
4556definePageMeta({
67 name: 'org',
···5152} = useStructuredFilters({
5253 packages,
5354 initialFilters: {
5454- text: (route.query.q as string) ?? '',
5555+ text: normalizeSearchParam(route.query.q),
5556 },
5656- initialSort: (route.query.sort as SortOption) ?? 'updated-desc',
5757+ initialSort: (normalizeSearchParam(route.query.sort) as SortOption) ?? 'updated-desc',
5758})
58595960// Pagination state
+3-2
app/pages/search.vue
···44import { debounce } from 'perfect-debounce'
55import { isValidNewPackageName, checkPackageExists } from '~/utils/package-name'
66import { isPlatformSpecificPackage } from '~/utils/platform-packages'
77+import { normalizeSearchParam } from '#shared/utils/url'
7889const route = useRoute()
910const router = useRouter()
···2930}, 500)
30313132// The actual search query (from URL, used for API calls)
3232-const query = computed(() => (route.query.q as string) ?? '')
3333+const query = computed(() => normalizeSearchParam(route.query.q))
33343435// Track if page just loaded (for hiding "Searching..." during view transition)
3536const hasInteracted = shallowRef(false)
···53545455// Get initial page from URL (for scroll restoration on reload)
5556const initialPage = computed(() => {
5656- const p = Number.parseInt(route.query.page as string, 10)
5757+ const p = Number.parseInt(normalizeSearchParam(route.query.page), 10)
5758 return Number.isNaN(p) ? 1 : Math.max(1, p)
5859})
5960
+6-7
app/pages/~[username]/index.vue
···11<script setup lang="ts">
22import { debounce } from 'perfect-debounce'
33+import { normalizeSearchParam } from '#shared/utils/url'
3445const route = useRoute('~username')
56const router = useRouter()
···13141415// Get initial page from URL (for scroll restoration on reload)
1516const initialPage = computed(() => {
1616- const p = Number.parseInt(route.query.page as string, 10)
1717+ const p = Number.parseInt(normalizeSearchParam(route.query.page), 10)
1718 return Number.isNaN(p) ? 1 : Math.max(1, p)
1819})
1920···3233type SortOption = 'downloads' | 'updated' | 'name-asc' | 'name-desc'
33343435// Filter and sort state (from URL)
3535-const filterText = shallowRef(
3636- (Array.isArray(route.query.q) ? route.query.q[0] : route.query.q) ?? '',
3737-)
3636+const filterText = shallowRef(normalizeSearchParam(route.query.q))
3837const sortOption = shallowRef<SortOption>(
3939- ((Array.isArray(route.query.sort) ? route.query.sort[0] : route.query.sort) as SortOption) ||
4040- 'downloads',
3838+ (normalizeSearchParam(route.query.sort) as SortOption) || 'downloads',
4139)
42404341// Track if we've loaded all results (one-way flag, doesn't reset)
4442// Initialize to true if URL already has filter/sort params
4543const hasLoadedAll = shallowRef(
4646- Boolean(route.query.q) || (route.query.sort && route.query.sort !== 'downloads'),
4444+ Boolean(route.query.q) ||
4545+ (route.query.sort && normalizeSearchParam(route.query.sort) !== 'downloads'),
4746)
48474948// Update URL when filter/sort changes (debounced)
+9
shared/utils/url.ts
···11+import type { LocationQueryValue } from 'vue-router'
12import { withoutProtocol, withoutTrailingSlash } from 'ufo'
2334/**
···2526export function areUrlsEquivalent(url1: string, url2: string): boolean {
2627 return normalizeUrlForComparison(url1) === normalizeUrlForComparison(url2)
2728}
2929+3030+export function normalizeSearchParam(query?: LocationQueryValue | LocationQueryValue[]): string {
3131+ if (!query) return ''
3232+3333+ if (typeof query === 'string') return query
3434+3535+ return normalizeSearchParam(query[0])
3636+}