feat: unified global search & filter system with Cmd-K navigation

Replace per-page filtering with a single GlobalFilterProvider (time range +
status) consumed by a redesigned TopBar across all pages. Lift CommandPalette
to App level so Cmd-K works globally with filtered results that navigate to
exchanges, routes, agents, and applications. Sidebar auto-reveals and selects
the target entry on Cmd-K navigation via location state.

- Extract shared time preset utilities (computePresetRange, DEFAULT_PRESETS)
- Add GlobalFilterProvider (time range + status) and CommandPaletteProvider
- Add TimeRangeDropdown primitive with Popover preset list
- Redesign TopBar: breadcrumb | time dropdown | status pills | search | env
- Add application category to Cmd-K search
- Remove FilterBar and local DateRangePicker from Dashboard/Metrics pages
- Filter AgentHealth EventFeed by global time range
- Remove shift/onSearchClick props from TopBar

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-18 20:06:25 +01:00
parent 7cd8864f2c
commit 5de97dab14
26 changed files with 598 additions and 244 deletions

View File

@@ -2,6 +2,7 @@ import {
useState,
useRef,
useCallback,
useEffect,
useMemo,
type ReactNode,
type KeyboardEvent,
@@ -31,6 +32,7 @@ export interface SidebarTreeProps {
className?: string
filterQuery?: string
persistKey?: string // sessionStorage key to persist expand state across remounts
autoRevealPath?: string | null // when set, auto-expand the parent of the matching node
}
// ── Star icon SVGs ───────────────────────────────────────────────────────────
@@ -138,6 +140,7 @@ export function SidebarTree({
className,
filterQuery,
persistKey,
autoRevealPath,
}: SidebarTreeProps) {
const navigate = useNavigate()
@@ -146,6 +149,27 @@ export function SidebarTree({
() => persistKey ? readExpandState(persistKey) : new Set(),
)
// Auto-expand parent when autoRevealPath changes (e.g. from Cmd-K navigation)
useEffect(() => {
if (!autoRevealPath) return
for (const node of nodes) {
// Check if a child of this node matches the reveal path
if (node.children?.some((child) => child.path === autoRevealPath)) {
if (!userExpandedIds.has(node.id)) {
setUserExpandedIds((prev) => {
const next = new Set(prev)
next.add(node.id)
if (persistKey) writeExpandState(persistKey, next)
return next
})
}
break
}
// Also check if the node itself matches (top-level node, no parent to expand)
if (node.path === autoRevealPath) break
}
}, [autoRevealPath]) // eslint-disable-line react-hooks/exhaustive-deps
// Filter
const { filtered, matchedParentIds } = useMemo(
() => filterNodes(nodes, filterQuery ?? ''),