From 90e3de2cdf074173c0b56a3dc41b3965e065e112 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 25 Mar 2026 09:26:55 +0100 Subject: [PATCH] feat: fix category counts and add matchContext for search highlights - Category tab counts now reflect query-filtered results, not total data - Added matchContext field to SearchResult for server-side match snippets - Renders -tagged highlight text below meta in muted style Co-Authored-By: Claude Opus 4.6 (1M context) --- .../CommandPalette/CommandPalette.module.css | 17 ++++++++++ .../CommandPalette/CommandPalette.tsx | 31 ++++++++++++------- .../composites/CommandPalette/types.ts | 2 ++ 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/design-system/composites/CommandPalette/CommandPalette.module.css b/src/design-system/composites/CommandPalette/CommandPalette.module.css index 256cc7f..4a57919 100644 --- a/src/design-system/composites/CommandPalette/CommandPalette.module.css +++ b/src/design-system/composites/CommandPalette/CommandPalette.module.css @@ -277,6 +277,23 @@ overflow-y: auto; } +/* Match context snippet */ +.matchContext { + font-size: 11px; + color: var(--text-faint); + font-family: var(--font-mono); + margin-top: 3px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.matchContext em { + font-style: normal; + color: var(--amber); + font-weight: 600; +} + /* Match highlight */ .mark { background: none; diff --git a/src/design-system/composites/CommandPalette/CommandPalette.tsx b/src/design-system/composites/CommandPalette/CommandPalette.tsx index 69921a3..9f4f9f5 100644 --- a/src/design-system/composites/CommandPalette/CommandPalette.tsx +++ b/src/design-system/composites/CommandPalette/CommandPalette.tsx @@ -92,14 +92,10 @@ export function CommandPalette({ open, onClose, onSelect, data, onOpen, onQueryC } }, [open]) - // Filter results - const filtered = useMemo(() => { + // Stage 1: apply text query + scope filters (used for counts) + const queryFiltered = useMemo(() => { let results = data - if (activeCategory !== 'all') { - results = results.filter((r) => r.category === activeCategory) - } - if (query.trim()) { const q = query.toLowerCase() results = results.filter( @@ -107,7 +103,6 @@ export function CommandPalette({ open, onClose, onSelect, data, onOpen, onQueryC ) } - // Apply scope filters for (const sf of scopeFilters) { results = results.filter((r) => r.category === sf.field || r.title.toLowerCase().includes(sf.value.toLowerCase()), @@ -115,7 +110,13 @@ export function CommandPalette({ open, onClose, onSelect, data, onOpen, onQueryC } return results - }, [data, query, activeCategory, scopeFilters]) + }, [data, query, scopeFilters]) + + // Stage 2: apply category filter (used for display) + const filtered = useMemo(() => { + if (activeCategory === 'all') return queryFiltered + return queryFiltered.filter((r) => r.category === activeCategory) + }, [queryFiltered, activeCategory]) // Group results by category const grouped = useMemo(() => { @@ -130,14 +131,14 @@ export function CommandPalette({ open, onClose, onSelect, data, onOpen, onQueryC // Flatten for keyboard nav const flatResults = useMemo(() => filtered, [filtered]) - // Counts per category + // Counts per category (from query-filtered, before category filter) const categoryCounts = useMemo(() => { - const counts: Record = { all: data.length } - for (const r of data) { + const counts: Record = { all: queryFiltered.length } + for (const r of queryFiltered) { counts[r.category] = (counts[r.category] ?? 0) + 1 } return counts - }, [data]) + }, [queryFiltered]) function handleKeyDown(e: React.KeyboardEvent) { switch (e.key) { @@ -303,6 +304,12 @@ export function CommandPalette({ open, onClose, onSelect, data, onOpen, onQueryC
{highlightText(result.meta, query)}
+ {result.matchContext && ( +
+ )}
{result.expandedContent && (