From 481eb98fd623631029004b42e2b6dc11f802388f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 05:57:33 +0000 Subject: [PATCH] chore: publish from main --- .../src/components/brand/AgentsCatalog.tsx | 7 ++-- .../src/components/brand/PluginsCatalog.tsx | 3 +- website/src/components/brand/TopNavSearch.tsx | 23 +++++++---- .../src/components/brand/catalogFilters.ts | 40 +++++++++++++++---- 4 files changed, 55 insertions(+), 18 deletions(-) diff --git a/website/src/components/brand/AgentsCatalog.tsx b/website/src/components/brand/AgentsCatalog.tsx index dfd6d3210..aef75e1da 100644 --- a/website/src/components/brand/AgentsCatalog.tsx +++ b/website/src/components/brand/AgentsCatalog.tsx @@ -152,13 +152,14 @@ export function AgentsCatalog({ const sortedAgents = useMemo(() => { const copy = agents.filter((agent) => { - const models = modelsOf(agent); + const modelSet = new Set(modelsOf(agent)); + const toolSet = new Set(agent.tools ?? []); const modelOk = filters.model.length === 0 || - filters.model.some((model) => models.includes(model)); + filters.model.some((model) => modelSet.has(model)); const toolsOk = filters.tools.length === 0 || - filters.tools.every((tool) => (agent.tools ?? []).includes(tool)); + filters.tools.every((tool) => toolSet.has(tool)); const updatedOk = filters.updated.length === 0 || filters.updated.includes(updatedBucketOf(daysSince(agent.lastUpdated))); diff --git a/website/src/components/brand/PluginsCatalog.tsx b/website/src/components/brand/PluginsCatalog.tsx index 36f0b1b39..2a2c69f3f 100644 --- a/website/src/components/brand/PluginsCatalog.tsx +++ b/website/src/components/brand/PluginsCatalog.tsx @@ -152,11 +152,12 @@ export function PluginsCatalog({ const sortedPlugins = useMemo(() => { const copy = plugins.filter((plugin) => { + const tagSet = new Set(plugin.tags ?? []); const sourceOk = filters.source.length === 0 || filters.source.includes(sourceOf(plugin)); const categoryOk = filters.category.length === 0 || - filters.category.some((tag) => (plugin.tags ?? []).includes(tag)); + filters.category.some((tag) => tagSet.has(tag)); const size = sizeLabelOf(plugin); const sizeOk = filters.size.length === 0 || diff --git a/website/src/components/brand/TopNavSearch.tsx b/website/src/components/brand/TopNavSearch.tsx index 3b637ad45..881e91d56 100644 --- a/website/src/components/brand/TopNavSearch.tsx +++ b/website/src/components/brand/TopNavSearch.tsx @@ -140,6 +140,7 @@ export function TopNavSearch({ const groups = useMemo(() => { const query = trimmed.toLowerCase(); if (query.length === 0) return []; + const staticMatches = normalizedIndex .filter( ({ title, description, category }) => @@ -148,25 +149,33 @@ export function TopNavSearch({ category.includes(query), ) .map(({ item }) => item); + // Static hits win on ties: they carry curated titles and descriptions. const seen = new Set(staticMatches.map((item) => hrefKey(item.href))); - const matches = [...staticMatches]; + const matches: SearchItem[] = [...staticMatches]; for (const hit of pagefindHits) { const key = hrefKey(hit.href); if (seen.has(key)) continue; seen.add(key); matches.push(hit); } + + const matchesByCategory = new Map(); + for (const item of matches) { + const bucket = matchesByCategory.get(item.category) ?? []; + bucket.push(item); + matchesByCategory.set(item.category, bucket); + } + let remaining = MAX_RESULTS; const grouped: ResultGroup[] = []; for (const category of CATEGORY_ORDER) { if (remaining <= 0) break; - const items = matches - .filter((item) => item.category === category) - .slice(0, Math.min(MAX_PER_GROUP, remaining)); - if (items.length === 0) continue; - remaining -= items.length; - grouped.push({ category, items }); + const items = matchesByCategory.get(category) ?? []; + const slice = items.slice(0, Math.min(MAX_PER_GROUP, remaining)); + if (slice.length === 0) continue; + remaining -= slice.length; + grouped.push({ category, items: slice }); } return grouped; }, [trimmed, normalizedIndex, pagefindHits]); diff --git a/website/src/components/brand/catalogFilters.ts b/website/src/components/brand/catalogFilters.ts index f8f5dca28..e4a7a458a 100644 --- a/website/src/components/brand/catalogFilters.ts +++ b/website/src/components/brand/catalogFilters.ts @@ -15,15 +15,34 @@ export const updatedBuckets: { label: string; max: number }[] = [ ]; /** Whole days between `lastUpdated` and now. Unparsable dates sort as oldest. */ +const daysSinceCache = new Map(); + export function daysSince(lastUpdated: string | undefined): number { if (!lastUpdated) return Number.POSITIVE_INFINITY; + + const dayStamp = Math.floor(Date.now() / 86_400_000); + const cacheKey = `${lastUpdated}:${dayStamp}`; + const cached = daysSinceCache.get(cacheKey); + if (cached !== undefined) return cached; + const then = Date.parse(lastUpdated); - if (Number.isNaN(then)) return Number.POSITIVE_INFINITY; - return Math.max(0, Math.floor((Date.now() - then) / 86_400_000)); + const days = Number.isNaN(then) + ? Number.POSITIVE_INFINITY + : Math.max(0, Math.floor((Date.now() - then) / 86_400_000)); + + daysSinceCache.set(cacheKey, days); + return days; } +const updatedBucketCache = new Map(); + export function updatedBucketOf(days: number): string { - return updatedBuckets.find((bucket) => days <= bucket.max)?.label ?? "Older"; + const cached = updatedBucketCache.get(days); + if (cached) return cached; + + const bucket = updatedBuckets.find((option) => days <= option.max)?.label ?? "Older"; + updatedBucketCache.set(days, bucket); + return bucket; } export const fileBuckets: { label: string; min: number; max: number }[] = [ @@ -33,11 +52,18 @@ export const fileBuckets: { label: string; min: number; max: number }[] = [ { label: "11+ files", min: 11, max: Number.POSITIVE_INFINITY }, ]; +const fileBucketCache = new Map(); + export function fileBucketOf(files: number): string { - return ( - fileBuckets.find((bucket) => files >= bucket.min && files <= bucket.max) - ?.label ?? "1–2 files" - ); + const cached = fileBucketCache.get(files); + if (cached) return cached; + + const bucket = + fileBuckets.find((option) => files >= option.min && files <= option.max)?.label ?? + "1–2 files"; + + fileBucketCache.set(files, bucket); + return bucket; } /**