diff --git a/AGENTS.md b/AGENTS.md
index 17e862e..19cfe6a 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -47,12 +47,14 @@ Consult these guides before working on related tasks:
- Agent architecture is derived from `bots/**` and `botcomponents/**`; component
ownership must be resolved per agent rather than applying every solution
component to every bot.
-- Agent previews use a deterministic hub-and-spoke layout: primary agents are
- centered, owned agents/tools/skills share radial rings, and straight edges
- attach to the nearest node side. Node color is presentation-only and does not
- alter the imported topology.
+- Detail pages render the imported workflow and agent topology. Gallery cards
+ instead show a fixed asset inventory for workflows, agents, tools, MCP
+ servers, and skills so dense graphs are not compressed into unreadable
+ thumbnails.
- The browser receives compact allowlisted graph data. Raw schemas, icons,
environment connection identifiers, and source blobs stay out of generated
content.
- Download ZIPs are deterministic rebuilds of the `solution/` contents at archive
- root; gallery sidecars are excluded.
+ root; gallery sidecars are excluded. Downloads stay locked until the visitor
+ acknowledges that community solutions should be reviewed and imported only
+ from a trusted source.
diff --git a/src/components/AssetSummaryPreview.astro b/src/components/AssetSummaryPreview.astro
new file mode 100644
index 0000000..345efff
--- /dev/null
+++ b/src/components/AssetSummaryPreview.astro
@@ -0,0 +1,94 @@
+---
+import type { GraphData } from "../lib/schema";
+import { isMcpNode, MCP_ICON_PATHS } from "../lib/graphVisuals";
+
+interface Props {
+ agentCount: number;
+ workflowCount: number;
+ agentGraph: GraphData;
+ label?: string;
+}
+
+const {
+ agentCount,
+ workflowCount,
+ agentGraph,
+ label = "Solution assets",
+} = Astro.props;
+
+const capabilityNodes = agentGraph.nodes.filter((node) => node.type === "tool" || node.type === "connector");
+const mcpCount = capabilityNodes.filter(isMcpNode).length;
+const toolCount = capabilityNodes.length - mcpCount;
+const skillCount = agentGraph.nodes.filter((node) => node.type === "skill").length;
+const assets = [
+ { kind: "workflow", label: "Workflows", count: workflowCount, color: "var(--graph-action)" },
+ { kind: "agent", label: "Agents", count: agentCount, color: "var(--graph-ai)" },
+ { kind: "tool", label: "Tools", count: toolCount, color: "var(--graph-tool)" },
+ { kind: "mcp", label: "MCP", count: mcpCount, color: "var(--graph-mcp)" },
+ { kind: "skill", label: "Skills", count: skillCount, color: "var(--graph-skill)" },
+] as const;
+const summary = assets.map((asset) => `${asset.count} ${asset.label.toLowerCase()}`).join(", ");
+---
+
+
+ {
+ assets.map((asset) => (
+
+
+ {asset.kind === "workflow" && (
+
+ )}
+ {asset.kind === "agent" && (
+
+ )}
+ {asset.kind === "tool" && (
+
+ )}
+ {asset.kind === "mcp" && (
+
+ )}
+ {asset.kind === "skill" && (
+
+ )}
+ {asset.count > 0 && (
+
+ {asset.count}
+
+ )}
+
+
+ {asset.label}
+
+
+ ))
+ }
+
diff --git a/src/components/SolutionCard.astro b/src/components/SolutionCard.astro
index 243be32..ed6475a 100644
--- a/src/components/SolutionCard.astro
+++ b/src/components/SolutionCard.astro
@@ -2,7 +2,7 @@
import type { CollectionEntry } from "astro:content";
import { authorKey, solutionMakeup } from "../lib/schema";
import { withBase } from "../lib/url";
-import TopologyPreview from "./TopologyPreview.astro";
+import AssetSummaryPreview from "./AssetSummaryPreview.astro";
interface Props {
solution: CollectionEntry<"solutions">;
@@ -11,7 +11,6 @@ interface Props {
const { solution, compact = false } = Astro.props;
const data = solution.data;
-const graph = data.workflows[0]?.graph ?? data.agentGraph;
const makeup = solutionMakeup(data.agentCount, data.workflowCount);
const isDemo = data.tags.includes("demo");
const buildingBlocks = [
@@ -27,11 +26,11 @@ const buildingBlocks = [
diff --git a/src/components/TopologyPreview.astro b/src/components/TopologyPreview.astro
deleted file mode 100644
index d11c156..0000000
--- a/src/components/TopologyPreview.astro
+++ /dev/null
@@ -1,403 +0,0 @@
----
-import type { GraphData } from "../lib/schema";
-import { graphVisualColor, graphVisualKind, isMcpNode, MCP_ICON_PATHS } from "../lib/graphVisuals";
-
-interface Props {
- graph?: GraphData;
- label?: string;
- compact?: boolean;
- variant?: "workflow" | "agent";
-}
-
-const { graph, label = "Solution topology", compact = false, variant = "workflow" } = Astro.props;
-const nodes = graph?.nodes.filter((node) => node.type !== "canvasNote").slice(0, compact ? 9 : 18) ?? [];
-const nodeIds = new Set(nodes.map((node) => node.id));
-const edges = graph?.edges.filter((edge) => nodeIds.has(edge.source) && nodeIds.has(edge.target)) ?? [];
-const nodeById = new Map(nodes.map((node) => [node.id, node]));
-const xs = nodes.map((node) => node.position.x);
-const ys = nodes.map((node) => node.position.y);
-const minX = xs.length ? Math.min(...xs) : 0;
-const maxX = xs.length ? Math.max(...xs) : 1;
-const minY = ys.length ? Math.min(...ys) : 0;
-const maxY = ys.length ? Math.max(...ys) : 1;
-const width = 540;
-const height = compact ? 116 : 210;
-const pad = compact ? 18 : 28;
-const gradientId = `topology-${label.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${compact ? "compact" : "full"}`;
-const arrowId = `${gradientId}-arrow`;
-const singleAgent = variant === "agent" && nodes.length === 1 && nodes[0].type === "agent" ? nodes[0] : undefined;
-const sparklePath = (x: number, y: number, size: number) =>
- `M ${x} ${y - size} L ${x + size * 0.28} ${y - size * 0.28} L ${x + size} ${y} L ${x + size * 0.28} ${y + size * 0.28} L ${x} ${y + size} L ${x - size * 0.28} ${y + size * 0.28} L ${x - size} ${y} L ${x - size * 0.28} ${y - size * 0.28} Z`;
-const scaleX = (x: number) =>
- maxX === minX ? width / 2 : pad + ((x - minX) / (maxX - minX)) * (width - pad * 2);
-const scaleY = (y: number) =>
- maxY === minY ? height / 2 : pad + ((y - minY) / (maxY - minY)) * (height - pad * 2);
-const fallback = [
- { x: 35, y: height / 2 },
- { x: 145, y: height / 2 },
- { x: 270, y: height / 3 },
- { x: 270, y: (height * 2) / 3 },
- { x: 405, y: height / 2 },
- { x: 505, y: height / 2 },
-];
----
-
-
diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro
index 7bc5133..f2cd779 100644
--- a/src/layouts/Layout.astro
+++ b/src/layouts/Layout.astro
@@ -8,13 +8,13 @@ interface Props {
}
const {
- title = "CAT Agents & Workflows Gallery",
+ title = "Copilot Studio Gallery",
description = "Discover community-built Copilot Studio solutions through their agents and workflow canvases.",
} = Astro.props;
const fullTitle =
- title === "CAT Agents & Workflows Gallery"
+ title === "Copilot Studio Gallery"
? title
- : `${title} · CAT Agents & Workflows Gallery`;
+ : `${title} · Copilot Studio Gallery`;
---
@@ -36,6 +36,7 @@ const fullTitle =
+
diff --git a/src/pages/solutions/[slug].astro b/src/pages/solutions/[slug].astro
index 738028a..03518d3 100644
--- a/src/pages/solutions/[slug].astro
+++ b/src/pages/solutions/[slug].astro
@@ -162,9 +162,38 @@ const heroGraph = data.workflows[0]?.graph ?? data.agentGraph;
Installable package
Download this solution
Import the rebuilt ZIP through Power Platform after reviewing the agents and workflows.
-
- Download solution ZIP
+
+
+
+
+
Import only what you trust
+
+
+
+
+
+ Download solution ZIP
+
+ This solution is provided as-is and is not covered by Microsoft Support.
+ Support details
+
- Publisher
- {data.publisher ?? "Not specified"}
@@ -197,4 +226,27 @@ const heroGraph = data.workflows[0]?.graph ?? data.agentGraph;
}
tabs.forEach((tab) => tab.addEventListener("click", () => activate(tab.dataset.tab ?? "overview")));
activate(location.hash.slice(1) || "overview", false);
+
+ const trustAcknowledgement = document.getElementById("trust-ack") as HTMLInputElement | null;
+ const download = document.querySelector("[data-download-gated]");
+
+ function syncDownload() {
+ if (!trustAcknowledgement || !download) return;
+ const enabled = trustAcknowledgement.checked;
+ download.classList.toggle("cursor-not-allowed", !enabled);
+ download.classList.toggle("opacity-50", !enabled);
+ download.classList.toggle("hover:bg-accent-hover", enabled);
+ download.setAttribute("aria-disabled", String(!enabled));
+ download.tabIndex = enabled ? 0 : -1;
+ if (enabled) download.href = download.dataset.downloadUrl ?? "";
+ else download.removeAttribute("href");
+ }
+
+ download?.addEventListener("click", (event) => {
+ if (trustAcknowledgement?.checked) return;
+ event.preventDefault();
+ trustAcknowledgement?.focus();
+ });
+ trustAcknowledgement?.addEventListener("change", syncDownload);
+ syncDownload();