From b30a5b57603578c0f02aa46f206614b31eb303a3 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 1 Apr 2026 21:22:50 +0200 Subject: [PATCH] fix: prevent cmd-k scroll reset on catalog poll refresh The searchData useMemo recomputed on every catalog poll cycle because catalogData got a new array reference even when content was unchanged. This caused the CommandPalette list to re-render and reset scroll. Use a ref with deep equality check to keep a stable catalog reference, only updating when the actual data changes. Co-Authored-By: Claude Opus 4.6 (1M context) --- ui/src/components/LayoutShell.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ui/src/components/LayoutShell.tsx b/ui/src/components/LayoutShell.tsx index e03e0e5a..d1eeaf72 100644 --- a/ui/src/components/LayoutShell.tsx +++ b/ui/src/components/LayoutShell.tsx @@ -5,7 +5,7 @@ import { useRouteCatalog } from '../api/queries/catalog'; import { useAgents } from '../api/queries/agents'; import { useSearchExecutions } from '../api/queries/executions'; import { useAuthStore } from '../auth/auth-store'; -import { useState, useMemo, useCallback, useEffect } from 'react'; +import { useState, useMemo, useCallback, useEffect, useRef } from 'react'; import { ContentTabs } from './ContentTabs'; import { useScope } from '../hooks/useScope'; @@ -145,6 +145,13 @@ function LayoutContent() { [catalog, agents], ); + // Stable reference for catalog data — only changes when catalog/agents actually change, + // not on every poll cycle (prevents cmd-k scroll reset) + const catalogRef = useRef(catalogData); + if (catalogData !== catalogRef.current && JSON.stringify(catalogData) !== JSON.stringify(catalogRef.current)) { + catalogRef.current = catalogData; + } + const searchData: SearchResult[] = useMemo(() => { const exchangeItems: SearchResult[] = (exchangeResults?.data || []).map((e: any) => ({ id: e.executionId, @@ -178,8 +185,8 @@ function LayoutContent() { } } - return [...catalogData, ...exchangeItems, ...attributeItems]; - }, [catalogData, exchangeResults, debouncedQuery]); + return [...catalogRef.current, ...exchangeItems, ...attributeItems]; + }, [catalogRef.current, exchangeResults, debouncedQuery]); const isAdminPage = location.pathname.startsWith('/admin'); const breadcrumb = useMemo(() => {