From f2a25e3dd32f32ffb69f73244b14acb86a83066f Mon Sep 17 00:00:00 2001 From: rafavalls Date: Sun, 1 Mar 2026 13:12:04 -0300 Subject: [PATCH 01/16] feat(connections): add ConnectionInstanceRow component --- .../connections/connection-instance-row.tsx | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 apps/mesh/src/web/components/connections/connection-instance-row.tsx diff --git a/apps/mesh/src/web/components/connections/connection-instance-row.tsx b/apps/mesh/src/web/components/connections/connection-instance-row.tsx new file mode 100644 index 0000000000..e5240523bd --- /dev/null +++ b/apps/mesh/src/web/components/connections/connection-instance-row.tsx @@ -0,0 +1,79 @@ +import { cn } from "@deco/ui/lib/utils.ts"; +import type { ConnectionEntity } from "@decocms/mesh-sdk"; +import { ChevronRight } from "@untitledui/icons"; +import { User } from "@/web/components/user/user.tsx"; + +type StatusValue = "active" | "error" | "inactive"; + +function StatusDot({ status }: { status: StatusValue }) { + if (status === "active") { + return ( +
+ + Connected +
+ ); + } + + if (status === "error") { + return ( +
+ + Error +
+ ); + } + + return ( +
+ + Inactive +
+ ); +} + +export interface ConnectionInstanceRowProps { + connection: ConnectionEntity; + onClick: () => void; +} + +export function ConnectionInstanceRow({ + connection, + onClick, +}: ConnectionInstanceRowProps) { + return ( + + ); +} From 8af3fb3ddb12a4336b645f99a83b5ec32093a65a Mon Sep 17 00:00:00 2001 From: rafavalls Date: Sun, 1 Mar 2026 13:14:41 -0300 Subject: [PATCH 02/16] feat(connections): add ConnectionServiceGroup accordion component --- .../connections/connection-service-group.tsx | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 apps/mesh/src/web/components/connections/connection-service-group.tsx diff --git a/apps/mesh/src/web/components/connections/connection-service-group.tsx b/apps/mesh/src/web/components/connections/connection-service-group.tsx new file mode 100644 index 0000000000..c4660cc05a --- /dev/null +++ b/apps/mesh/src/web/components/connections/connection-service-group.tsx @@ -0,0 +1,85 @@ +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@deco/ui/components/collapsible.tsx"; +import type { ConnectionEntity } from "@decocms/mesh-sdk"; +import { ChevronDown, ChevronRight } from "@untitledui/icons"; +import { useState } from "react"; +import { IntegrationIcon } from "@/web/components/integration-icon.tsx"; +import { ConnectionInstanceRow } from "./connection-instance-row.tsx"; + +export interface ConnectionServiceGroupProps { + serviceName: string; + icon: string | null; + instances: ConnectionEntity[]; + defaultOpen?: boolean; + onInstanceClick: (connectionId: string) => void; +} + +export function ConnectionServiceGroup({ + serviceName, + icon, + instances, + defaultOpen, + onInstanceClick, +}: ConnectionServiceGroupProps) { + const firstInstance = instances[0]; + const isSolo = + instances.length === 1 && firstInstance != null && !firstInstance.app_name; + const resolvedDefaultOpen = defaultOpen ?? instances.length === 1; + const [open, setOpen] = useState(resolvedDefaultOpen); + + if (isSolo && firstInstance != null) { + return ( +
+ onInstanceClick(firstInstance.id)} + /> +
+ ); + } + + return ( + + + + + +
+ {instances.map((instance) => ( + onInstanceClick(instance.id)} + /> + ))} +
+
+
+ ); +} From 2c5d3fd1073f6ebf3e8348f05402e9bbcd77f286 Mon Sep 17 00:00:00 2001 From: rafavalls Date: Sun, 1 Mar 2026 13:14:43 -0300 Subject: [PATCH 03/16] feat(connections): add groupConnections utility --- apps/mesh/src/web/utils/group-connections.ts | 54 ++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 apps/mesh/src/web/utils/group-connections.ts diff --git a/apps/mesh/src/web/utils/group-connections.ts b/apps/mesh/src/web/utils/group-connections.ts new file mode 100644 index 0000000000..2e654cb292 --- /dev/null +++ b/apps/mesh/src/web/utils/group-connections.ts @@ -0,0 +1,54 @@ +import type { ConnectionEntity } from "@decocms/mesh-sdk"; + +export interface ConnectionGroup { + key: string; + serviceName: string; + icon: string | null; + instances: ConnectionEntity[]; +} + +export function groupConnections( + connections: ConnectionEntity[], +): ConnectionGroup[] { + const grouped = new Map(); + + for (const conn of connections) { + if (!conn.app_name) { + // Solo group — keyed by connection id + const key = `__solo__${conn.id}`; + grouped.set(key, { + key, + serviceName: conn.title, + icon: conn.icon ?? null, + instances: [conn], + }); + } else { + const existing = grouped.get(conn.app_name); + if (existing) { + existing.instances.push(conn); + } else { + grouped.set(conn.app_name, { + key: conn.app_name, + serviceName: conn.app_name, + icon: conn.icon ?? null, + instances: [conn], + }); + } + } + } + + const groups = Array.from(grouped.values()); + + // Sort: multi-instance groups first, then singles — within each tier, alphabetically + groups.sort((a, b) => { + const aIsMulti = a.instances.length > 1; + const bIsMulti = b.instances.length > 1; + + if (aIsMulti && !bIsMulti) return -1; + if (!aIsMulti && bIsMulti) return 1; + + return a.serviceName.localeCompare(b.serviceName); + }); + + return groups; +} From 5ed71c89e219cd42954f5882de4125f6324a7933 Mon Sep 17 00:00:00 2001 From: rafavalls Date: Sun, 1 Mar 2026 13:16:49 -0300 Subject: [PATCH 04/16] feat(connections): replace flat grid with grouped accordion list --- apps/mesh/src/web/routes/orgs/connections.tsx | 81 +++---------------- 1 file changed, 11 insertions(+), 70 deletions(-) diff --git a/apps/mesh/src/web/routes/orgs/connections.tsx b/apps/mesh/src/web/routes/orgs/connections.tsx index 67517f5a63..e14ac10a1f 100644 --- a/apps/mesh/src/web/routes/orgs/connections.tsx +++ b/apps/mesh/src/web/routes/orgs/connections.tsx @@ -3,7 +3,7 @@ import { CollectionDisplayButton } from "@/web/components/collections/collection import { CollectionSearch } from "@/web/components/collections/collection-search.tsx"; import { CollectionTableWrapper } from "@/web/components/collections/collection-table-wrapper.tsx"; import { type TableColumn } from "@/web/components/collections/collection-table.tsx"; -import { ConnectionCard } from "@/web/components/connections/connection-card.tsx"; +import { ConnectionServiceGroup } from "@/web/components/connections/connection-service-group.tsx"; import { ConnectionStatus } from "@/web/components/connections/connection-status.tsx"; import { EmptyState } from "@/web/components/empty-state.tsx"; import { ErrorBoundary } from "@/web/components/error-boundary"; @@ -15,6 +15,7 @@ import { useRegistryConnections } from "@/web/hooks/use-binding"; import { useListState } from "@/web/hooks/use-list-state"; import { authClient } from "@/web/lib/auth-client"; import { useAuthConfig } from "@/web/providers/auth-config-provider"; +import { groupConnections } from "@/web/utils/group-connections.ts"; import { extractItemsFromResponse, findListToolName, @@ -1518,83 +1519,23 @@ function OrgMcpsContent() { } /> ) : ( -
- {connections.map((connection) => ( - } - onClick={() => +
+ {groupConnections(connections).map((group) => ( + navigate({ to: "/$org/$project/mcps/$connectionId", params: { org: org.slug, project: ORG_ADMIN_PROJECT_SLUG, - connectionId: connection.id, + connectionId: id, }, }) } - headerActions={ - - - - - e.stopPropagation()} - > - { - e.stopPropagation(); - navigate({ - to: "/$org/$project/mcps/$connectionId", - params: { - org: org.slug, - project: ORG_ADMIN_PROJECT_SLUG, - connectionId: connection.id, - }, - }); - }} - > - - Open - - { - e.stopPropagation(); - dispatch({ type: "delete", connection }); - }} - > - - Delete - - - - } - body={} - footer={ -
-
- -
- - {connection.updated_at - ? formatTimeAgo(new Date(connection.updated_at)) - : "—"} - -
- } /> ))}
From 24cfb10e217d146ba64cec8b377cee3eb947ad29 Mon Sep 17 00:00:00 2001 From: rafavalls Date: Sun, 1 Mar 2026 13:18:15 -0300 Subject: [PATCH 05/16] refactor(connections): export ConnectionFields for settings sheet reuse --- .../web/components/details/connection/connection-sidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/mesh/src/web/components/details/connection/connection-sidebar.tsx b/apps/mesh/src/web/components/details/connection/connection-sidebar.tsx index 434d59884e..9d31f1a5e6 100644 --- a/apps/mesh/src/web/components/details/connection/connection-sidebar.tsx +++ b/apps/mesh/src/web/components/details/connection/connection-sidebar.tsx @@ -54,7 +54,7 @@ interface ConnectionSidebarProps { onRemoveOAuth?: () => void | Promise; } -function ConnectionFields({ +export function ConnectionFields({ form, connection, hasOAuthToken, From e37410b8acdbbb7b287ce18913e922acd5dabe59 Mon Sep 17 00:00:00 2001 From: rafavalls Date: Sun, 1 Mar 2026 13:18:17 -0300 Subject: [PATCH 06/16] feat(ui): add xl size to IntegrationIcon --- apps/mesh/src/web/components/integration-icon.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/mesh/src/web/components/integration-icon.tsx b/apps/mesh/src/web/components/integration-icon.tsx index d07a77590e..5e9104494d 100644 --- a/apps/mesh/src/web/components/integration-icon.tsx +++ b/apps/mesh/src/web/components/integration-icon.tsx @@ -16,6 +16,7 @@ const SIZE_CLASSES = { sm: "h-8 w-8", md: "h-12 w-12", lg: "h-16 w-16", + xl: "h-14 w-14", }; type Size = keyof typeof SIZE_CLASSES; @@ -26,6 +27,7 @@ const MIN_WIDTH_CLASSES: Record = { sm: "min-w-8", md: "min-w-12", lg: "min-w-16", + xl: "min-w-14", }; const ICON_SIZES: Record = { @@ -34,6 +36,7 @@ const ICON_SIZES: Record = { sm: 16, md: 24, lg: 32, + xl: 28, }; export function IntegrationIcon({ From d1353db9de0e66731810e861585d7acfd3234579 Mon Sep 17 00:00:00 2001 From: rafavalls Date: Sun, 1 Mar 2026 13:21:20 -0300 Subject: [PATCH 07/16] feat(connections): add ConnectionDetailHeader hero component --- .../connection/connection-detail-header.tsx | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 apps/mesh/src/web/components/details/connection/connection-detail-header.tsx diff --git a/apps/mesh/src/web/components/details/connection/connection-detail-header.tsx b/apps/mesh/src/web/components/details/connection/connection-detail-header.tsx new file mode 100644 index 0000000000..9a627d7f0f --- /dev/null +++ b/apps/mesh/src/web/components/details/connection/connection-detail-header.tsx @@ -0,0 +1,66 @@ +import { IntegrationIcon } from "@/web/components/integration-icon.tsx"; +import { ConnectionStatus } from "@/web/components/connections/connection-status.tsx"; +import type { ConnectionEntity } from "@decocms/mesh-sdk"; +import { Badge } from "@deco/ui/components/badge.tsx"; +import { Button } from "@deco/ui/components/button.tsx"; +import { Settings01 } from "@untitledui/icons"; + +interface ConnectionDetailHeaderProps { + connection: ConnectionEntity; + onOpenSettings: () => void; + onDisconnect: () => void; +} + +export function ConnectionDetailHeader({ + connection, + onOpenSettings, + onDisconnect, +}: ConnectionDetailHeaderProps) { + return ( +
+ +
+
+

+ {connection.title} +

+ {connection.app_name && ( + + {connection.app_name} + + )} + +
+ {connection.description && ( +

+ {connection.description} +

+ )} +
+
+ + +
+
+ ); +} From b0db62ec8365f9c2821d45472d81a14f1ec256d1 Mon Sep 17 00:00:00 2001 From: rafavalls Date: Sun, 1 Mar 2026 13:21:22 -0300 Subject: [PATCH 08/16] feat(connections): add read-only ConnectionCapabilities component --- .../connection/connection-capabilities.tsx | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 apps/mesh/src/web/components/details/connection/connection-capabilities.tsx diff --git a/apps/mesh/src/web/components/details/connection/connection-capabilities.tsx b/apps/mesh/src/web/components/details/connection/connection-capabilities.tsx new file mode 100644 index 0000000000..da9c056440 --- /dev/null +++ b/apps/mesh/src/web/components/details/connection/connection-capabilities.tsx @@ -0,0 +1,86 @@ +import { Tool01 } from "@untitledui/icons"; + +/** + * Converts a snake_case or dot.case tool function name to readable English. + * e.g. "repos.list" -> "List Repos", "create_issue" -> "Create Issue" + */ +function humanizeName(name: string): string { + const parts = name.replace(/[._]/g, " ").trim().split(/\s+/); + const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1); + + const verbs = new Set([ + "list", + "create", + "get", + "update", + "delete", + "fetch", + "search", + "run", + "trigger", + "review", + "send", + "check", + ]); + + const words = parts.map(capitalize); + // If last word is a verb, move it to front for natural reading + const lastWord = parts[parts.length - 1]; + if (words.length >= 2 && lastWord && verbs.has(lastWord)) { + const verb = words.pop()!; + return [verb, ...words].join(" "); + } + return words.join(" "); +} + +interface ConnectionCapabilitiesProps { + tools: Array<{ + name: string; + description?: string; + }>; +} + +export function ConnectionCapabilities({ tools }: ConnectionCapabilitiesProps) { + if (tools.length === 0) { + return ( +
+

+ Capabilities +

+

+ No tools discovered yet. The connection may still be connecting. +

+
+ ); + } + + return ( +
+
+

Capabilities

+

+ {tools.length} {tools.length === 1 ? "tool" : "tools"} available +

+
+
+ {tools.map((tool) => ( +
+
+ +
+
+
+ {humanizeName(tool.name)} +
+ {tool.description && ( +
+ {tool.description} +
+ )} +
+
+ ))} +
+
+ ); +} From 6ad950ae718f08704528640fe9a338826caa5fcc Mon Sep 17 00:00:00 2001 From: rafavalls Date: Sun, 1 Mar 2026 13:21:24 -0300 Subject: [PATCH 09/16] feat(connections): add ConnectionActivity bar chart component --- .../connection/connection-activity.tsx | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 apps/mesh/src/web/components/details/connection/connection-activity.tsx diff --git a/apps/mesh/src/web/components/details/connection/connection-activity.tsx b/apps/mesh/src/web/components/details/connection/connection-activity.tsx new file mode 100644 index 0000000000..b895ec1638 --- /dev/null +++ b/apps/mesh/src/web/components/details/connection/connection-activity.tsx @@ -0,0 +1,78 @@ +import { + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@deco/ui/components/chart.tsx"; +import { Bar, BarChart, XAxis } from "recharts"; + +const PLACEHOLDER_DATA = [ + { label: "Feb 16", calls: 28 }, + { label: "17", calls: 40 }, + { label: "18", calls: 22 }, + { label: "19", calls: 55 }, + { label: "20", calls: 18 }, + { label: "21", calls: 48 }, + { label: "22", calls: 35 }, + { label: "23", calls: 62 }, + { label: "24", calls: 44 }, + { label: "25", calls: 58 }, + { label: "26", calls: 36 }, + { label: "27", calls: 68 }, + { label: "28", calls: 52 }, + { label: "Mar 1", calls: 42 }, +]; + +const CHART_CONFIG = { + calls: { label: "Tool calls" }, +}; + +interface ConnectionActivityProps { + data?: Array<{ label: string; calls: number }>; + isPlaceholder?: boolean; +} + +export function ConnectionActivity({ + data = PLACEHOLDER_DATA, + isPlaceholder = true, +}: ConnectionActivityProps) { + return ( +
+
+
+

Activity

+

+ Tool calls per day, last 14 days +

+
+ {isPlaceholder && ( + + Sample data + + )} +
+
+ + + + } + cursor={{ fill: "var(--muted)" }} + /> + + + +
+
+ ); +} From 74405ac3ce6ce3347bd0eb260370b497d93350c3 Mon Sep 17 00:00:00 2001 From: rafavalls Date: Sun, 1 Mar 2026 13:23:10 -0300 Subject: [PATCH 10/16] feat(connections): add ConnectionAgentsPanel for detail page --- .../connection/connection-agents-panel.tsx | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 apps/mesh/src/web/components/details/connection/connection-agents-panel.tsx diff --git a/apps/mesh/src/web/components/details/connection/connection-agents-panel.tsx b/apps/mesh/src/web/components/details/connection/connection-agents-panel.tsx new file mode 100644 index 0000000000..4757115c2c --- /dev/null +++ b/apps/mesh/src/web/components/details/connection/connection-agents-panel.tsx @@ -0,0 +1,32 @@ +import { useProjectContext } from "@decocms/mesh-sdk"; +import type { ConnectionEntity } from "@decocms/mesh-sdk"; +import { ConnectionVirtualMCPsSection } from "./settings-tab/connection-virtual-mcps-section"; + +interface ConnectionAgentsPanelProps { + connection: ConnectionEntity; +} + +export function ConnectionAgentsPanel({ + connection, +}: ConnectionAgentsPanelProps) { + const { org } = useProjectContext(); + + return ( +
+
+

+ Used by agents +

+
+
+ +
+
+ ); +} From 3c670e01f9a18132474308f556817141a73eea32 Mon Sep 17 00:00:00 2001 From: rafavalls Date: Sun, 1 Mar 2026 13:23:13 -0300 Subject: [PATCH 11/16] feat(connections): add ConnectionInfoCard metadata component --- .../connection/connection-info-card.tsx | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 apps/mesh/src/web/components/details/connection/connection-info-card.tsx diff --git a/apps/mesh/src/web/components/details/connection/connection-info-card.tsx b/apps/mesh/src/web/components/details/connection/connection-info-card.tsx new file mode 100644 index 0000000000..5027e86b89 --- /dev/null +++ b/apps/mesh/src/web/components/details/connection/connection-info-card.tsx @@ -0,0 +1,48 @@ +import { User } from "@/web/components/user/user.tsx"; +import { formatTimeAgo } from "@/web/lib/format-time.ts"; +import type { ConnectionEntity } from "@decocms/mesh-sdk"; + +interface ConnectionInfoCardProps { + connection: ConnectionEntity; + onOpenSettings: () => void; +} + +export function ConnectionInfoCard({ + connection, + onOpenSettings, +}: ConnectionInfoCardProps) { + return ( +
+
+

Connection

+ +
+
+
+ Added by + +
+
+ Updated + + {connection.updated_at + ? formatTimeAgo(new Date(connection.updated_at)) + : "—"} + +
+
+ Protocol + + {connection.connection_type} + +
+
+
+ ); +} From 236700dc92e55fa69c2b01974e104732d1cfddda Mon Sep 17 00:00:00 2001 From: rafavalls Date: Sun, 1 Mar 2026 13:26:43 -0300 Subject: [PATCH 12/16] feat(connections): redesign detail page with hero header and dashboard layout - Replace sidebar+tabs layout with ConnectionDetailHeader + two-column dashboard - Move connection settings (URL, auth, token) into a slide-over Sheet - Add handleDisconnect with confirm dialog and post-delete navigation - Surface ConnectionActivity, ConnectionCapabilities, ConnectionAgentsPanel, ConnectionInfoCard panels - Remove unused tabs state, CollectionTabs, SaveActions, PinToSidebarButton, ConnectionSidebar, ReadmeTab imports --- .../components/details/connection/index.tsx | 323 ++++++++---------- 1 file changed, 139 insertions(+), 184 deletions(-) diff --git a/apps/mesh/src/web/components/details/connection/index.tsx b/apps/mesh/src/web/components/details/connection/index.tsx index c3ac3bcf5c..be2d223681 100644 --- a/apps/mesh/src/web/components/details/connection/index.tsx +++ b/apps/mesh/src/web/components/details/connection/index.tsx @@ -5,12 +5,7 @@ import { recordToEnvVars, type EnvVar, } from "@/web/components/env-vars-editor"; -import { getUIResourceUri } from "@/mcp-apps/types.ts"; -import { SaveActions } from "@/web/components/save-actions"; -import { - useBindingConnections, - useCollectionBindings, -} from "@/web/hooks/use-binding"; +import { useBindingConnections } from "@/web/hooks/use-binding"; import { useMCPAuthStatus } from "@/web/hooks/use-mcp-auth-status"; import { authenticateMcp } from "@/web/lib/mcp-oauth"; import { KEYS } from "@/web/lib/query-keys"; @@ -23,7 +18,22 @@ import { BreadcrumbSeparator, } from "@deco/ui/components/breadcrumb.tsx"; import { Button } from "@deco/ui/components/button.tsx"; -import { CollectionTabs } from "@/web/components/collections/collection-tabs.tsx"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@deco/ui/components/form.tsx"; +import { Input } from "@deco/ui/components/input.tsx"; +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, +} from "@deco/ui/components/sheet.tsx"; import { isStdioParameters, ORG_ADMIN_PROJECT_SLUG, @@ -41,29 +51,23 @@ import { } from "@decocms/mesh-sdk"; import { zodResolver } from "@hookform/resolvers/zod"; import { useQueryClient } from "@tanstack/react-query"; -import { - Link, - useNavigate, - useParams, - useSearch, -} from "@tanstack/react-router"; +import { Link, useNavigate, useParams } from "@tanstack/react-router"; import { Loading01 } from "@untitledui/icons"; -import { Suspense } from "react"; +import { Suspense, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; -import { ViewActions, ViewLayout } from "../layout"; -import { CollectionTab } from "./collection-tab"; -import { ConnectionSidebar } from "./connection-sidebar"; -import { PromptsTab } from "./prompts-tab"; -import { ReadmeTab } from "./readme-tab"; -import { ResourcesTab } from "./resources-tab"; +import { ViewLayout } from "../layout"; +import { ConnectionActivity } from "./connection-activity.tsx"; +import { ConnectionAgentsPanel } from "./connection-agents-panel.tsx"; +import { ConnectionCapabilities } from "./connection-capabilities.tsx"; +import { ConnectionDetailHeader } from "./connection-detail-header.tsx"; +import { ConnectionInfoCard } from "./connection-info-card.tsx"; +import { ConnectionFields } from "./connection-sidebar.tsx"; import { SettingsTab } from "./settings-tab"; import { connectionFormSchema, type ConnectionFormData, } from "./settings-tab/schema"; -import { ToolsTab } from "./tools-tab"; -import { UiTab } from "./ui-tab"; /** * Check if STDIO params look like an NPX command @@ -256,14 +260,9 @@ function ConnectionInspectorViewWithConnection({ connection, connectionId, org, - requestedTabId, - collections, onUpdate, isUpdating, - prompts, - resources, tools, - isLoadingTools, }: { connection: ConnectionEntity; connectionId: string; @@ -293,6 +292,8 @@ function ConnectionInspectorViewWithConnection({ const queryClient = useQueryClient(); const connectionActions = useConnectionActions(); + const [settingsOpen, setSettingsOpen] = useState(false); + const authStatus = useMCPAuthStatus({ connectionId: connectionId, }); @@ -308,12 +309,6 @@ function ConnectionInspectorViewWithConnection({ }); const hasMcpBinding = mcpBindingConnections.length > 0; - // Check if connection has repository info for README tab (stored in metadata) - const repository = connection?.metadata?.repository as - | { url?: string; source?: string; subfolder?: string } - | undefined; - const hasRepository = !!repository?.url; - // Form state lifted to parent const form = useForm({ resolver: zodResolver(connectionFormSchema), @@ -441,62 +436,20 @@ function ConnectionInspectorViewWithConnection({ } }; - const toolsCount = tools.length; - const promptsCount = prompts.length; - const resourcesCount = resources.length; - const uiToolsCount = tools.filter((t) => !!getUIResourceUri(t._meta)).length; - - // Show Tools tab if we have tools OR if we're still loading them - // This handles VIRTUAL connections and others that fetch tools dynamically - const showToolsTab = toolsCount > 0 || isLoadingTools; - - const tabs = [ - { id: "settings", label: "Settings" }, - ...(isMCPAuthenticated && showToolsTab - ? [ - { - id: "tools", - label: "Tools", - count: isLoadingTools ? undefined : toolsCount, - }, - ] - : []), - ...(isMCPAuthenticated && promptsCount > 0 - ? [{ id: "prompts", label: "Prompts", count: promptsCount }] - : []), - ...(isMCPAuthenticated && resourcesCount > 0 - ? [{ id: "resources", label: "Resources", count: resourcesCount }] - : []), - ...(isMCPAuthenticated && uiToolsCount > 0 - ? [{ id: "ui", label: "UI", count: uiToolsCount }] - : []), - ...(isMCPAuthenticated - ? (collections || []).map((c) => ({ id: c.name, label: c.displayName })) - : []), - ...(hasRepository ? [{ id: "readme", label: "README" }] : []), - ]; - - // Default to "tools" when authenticated (if tools tab exists), otherwise "settings" - const defaultTab = - isMCPAuthenticated && tabs.some((t) => t.id === "tools") - ? "tools" - : "settings"; - - const activeTabId = tabs.some((t) => t.id === requestedTabId) - ? requestedTabId - : defaultTab; - - const handleTabChange = (tabId: string) => { + const handleDisconnect = async () => { + if ( + !window.confirm( + `Disconnect "${connection.title}"? This cannot be undone.`, + ) + ) + return; + await connectionActions.delete.mutateAsync(connection.id); navigate({ - search: (prev: { tab?: string }) => ({ ...prev, tab: tabId }), - replace: true, + to: "/$org/$project/mcps", + params: { org, project: ORG_ADMIN_PROJECT_SLUG }, }); }; - const activeCollection = (collections || []).find( - (c) => c.name === activeTabId, - ); - const breadcrumb = ( @@ -519,110 +472,112 @@ function ConnectionInspectorViewWithConnection({ ); return ( - - - - -
- {/* Fixed left sidebar */} -
- + {/* Settings Sheet */} + + + + Configure connection + + Update the URL, authentication, and other technical settings. + + +
+
+
+ ( + + Name + + + + + + )} + /> + ( + + Description + + + + + + )} + /> +
+ + {hasMcpBinding && ( + + )} +
+ + {hasAnyChanges && ( + + )} +
+
+
+
+
+ + {/* Main page */} + +
+ setSettingsOpen(true)} + onDisconnect={handleDisconnect} /> -
- - {/* Right side - Tabs + Content */} -
- {/* Tabs header */} -
- -
- - {/* Tab content */}
- - - -
- } - > - {activeTabId === "tools" ? ( - - ) : activeTabId === "ui" ? ( - - ) : activeTabId === "prompts" ? ( - - ) : activeTabId === "resources" ? ( - - ) : activeTabId === "settings" ? ( - handleTabChange("readme") - : undefined - } - /> - ) : activeTabId === "readme" && hasRepository ? ( - - ) : activeCollection && isMCPAuthenticated ? ( - - ) : ( - - )} - - +
+ {/* Left column */} +
+ + +
+ {/* Right column */} +
+ + setSettingsOpen(true)} + /> +
+
-
-
+ + ); } From da1bc3f79ff5559b0973db90e358d9185cee5d1d Mon Sep 17 00:00:00 2001 From: rafavalls Date: Sun, 1 Mar 2026 13:30:30 -0300 Subject: [PATCH 13/16] fix(connections): final QA cleanup after redesign - Remove unused imports: useSearch, useCollectionBindings, useMCPPromptsListQuery, useMCPResourcesListQuery - Remove dead props from ConnectionInspectorViewWithConnection (requestedTabId, collections, prompts, resources, isLoadingTools) - Remove corresponding dead variables and hook calls from ConnectionInspectorViewContent --- .../components/details/connection/index.tsx | 39 +------------------ 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/apps/mesh/src/web/components/details/connection/index.tsx b/apps/mesh/src/web/components/details/connection/index.tsx index be2d223681..0ca593e9b9 100644 --- a/apps/mesh/src/web/components/details/connection/index.tsx +++ b/apps/mesh/src/web/components/details/connection/index.tsx @@ -40,8 +40,6 @@ import { useConnection, useConnectionActions, useMCPClient, - useMCPPromptsListQuery, - useMCPResourcesListQuery, useMCPToolsListQuery, useProjectContext, type ConnectionEntity, @@ -267,17 +265,8 @@ function ConnectionInspectorViewWithConnection({ connection: ConnectionEntity; connectionId: string; org: string; - requestedTabId: string; - collections: ReturnType; onUpdate: (connection: Partial) => Promise; isUpdating: boolean; - prompts: Array<{ name: string; description?: string }>; - resources: Array<{ - uri: string; - name?: string; - description?: string; - mimeType?: string; - }>; tools: Array<{ name: string; description?: string; @@ -286,7 +275,6 @@ function ConnectionInspectorViewWithConnection({ annotations?: ToolDefinition["annotations"]; _meta?: Record; }>; - isLoadingTools: boolean; }) { const navigate = useNavigate({ from: "/$org/$project/mcps/$connectionId" }); const queryClient = useQueryClient(); @@ -588,26 +576,15 @@ function ConnectionInspectorViewContent() { }); const { org: projectOrg } = useProjectContext(); - // We can use search params for active tab if we want persistent tabs - const search = useSearch({ from: "/shell/$org/$project/mcps/$connectionId" }); - const requestedTabId = search.tab ?? ""; - const connection = useConnection(connectionId); const actions = useConnectionActions(); - // Detect collection bindings - const collections = useCollectionBindings(connection ?? undefined); - // Get MCP client for this connection (suspense-based) const client = useMCPClient({ connectionId, orgId: projectOrg.id, }); - // Fetch prompts and resources using SDK hooks - const { data: promptsData } = useMCPPromptsListQuery({ client }); - const { data: resourcesData } = useMCPResourcesListQuery({ client }); - // Fetch tools - uses cached if available, otherwise fetches dynamically // VIRTUAL connections always fetch dynamically because: // 1. Their tools column contains virtual tool definitions (code), not cached downstream tools @@ -615,20 +592,11 @@ function ConnectionInspectorViewContent() { const isVirtualConnection = connection?.connection_type === "VIRTUAL"; const hasCachedTools = !isVirtualConnection && connection?.tools && connection.tools.length > 0; - const { data: toolsData, isLoading: isLoadingTools } = useMCPToolsListQuery({ + const { data: toolsData } = useMCPToolsListQuery({ client, enabled: !hasCachedTools, }); - const prompts = (promptsData?.prompts ?? []).map((p) => ({ - name: p.name, - description: p.description, - })); - const resources = (resourcesData?.resources ?? []).map((r) => ({ - uri: r.uri, - name: r.name, - description: r.description, - })); const tools = hasCachedTools ? (connection.tools ?? []) : (toolsData?.tools ?? []).map((t) => ({ @@ -681,14 +649,9 @@ function ConnectionInspectorViewContent() { org={org} connection={connection} connectionId={connectionId} - requestedTabId={requestedTabId} - collections={collections} onUpdate={handleUpdateConnection} isUpdating={actions.update.isPending} - prompts={prompts} - resources={resources} tools={tools} - isLoadingTools={isLoadingTools} /> ); } From ce443722aa02a812e6634809363760ce5def5628 Mon Sep 17 00:00:00 2001 From: rafavalls Date: Sun, 1 Mar 2026 14:37:32 -0300 Subject: [PATCH 14/16] feat(connections): polish redesign with real monitoring data and improved UX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Connect activity chart to real MONITORING_LOGS_LIST data with 7d/14d/30d timeframe toggle - Add prompts and resources tabs to capabilities section (Tools / Prompts / Resources) - Replace Sheet drawer with Dialog for settings — better UX than a sliding panel - Add IntegrationIcon to solo connection rows so they are visually recognizable - Design polish: header hierarchy, status dots, disconnect button styling - Add connectionActivity query key to KEYS constant Co-Authored-By: Claude Sonnet 4.6 --- .../connections/connection-instance-row.tsx | 31 ++- .../connections/connection-service-group.tsx | 1 + .../connection/connection-activity.tsx | 223 ++++++++++++++---- .../connection/connection-capabilities.tsx | 216 ++++++++++++++--- .../connection/connection-detail-header.tsx | 46 ++-- .../components/details/connection/index.tsx | 65 +++-- apps/mesh/src/web/lib/query-keys.ts | 2 + 7 files changed, 472 insertions(+), 112 deletions(-) diff --git a/apps/mesh/src/web/components/connections/connection-instance-row.tsx b/apps/mesh/src/web/components/connections/connection-instance-row.tsx index e5240523bd..1b607aa9c3 100644 --- a/apps/mesh/src/web/components/connections/connection-instance-row.tsx +++ b/apps/mesh/src/web/components/connections/connection-instance-row.tsx @@ -1,3 +1,4 @@ +import { IntegrationIcon } from "@/web/components/integration-icon.tsx"; import { cn } from "@deco/ui/lib/utils.ts"; import type { ConnectionEntity } from "@decocms/mesh-sdk"; import { ChevronRight } from "@untitledui/icons"; @@ -8,8 +9,8 @@ type StatusValue = "active" | "error" | "inactive"; function StatusDot({ status }: { status: StatusValue }) { if (status === "active") { return ( -
- +
+ Connected
); @@ -17,16 +18,16 @@ function StatusDot({ status }: { status: StatusValue }) { if (status === "error") { return ( -
- +
+ Error
); } return ( -
- +
+ Inactive
); @@ -35,29 +36,41 @@ function StatusDot({ status }: { status: StatusValue }) { export interface ConnectionInstanceRowProps { connection: ConnectionEntity; onClick: () => void; + /** Show the integration icon — used for solo connections that render without an accordion header */ + showIcon?: boolean; } export function ConnectionInstanceRow({ connection, onClick, + showIcon = false, }: ConnectionInstanceRowProps) { return ( ); diff --git a/apps/mesh/src/web/components/connections/connection-service-group.tsx b/apps/mesh/src/web/components/connections/connection-service-group.tsx index c4660cc05a..1a6f97b52c 100644 --- a/apps/mesh/src/web/components/connections/connection-service-group.tsx +++ b/apps/mesh/src/web/components/connections/connection-service-group.tsx @@ -36,6 +36,7 @@ export function ConnectionServiceGroup({ onInstanceClick(firstInstance.id)} + showIcon />
); diff --git a/apps/mesh/src/web/components/details/connection/connection-activity.tsx b/apps/mesh/src/web/components/details/connection/connection-activity.tsx index b895ec1638..2fd7ff4344 100644 --- a/apps/mesh/src/web/components/details/connection/connection-activity.tsx +++ b/apps/mesh/src/web/components/details/connection/connection-activity.tsx @@ -1,58 +1,111 @@ +import { + calculateStats, + type MonitoringLogsResponse as BaseMonitoringLogsResponse, +} from "@/web/components/monitoring/monitoring-stats-row.tsx"; import { ChartContainer, ChartTooltip, ChartTooltipContent, } from "@deco/ui/components/chart.tsx"; -import { Bar, BarChart, XAxis } from "recharts"; +import { KEYS } from "@/web/lib/query-keys.ts"; +import { cn } from "@deco/ui/lib/utils.ts"; +import { + SELF_MCP_ALIAS_ID, + useMCPClient, + useProjectContext, +} from "@decocms/mesh-sdk"; +import { useSuspenseQuery } from "@tanstack/react-query"; +import { Suspense, useState } from "react"; +import { Bar, BarChart, Cell, XAxis } from "recharts"; + +type Timeframe = "7d" | "14d" | "30d"; -const PLACEHOLDER_DATA = [ - { label: "Feb 16", calls: 28 }, - { label: "17", calls: 40 }, - { label: "18", calls: 22 }, - { label: "19", calls: 55 }, - { label: "20", calls: 18 }, - { label: "21", calls: 48 }, - { label: "22", calls: 35 }, - { label: "23", calls: 62 }, - { label: "24", calls: 44 }, - { label: "25", calls: 58 }, - { label: "26", calls: 36 }, - { label: "27", calls: 68 }, - { label: "28", calls: 52 }, - { label: "Mar 1", calls: 42 }, +const TIMEFRAMES: { value: Timeframe; label: string }[] = [ + { value: "7d", label: "7d" }, + { value: "14d", label: "14d" }, + { value: "30d", label: "30d" }, ]; +function getDateRange(timeframe: Timeframe): { + startDate: Date; + endDate: Date; +} { + const end = new Date(); + const start = new Date(end); + if (timeframe === "7d") start.setDate(start.getDate() - 7); + else if (timeframe === "14d") start.setDate(start.getDate() - 14); + else start.setDate(start.getDate() - 30); + return { startDate: start, endDate: end }; +} + const CHART_CONFIG = { calls: { label: "Tool calls" }, + errors: { label: "Errors" }, }; -interface ConnectionActivityProps { - data?: Array<{ label: string; calls: number }>; - isPlaceholder?: boolean; +interface ActivityChartProps { + connectionId: string; + orgId: string; + timeframe: Timeframe; } -export function ConnectionActivity({ - data = PLACEHOLDER_DATA, - isPlaceholder = true, -}: ConnectionActivityProps) { +function ActivityChart({ connectionId, orgId, timeframe }: ActivityChartProps) { + const client = useMCPClient({ connectionId: SELF_MCP_ALIAS_ID, orgId }); + const dateRange = getDateRange(timeframe); + + const { data } = useSuspenseQuery({ + queryKey: KEYS.connectionActivity(connectionId, timeframe), + queryFn: async () => { + const result = (await client.callTool({ + name: "MONITORING_LOGS_LIST", + arguments: { + startDate: dateRange.startDate.toISOString(), + endDate: dateRange.endDate.toISOString(), + connectionId, + limit: 2000, + offset: 0, + }, + })) as { structuredContent?: unknown }; + return (result.structuredContent ?? result) as BaseMonitoringLogsResponse; + }, + staleTime: 5 * 60 * 1000, + }); + + const stats = calculateStats(data?.logs ?? [], dateRange); + const chartData = stats.data; + const hasData = stats.totalCalls > 0; + return ( -
-
+
+ {/* Summary numbers */} +
-

Activity

-

- Tool calls per day, last 14 days +

+ {stats.totalCalls.toLocaleString()}

+

Tool calls

- {isPlaceholder && ( - - Sample data - + {stats.totalErrors > 0 && ( +
+

+ {stats.totalErrors.toLocaleString()} +

+

Errors

+
+ )} + {stats.avgDurationMs > 0 && ( +
+

+ {Math.round(stats.avgDurationMs)}ms +

+

Avg latency

+
)}
-
- - + + {hasData ? ( + + } + content={ + { + const first = Array.isArray(payload) + ? payload[0] + : undefined; + return first && + typeof first === "object" && + "payload" in first + ? ((first as { payload?: { label?: string } }).payload + ?.label ?? "") + : ""; + }} + /> + } cursor={{ fill: "var(--muted)" }} /> - + + {chartData.map((entry, index) => ( + 50 + ? "var(--destructive)" + : "var(--foreground)" + } + fillOpacity={ + entry.calls === 0 ? 0.2 : entry.errorRate > 50 ? 0.7 : 0.85 + } + /> + ))} + + ) : ( +
+

+ No activity in this period +

+
+ )} +
+ ); +} + +function ActivitySkeleton() { + return ( +
+
+
+
+
+
+
+
+
+ ); +} + +interface ConnectionActivityProps { + connectionId: string; +} + +export function ConnectionActivity({ connectionId }: ConnectionActivityProps) { + const [timeframe, setTimeframe] = useState("14d"); + const { org } = useProjectContext(); + + return ( +
+
+
+

Activity

+
+
+ {TIMEFRAMES.map((tf) => ( + + ))} +
+ }> + +
); } diff --git a/apps/mesh/src/web/components/details/connection/connection-capabilities.tsx b/apps/mesh/src/web/components/details/connection/connection-capabilities.tsx index da9c056440..55d007bfc7 100644 --- a/apps/mesh/src/web/components/details/connection/connection-capabilities.tsx +++ b/apps/mesh/src/web/components/details/connection/connection-capabilities.tsx @@ -1,4 +1,6 @@ -import { Tool01 } from "@untitledui/icons"; +import { cn } from "@deco/ui/lib/utils.ts"; +import { BookOpen01, Columns01, Tool01 } from "@untitledui/icons"; +import { useState } from "react"; /** * Converts a snake_case or dot.case tool function name to readable English. @@ -33,22 +35,92 @@ function humanizeName(name: string): string { return words.join(" "); } +interface Tool { + name: string; + description?: string; +} + +interface Prompt { + name: string; + description?: string; +} + +interface Resource { + name: string; + description?: string; + uri?: string; +} + interface ConnectionCapabilitiesProps { - tools: Array<{ - name: string; - description?: string; - }>; + tools: Tool[]; + prompts?: Prompt[]; + resources?: Resource[]; +} + +type Tab = "tools" | "prompts" | "resources"; + +function EmptyCapabilities({ label }: { label: string }) { + return ( +
+

No {label} available.

+
+ ); } -export function ConnectionCapabilities({ tools }: ConnectionCapabilitiesProps) { - if (tools.length === 0) { +export function ConnectionCapabilities({ + tools, + prompts = [], + resources = [], +}: ConnectionCapabilitiesProps) { + const hasTools = tools.length > 0; + const hasPrompts = prompts.length > 0; + const hasResources = resources.length > 0; + + const tabs = [ + { + id: "tools" as Tab, + label: "Tools", + count: tools.length, + icon: Tool01, + show: true, + }, + { + id: "prompts" as Tab, + label: "Prompts", + count: prompts.length, + icon: BookOpen01, + show: hasPrompts, + }, + { + id: "resources" as Tab, + label: "Resources", + count: resources.length, + icon: Columns01, + show: hasResources, + }, + ].filter((t) => t.show); + + const [activeTab, setActiveTab] = useState("tools"); + + // If the active tab has no content, reset to tools + const resolvedTab = + activeTab === "prompts" && !hasPrompts + ? "tools" + : activeTab === "resources" && !hasResources + ? "tools" + : activeTab; + + const totalItems = tools.length + prompts.length + resources.length; + + if (totalItems === 0) { return (

Capabilities

- No tools discovered yet. The connection may still be connecting. + No capabilities discovered yet. The connection may still be + connecting.

); @@ -56,31 +128,121 @@ export function ConnectionCapabilities({ tools }: ConnectionCapabilitiesProps) { return (
-
+

Capabilities

-

- {tools.length} {tools.length === 1 ? "tool" : "tools"} available -

+ {tabs.length > 1 && ( +
+ {tabs.map((tab) => ( + + ))} +
+ )} + {tabs.length === 1 && ( +

+ {tools.length} {tools.length === 1 ? "tool" : "tools"} +

+ )}
-
- {tools.map((tool) => ( -
-
- + + {resolvedTab === "tools" && ( +
+ {hasTools ? ( + tools.map((tool) => ( +
+
+ +
+
+
+ {humanizeName(tool.name)} +
+ {tool.description && ( +
+ {tool.description} +
+ )} +
+
+ )) + ) : ( + + )} +
+ )} + + {resolvedTab === "prompts" && ( +
+ {prompts.map((prompt) => ( +
+
+ +
+
+
+ {humanizeName(prompt.name)} +
+ {prompt.description && ( +
+ {prompt.description} +
+ )} +
-
-
- {humanizeName(tool.name)} + ))} +
+ )} + + {resolvedTab === "resources" && ( +
+ {resources.map((resource) => ( +
+
+
- {tool.description && ( -
- {tool.description} +
+
+ {resource.name}
- )} + {resource.description && ( +
+ {resource.description} +
+ )} + {resource.uri && ( +
+ {resource.uri} +
+ )} +
-
- ))} -
+ ))} +
+ )}
); } diff --git a/apps/mesh/src/web/components/details/connection/connection-detail-header.tsx b/apps/mesh/src/web/components/details/connection/connection-detail-header.tsx index 9a627d7f0f..f0a9a42c56 100644 --- a/apps/mesh/src/web/components/details/connection/connection-detail-header.tsx +++ b/apps/mesh/src/web/components/details/connection/connection-detail-header.tsx @@ -17,46 +17,62 @@ export function ConnectionDetailHeader({ onDisconnect, }: ConnectionDetailHeaderProps) { return ( -
+
-
-

+
+

{connection.title}

+ +
+
{connection.app_name && ( - + {connection.app_name} - + + )} + {connection.app_name && connection.description && ( + · + )} + {connection.description && ( +

+ {connection.description} +

)} -
- {connection.description && ( -

- {connection.description} -

+ {connection.tools && connection.tools.length > 0 && ( +
+ + {connection.tools.length}{" "} + {connection.tools.length === 1 ? "tool" : "tools"} + +
)}

-
+
diff --git a/apps/mesh/src/web/components/details/connection/index.tsx b/apps/mesh/src/web/components/details/connection/index.tsx index 0ca593e9b9..a7e5a9c45d 100644 --- a/apps/mesh/src/web/components/details/connection/index.tsx +++ b/apps/mesh/src/web/components/details/connection/index.tsx @@ -18,6 +18,13 @@ import { BreadcrumbSeparator, } from "@deco/ui/components/breadcrumb.tsx"; import { Button } from "@deco/ui/components/button.tsx"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@deco/ui/components/dialog.tsx"; import { Form, FormControl, @@ -27,19 +34,14 @@ import { FormMessage, } from "@deco/ui/components/form.tsx"; import { Input } from "@deco/ui/components/input.tsx"; -import { - Sheet, - SheetContent, - SheetDescription, - SheetHeader, - SheetTitle, -} from "@deco/ui/components/sheet.tsx"; import { isStdioParameters, ORG_ADMIN_PROJECT_SLUG, useConnection, useConnectionActions, useMCPClient, + useMCPPromptsListQuery, + useMCPResourcesListQuery, useMCPToolsListQuery, useProjectContext, type ConnectionEntity, @@ -261,6 +263,8 @@ function ConnectionInspectorViewWithConnection({ onUpdate, isUpdating, tools, + prompts, + resources, }: { connection: ConnectionEntity; connectionId: string; @@ -275,6 +279,8 @@ function ConnectionInspectorViewWithConnection({ annotations?: ToolDefinition["annotations"]; _meta?: Record; }>; + prompts: Array<{ name: string; description?: string }>; + resources: Array<{ name: string; description?: string; uri?: string }>; }) { const navigate = useNavigate({ from: "/$org/$project/mcps/$connectionId" }); const queryClient = useQueryClient(); @@ -461,15 +467,15 @@ function ConnectionInspectorViewWithConnection({ return ( <> - {/* Settings Sheet */} - - - - Configure connection - + {/* Settings Dialog */} + + + + Configure connection + Update the URL, authentication, and other technical settings. - - + +
@@ -535,8 +541,8 @@ function ConnectionInspectorViewWithConnection({
-
-
+ + {/* Main page */} @@ -550,8 +556,12 @@ function ConnectionInspectorViewWithConnection({
{/* Left column */}
- - + +
{/* Right column */}
@@ -607,6 +617,21 @@ function ConnectionInspectorViewContent() { _meta: t._meta as Record | undefined, })); + // Fetch prompts and resources from the MCP connection + const { data: promptsData } = useMCPPromptsListQuery({ client }); + const { data: resourcesData } = useMCPResourcesListQuery({ client }); + + const prompts = (promptsData?.prompts ?? []).map((p) => ({ + name: p.name, + description: p.description, + })); + + const resources = (resourcesData?.resources ?? []).map((r) => ({ + name: r.name, + description: r.description, + uri: r.uri, + })); + // Update connection handler const handleUpdateConnection = async ( updatedConnection: Partial, @@ -652,6 +677,8 @@ function ConnectionInspectorViewContent() { onUpdate={handleUpdateConnection} isUpdating={actions.update.isPending} tools={tools} + prompts={prompts} + resources={resources} /> ); } diff --git a/apps/mesh/src/web/lib/query-keys.ts b/apps/mesh/src/web/lib/query-keys.ts index 20a38913a7..df4bd95e66 100644 --- a/apps/mesh/src/web/lib/query-keys.ts +++ b/apps/mesh/src/web/lib/query-keys.ts @@ -147,6 +147,8 @@ export const KEYS = { }) => ["monitoring", "logs", filters] as const, monitoringLogsInfinite: (locator: string, paramsKey: string) => ["monitoring", "logs-infinite", locator, paramsKey] as const, + connectionActivity: (connectionId: string, timeframe: string) => + ["monitoring", "activity", connectionId, timeframe] as const, // Monitoring dashboards monitoringDashboards: (locator: ProjectLocator) => From 9b445c6ff4ea7a3cfb98718d387a46ceae9232ce Mon Sep 17 00:00:00 2001 From: rafavalls Date: Sun, 1 Mar 2026 16:52:03 -0300 Subject: [PATCH 15/16] fix(connections): default to cards view for accordion-first list design Switch default view mode from "table" to "cards" and bump the storage key to "connections-v2" so existing users' persisted "table" preference is reset, revealing the accordion-grouped connection list. Co-Authored-By: Claude Sonnet 4.6 --- apps/mesh/src/web/routes/orgs/connections.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/mesh/src/web/routes/orgs/connections.tsx b/apps/mesh/src/web/routes/orgs/connections.tsx index e14ac10a1f..bb03c1bfad 100644 --- a/apps/mesh/src/web/routes/orgs/connections.tsx +++ b/apps/mesh/src/web/routes/orgs/connections.tsx @@ -411,9 +411,12 @@ function OrgMcpsContent() { const { stdioEnabled } = useAuthConfig(); // Consolidated list UI state (search, filters, sorting, view mode) + // Using "connections-v2" resource key to reset persisted view mode preference + // to "cards" now that the accordion design is the primary list view. const listState = useListState({ namespace: org.slug, - resource: "connections", + resource: "connections-v2", + defaultViewMode: "cards", }); const actions = useConnectionActions(); From 1e393c2a339b42d228b362f216da19829d0bdafb Mon Sep 17 00:00:00 2001 From: rafavalls Date: Sun, 1 Mar 2026 17:16:42 -0300 Subject: [PATCH 16/16] feat(connections): inline store modal, revert settings to sheet, remove store from sidebar - Revert settings panel from Dialog back to Sheet drawer with polished layout (sticky header with connection title, scrollable form body, sticky save/undo footer) - Replace 'This server is all set!' empty state with compact inline status badge - Fix ConnectionFields double-padding by removing p-5 (Sheet already has px-6 padding) - Remove Store from sidebar navigation - Add inline 'Add Connection' store modal on connections page so users can browse and pick MCP servers without leaving the connections list Co-Authored-By: Claude Sonnet 4.6 --- .../details/connection/connection-sidebar.tsx | 4 +- .../components/details/connection/index.tsx | 65 ++++++++++--------- .../details/connection/settings-tab/index.tsx | 41 ++++-------- .../web/hooks/use-project-sidebar-items.tsx | 15 +---- apps/mesh/src/web/routes/orgs/connections.tsx | 63 +++++++++--------- 5 files changed, 82 insertions(+), 106 deletions(-) diff --git a/apps/mesh/src/web/components/details/connection/connection-sidebar.tsx b/apps/mesh/src/web/components/details/connection/connection-sidebar.tsx index 9d31f1a5e6..2e350d4a6c 100644 --- a/apps/mesh/src/web/components/details/connection/connection-sidebar.tsx +++ b/apps/mesh/src/web/components/details/connection/connection-sidebar.tsx @@ -97,7 +97,7 @@ export function ConnectionFields({ if (isVirtualConnection) { return ( -
+
Type @@ -129,7 +129,7 @@ export function ConnectionFields({ } return ( -
+
- {/* Settings Dialog */} - - - - Configure connection - - Update the URL, authentication, and other technical settings. - - + {/* Settings Sheet */} + + + + {connection.title} + + Update URL, authentication, and other settings + +
-
+
)} -
-
+
+ + {hasAnyChanges && ( + - {hasAnyChanges && ( - - )} -
+ )}
- -
+ + {/* Main page */} diff --git a/apps/mesh/src/web/components/details/connection/settings-tab/index.tsx b/apps/mesh/src/web/components/details/connection/settings-tab/index.tsx index a46295c8b5..4f9380d866 100644 --- a/apps/mesh/src/web/components/details/connection/settings-tab/index.tsx +++ b/apps/mesh/src/web/components/details/connection/settings-tab/index.tsx @@ -1,4 +1,3 @@ -import { EmptyState } from "@/web/components/empty-state.tsx"; import { ErrorBoundary } from "@/web/components/error-boundary.tsx"; import { useMCPClient, @@ -165,20 +164,11 @@ function McpConfigurationContent({ if (!hasProperties) { return ( -
-