diff --git a/src/app/(dashboard)/dashboard/HomePageClient.tsx b/src/app/(dashboard)/dashboard/HomePageClient.tsx
index 3b1e2a91da..b5b5883dfd 100644
--- a/src/app/(dashboard)/dashboard/HomePageClient.tsx
+++ b/src/app/(dashboard)/dashboard/HomePageClient.tsx
@@ -498,6 +498,12 @@ export default function HomePageClient({ machineId }: HomePageClientProps) {
const canonicalProviderId = normalizeProviderId(rawProviderId);
if (!canonicalProviderId || byProvider.has(canonicalProviderId)) return;
+ // Exclude providers with no active connections (or where all connections are deactivated)
+ const hasActiveConn = providerConnections.some(
+ (c) => normalizeProviderId(c.provider) === canonicalProviderId && c.isActive !== false
+ );
+ if (!hasActiveConn) return;
+
const resolvedName =
getProviderDisplayLabel(rawProviderId, providerNodes) ||
name ||
@@ -515,10 +521,11 @@ export default function HomePageClient({ machineId }: HomePageClientProps) {
providerStats
.filter((provider) => provider.total > 0)
.forEach((provider) => addProvider(provider.id, provider.provider.name));
+ providerConnections.forEach((conn) => addProvider(conn.provider));
Object.keys(providerMetrics).forEach((provider) => addProvider(provider));
return Array.from(byProvider.values());
- }, [providerStats, providerMetrics, providerNodes]);
+ }, [providerStats, providerMetrics, providerNodes, providerConnections]);
const { lastProvider, errorProvider } = providerTopology;
diff --git a/src/app/(dashboard)/home/ProviderTopology.tsx b/src/app/(dashboard)/home/ProviderTopology.tsx
index 2324d93d85..413a7a7a7c 100644
--- a/src/app/(dashboard)/home/ProviderTopology.tsx
+++ b/src/app/(dashboard)/home/ProviderTopology.tsx
@@ -1,6 +1,7 @@
"use client";
-import { useMemo } from "react";
+import { useMemo, useCallback } from "react";
+import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { Handle, Position, type Node, type Edge, type NodeTypes } from "@xyflow/react";
import { AI_PROVIDERS } from "@/shared/constants/providers";
@@ -58,7 +59,7 @@ function ProviderNode({ data }: { data: ProviderNodeData }) {
return (
{
+ if (node.type !== "provider") return;
+ const providerId =
+ (node.data as ProviderNodeData | undefined)?.providerId ||
+ node.id.replace(/^provider-/, "");
+ if (providerId) {
+ router.push(`/dashboard/providers/${providerId}`);
+ }
+ },
+ [router]
+ );
+
const containerClass =
"h-[300px] w-full min-w-0 rounded-xl border border-border bg-bg-subtle/20 overflow-hidden sm:h-[420px]";
@@ -351,6 +365,7 @@ export default function ProviderTopology({
nodeTypes={nodeTypes}
fitKey={providersKey}
className={containerClass}
+ onNodeClick={handleNodeClick}
/>
);
}
diff --git a/tests/unit/topology-filtering-and-click.test.ts b/tests/unit/topology-filtering-and-click.test.ts
new file mode 100644
index 0000000000..aa16c27253
--- /dev/null
+++ b/tests/unit/topology-filtering-and-click.test.ts
@@ -0,0 +1,48 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import { readFileSync } from "node:fs";
+import { fileURLToPath } from "node:url";
+
+const homePageClientSrc = readFileSync(
+ fileURLToPath(new URL("../../src/app/(dashboard)/dashboard/HomePageClient.tsx", import.meta.url)),
+ "utf8"
+);
+
+const providerTopologySrc = readFileSync(
+ fileURLToPath(new URL("../../src/app/(dashboard)/home/ProviderTopology.tsx", import.meta.url)),
+ "utf8"
+);
+
+test("HomePageClient filters out providers where all connections are deactivated (isActive === false)", () => {
+ assert.match(
+ homePageClientSrc,
+ /hasActiveConn\s*=\s*providerConnections\.some\(\s*\(c\)\s*=>\s*normalizeProviderId\(c\.provider\)\s*===\s*canonicalProviderId\s*&&\s*c\.isActive\s*!==\s*false\s*\)/,
+ "HomePageClient must exclude providers whose connections are all inactive/disabled"
+ );
+
+ assert.match(
+ homePageClientSrc,
+ /\}, \[providerStats, providerMetrics, providerNodes, providerConnections\]\);/,
+ "topologyProviders must depend on providerConnections to reflect switch toggle state changes"
+ );
+});
+
+test("ProviderTopology configures click-to-navigate on provider nodes", () => {
+ assert.match(
+ providerTopologySrc,
+ /router\.push\(`\/dashboard\/providers\/\${providerId}`\)/,
+ "ProviderTopology must navigate to the clicked provider page /dashboard/providers/${providerId}"
+ );
+
+ assert.match(
+ providerTopologySrc,
+ /onNodeClick=\{handleNodeClick\}/,
+ "ProviderTopology must pass handleNodeClick to FlowCanvas"
+ );
+
+ assert.match(
+ providerTopologySrc,
+ /cursor-pointer hover:scale-105/,
+ "ProviderNode must render with a pointer cursor and visual hover effect"
+ );
+});