diff --git a/ui/src/components/LayoutShell.tsx b/ui/src/components/LayoutShell.tsx index 9f58baaf..bc3b033a 100644 --- a/ui/src/components/LayoutShell.tsx +++ b/ui/src/components/LayoutShell.tsx @@ -210,7 +210,21 @@ function LayoutContent() { const handlePaletteSelect = useCallback((result: any) => { if (result.path) { - navigate(result.path, { state: result.path ? { sidebarReveal: result.path } : undefined }); + const state: Record = { sidebarReveal: result.path }; + + // For exchange/attribute results, pass selectedExchange in state + if (result.category === 'exchange' || result.category === 'attribute') { + const parts = result.path.split('/').filter(Boolean); + if (parts.length === 4 && parts[0] === 'exchanges') { + state.selectedExchange = { + executionId: parts[3], + applicationName: parts[1], + routeId: parts[2], + }; + } + } + + navigate(result.path, { state }); } setPaletteOpen(false); }, [navigate, setPaletteOpen]); diff --git a/ui/src/pages/Exchanges/ExchangesPage.tsx b/ui/src/pages/Exchanges/ExchangesPage.tsx index 31531a63..69a7606f 100644 --- a/ui/src/pages/Exchanges/ExchangesPage.tsx +++ b/ui/src/pages/Exchanges/ExchangesPage.tsx @@ -20,17 +20,35 @@ import type { SelectedExchange } from '../Dashboard/Dashboard'; export default function ExchangesPage() { const navigate = useNavigate(); const location = useLocation(); - const { appId: scopedAppId, routeId: scopedRouteId } = useParams<{ appId?: string; routeId?: string }>(); + const { appId: scopedAppId, routeId: scopedRouteId, exchangeId: scopedExchangeId } = + useParams<{ appId?: string; routeId?: string; exchangeId?: string }>(); // Restore selection from browser history state (enables Back/Forward) const stateSelected = (location.state as any)?.selectedExchange as SelectedExchange | undefined; - const [selected, setSelectedInternal] = useState(stateSelected ?? null); - // Sync from history state when the user navigates Back/Forward + // Derive selection from URL params when no state-based selection exists (Cmd-K, bookmarks) + const urlDerivedExchange: SelectedExchange | null = + (scopedExchangeId && scopedAppId && scopedRouteId) + ? { executionId: scopedExchangeId, applicationName: scopedAppId, routeId: scopedRouteId } + : null; + + const [selected, setSelectedInternal] = useState(stateSelected ?? urlDerivedExchange); + + // Sync selection from history state or URL params on navigation changes useEffect(() => { const restored = (location.state as any)?.selectedExchange as SelectedExchange | undefined; - setSelectedInternal(restored ?? null); - }, [location.state]); + if (restored) { + setSelectedInternal(restored); + } else if (scopedExchangeId && scopedAppId && scopedRouteId) { + setSelectedInternal({ + executionId: scopedExchangeId, + applicationName: scopedAppId, + routeId: scopedRouteId, + }); + } else { + setSelectedInternal(null); + } + }, [location.state, scopedExchangeId, scopedAppId, scopedRouteId]); const [splitPercent, setSplitPercent] = useState(50); const containerRef = useRef(null); @@ -52,10 +70,15 @@ export default function ExchangesPage() { }); }, [navigate, location.pathname, location.search, location.state]); - // Clear selection: push a history entry without selection (so Back returns to selected state) + // Clear selection: navigate up to route level when URL has exchangeId const handleClearSelection = useCallback(() => { setSelectedInternal(null); - }, []); + if (scopedExchangeId && scopedAppId && scopedRouteId) { + navigate(`/exchanges/${scopedAppId}/${scopedRouteId}`, { + state: { ...location.state, selectedExchange: undefined }, + }); + } + }, [scopedExchangeId, scopedAppId, scopedRouteId, navigate, location.state]); const handleSplitterDown = useCallback((e: React.PointerEvent) => { e.currentTarget.setPointerCapture(e.pointerId);