From 72049f1ed974f264f05fc06c1d37abb62f57a2dc Mon Sep 17 00:00:00 2001 From: Cosmin Popovici Date: Sat, 15 Aug 2026 15:32:32 +0300 Subject: [PATCH 1/2] perf(serve): cap command palette template results to keep it responsive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rendering every template as a CommandItem froze the palette on the first keystroke in large projects (1000s of templates) — all instances mounted at once. Render only the matches, capped at 50, with a "showing N of M" hint when truncated. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/server/ui/App.vue | 44 ++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/server/ui/App.vue b/src/server/ui/App.vue index 7c629d55..ba35205d 100644 --- a/src/server/ui/App.vue +++ b/src/server/ui/App.vue @@ -211,21 +211,15 @@ async function copySource() { await navigator.clipboard.writeText(el.textContent || '') } -const commandGrouped = computed(() => { - const groups: Record = {} - - for (const t of templates.value) { - const parts = t.path.split('/') - const dir = parts.length > 1 ? parts.slice(0, -1).join('/') : '.' - if (!groups[dir]) groups[dir] = [] - groups[dir].push(t) - } - - return groups -}) - const { contains } = useFilter({ sensitivity: 'base' }) +/** + * Cap how many template results render at once. Rendering every template as + * a CommandItem freezes the palette on the first keystroke in large projects + * (100s–1000s of templates), so we only render the matches, up to this many. + */ +const MAX_TEMPLATE_RESULTS = 50 + const filteredTemplatesCount = computed(() => { const tokens = commandSearch.value.split(/\s+/).filter(Boolean) if (tokens.length === 0) return 0 @@ -237,6 +231,25 @@ const filteredTemplatesCount = computed(() => { return count }) +/** The matching templates (capped), grouped by directory, for rendering. */ +const filteredCommandGrouped = computed(() => { + const groups: Record = {} + const tokens = commandSearch.value.split(/\s+/).filter(Boolean) + if (tokens.length === 0) return groups + let count = 0 + for (const t of templates.value) { + const haystack = `${getFileName(t.path)} ${t.path.split('/').join(' ')}` + if (!tokens.every(token => contains(haystack, token))) continue + const parts = t.path.split('/') + const dir = parts.length > 1 ? parts.slice(0, -1).join('/') : '.' + ;(groups[dir] ??= []).push(t) + if (++count >= MAX_TEMPLATE_RESULTS) break + } + return groups +}) + +const templatesTruncated = computed(() => filteredTemplatesCount.value > MAX_TEMPLATE_RESULTS) + function getFileName(path: string) { return path.split('/').pop() || path } @@ -539,7 +552,7 @@ onUnmounted(() => {