diff --git a/ui/src/components/LayoutShell.tsx b/ui/src/components/LayoutShell.tsx index 423fee78..ec3c3c68 100644 --- a/ui/src/components/LayoutShell.tsx +++ b/ui/src/components/LayoutShell.tsx @@ -443,7 +443,14 @@ function LayoutContent() { if (sidebarRevealPath.startsWith('/admin') && !adminOpen) setAdminOpen(true); }, [sidebarRevealPath]); // eslint-disable-line react-hooks/exhaustive-deps - const effectiveSelectedPath = sidebarRevealPath ?? location.pathname; + // Normalize path so sidebar highlights the app regardless of which tab is active. + // Sidebar nodes use /exchanges/{slug} paths, so map /dashboard/{slug}, /apps/{slug}, etc. + const effectiveSelectedPath = useMemo(() => { + const raw = sidebarRevealPath ?? location.pathname; + const match = raw.match(/^\/(exchanges|dashboard|apps|runtime)\/([^/]+)(\/.*)?$/); + if (match) return `/exchanges/${match[2]}${match[3] ?? ''}`; + return raw; + }, [sidebarRevealPath, location.pathname]); // --- About Me dialog ----------------------------------------------- const [aboutMeOpen, setAboutMeOpen] = useState(false); @@ -629,6 +636,17 @@ function LayoutContent() { return; } + const exchangeMatch = path.match(/^\/exchanges\/([^/]+)(?:\/(.+))?$/); + if (exchangeMatch) { + const [, sAppId, sRouteId] = exchangeMatch; + if (scope.tab === 'apps') { + navigate(`/apps/${sAppId}`, { state }); + } else { + navigate(sRouteId ? `/${scope.tab}/${sAppId}/${sRouteId}` : `/${scope.tab}/${sAppId}`, { state }); + } + return; + } + const agentMatch = path.match(/^\/agents\/([^/]+)(?:\/(.+))?$/); if (agentMatch) { const [, sAppId, sInstanceId] = agentMatch;