import { CloseButton, Group, ScrollArea, Stack, Text, TextInput, Title, } from "@mantine/core"; import type { Tool } from "@modelcontextprotocol/sdk/types.js"; import { ListChangedIndicator } from "../../elements/ListChangedIndicator/ListChangedIndicator"; import { AppListItem } from "../AppListItem/AppListItem"; import { useScrollMemory } from "../../../hooks/useScrollMemory"; export interface AppControlsProps { tools: Tool[]; selectedName?: string; // Search text is controlled by the parent (App, via AppsScreen) so it // persists across tab navigation within a live session — see #1417. searchText?: string; listChanged: boolean; onRefreshList: () => void; onSearchChange: (value: string) => void; onSelectApp: (name: string) => void; } const LIST_MAX_HEIGHT = "calc(100vh - var(--app-shell-header-height, 0px) - var(--mantine-spacing-xl) * 2 - 220px)"; const EmptyState = Text.withProps({ c: "dimmed", ta: "center", py: "xl", }); export function AppControls({ tools, selectedName, searchText = "", listChanged, onRefreshList, onSearchChange, onSelectApp, }: AppControlsProps) { const viewportRef = useScrollMemory("apps-sidebar"); const query = searchText.toLowerCase(); const filteredTools = searchText ? tools.filter( (tool) => tool.name.toLowerCase().includes(query) || (tool.title?.toLowerCase().includes(query) ?? false), ) : tools; return ( MCP Apps ({tools.length}) onSearchChange(e.currentTarget.value)} rightSectionPointerEvents="auto" rightSection={ searchText ? ( onSearchChange("")} /> ) : null } /> {filteredTools.length === 0 ? ( {tools.length === 0 ? "No apps available" : "No matching apps"} ) : ( filteredTools.map((tool) => ( { if (tool.name !== selectedName) onSelectApp(tool.name); }} /> )) )} ); }