diff --git a/understand-anything-plugin/packages/dashboard/src/components/CustomNode.tsx b/understand-anything-plugin/packages/dashboard/src/components/CustomNode.tsx
index 5dffd2df..e40243eb 100644
--- a/understand-anything-plugin/packages/dashboard/src/components/CustomNode.tsx
+++ b/understand-anything-plugin/packages/dashboard/src/components/CustomNode.tsx
@@ -129,11 +129,11 @@ function CustomNodeComponent({
const name = data.label ?? "unnamed";
const truncatedName =
- name.length > 24 ? name.slice(0, 22) + "..." : name;
+ name.length > 60 ? name.slice(0, 58) + "..." : name;
return (
data.onNodeClick?.(id)}
>
{/* Left color bar */}
@@ -168,11 +168,11 @@ function CustomNodeComponent({
-
+
{truncatedName}
-
diff --git a/understand-anything-plugin/packages/dashboard/src/utils/__tests__/containers.test.ts b/understand-anything-plugin/packages/dashboard/src/utils/__tests__/containers.test.ts
index b5d6d407..92d9b061 100644
--- a/understand-anything-plugin/packages/dashboard/src/utils/__tests__/containers.test.ts
+++ b/understand-anything-plugin/packages/dashboard/src/utils/__tests__/containers.test.ts
@@ -107,10 +107,36 @@ describe("deriveContainers — community fallback", () => {
}
}
const { containers } = deriveContainers(nodes, edges);
+ // A single folder covering the whole set is now kept as ONE folder
+ // container (named after the folder) instead of being split into
+ // anonymous Louvain communities.
+ expect(containers.length).toBe(1);
+ expect(containers[0].strategy).toBe("folder");
+ expect(containers[0].name).toBe("services");
+ });
+
+ it("names community clusters by member files when no folder signal exists", () => {
+ // Flat paths (no directories) force the community fallback.
+ const nodes = Array.from({ length: 10 }, (_, i) =>
+ node(`n${i}`, `n${i}.go`),
+ );
+ const edges: GraphEdge[] = [];
+ for (const i of [0, 1, 2, 3, 4]) {
+ for (const j of [0, 1, 2, 3, 4]) {
+ if (i !== j) edges.push({ source: `n${i}`, target: `n${j}`, type: "calls" } as GraphEdge);
+ }
+ }
+ for (const i of [5, 6, 7, 8, 9]) {
+ for (const j of [5, 6, 7, 8, 9]) {
+ if (i !== j) edges.push({ source: `n${i}`, target: `n${j}`, type: "calls" } as GraphEdge);
+ }
+ }
+ const { containers } = deriveContainers(nodes, edges);
expect(containers.length).toBeGreaterThanOrEqual(2);
for (const c of containers) {
expect(c.strategy).toBe("community");
- expect(c.name).toMatch(/^Cluster [A-Z]$/);
+ // Member-derived label, e.g. "n0 · n1 · n2 +2" — not "Cluster A".
+ expect(c.name).toMatch(/·|\+/);
}
});
diff --git a/understand-anything-plugin/packages/dashboard/src/utils/containers.ts b/understand-anything-plugin/packages/dashboard/src/utils/containers.ts
index dd885219..2afe8af5 100644
--- a/understand-anything-plugin/packages/dashboard/src/utils/containers.ts
+++ b/understand-anything-plugin/packages/dashboard/src/utils/containers.ts
@@ -79,6 +79,10 @@ function shouldFallbackToCommunity(
rooted: string[],
totalNodes: number,
): boolean {
+ // A single folder covering the whole set is a meaningful unit (e.g. a
+ // Redux slice folder like src/store/meetingTypes) — keep it as ONE named
+ // container instead of splitting into anonymous Louvain communities.
+ if (groups.size === 1 && rooted.length === 0) return false;
const bucketCount = groups.size + (rooted.length > 0 ? 1 : 0);
if (bucketCount < MIN_BUCKET_COUNT) return true;
for (const ids of groups.values()) {
@@ -113,11 +117,33 @@ export function deriveContainers(
byCommunity.set(cid, arr);
}
const sorted = [...byCommunity.entries()].sort((a, b) => a[0] - b[0]);
+ // Name community clusters by their member files instead of "Cluster A/B/C".
+ const nodeById = new Map(nodes.map((n) => [n.id, n]));
+ const FILE_LEVEL = new Set([
+ "file", "config", "document", "service", "pipeline",
+ "table", "schema", "resource", "endpoint",
+ ]);
+ const labelFor = (ids: string[]): string => {
+ let members = ids
+ .map((id) => nodeById.get(id))
+ .filter((n): n is GraphNode => Boolean(n));
+ const fileMembers = members.filter((m) => FILE_LEVEL.has(m.type));
+ if (fileMembers.length > 0) members = fileMembers;
+ const bases = [
+ ...new Set(
+ members.map((m) =>
+ (m.name ?? m.id).replace(/\.(tsx?|jsx?|java|py|go|rb|cs|kt)$/i, ""),
+ ),
+ ),
+ ];
+ const head = bases.slice(0, 3).join(" · ");
+ return bases.length > 3 ? `${head} +${bases.length - 3}` : head;
+ };
containers = sorted.map(([cid, ids], i) => ({
id: `container:cluster-${cid}`,
- // A-Z for the first 26, then numeric. Avoids `String.fromCharCode(65+i)`
- // wrapping into `[`, `\`, `]` ... once the cluster count exceeds 26.
- name: i < 26 ? `Cluster ${String.fromCharCode(65 + i)}` : `Cluster ${i + 1}`,
+ name:
+ labelFor(ids) ||
+ (i < 26 ? `Cluster ${String.fromCharCode(65 + i)}` : `Cluster ${i + 1}`),
nodeIds: ids,
strategy: "community" as const,
}));
diff --git a/understand-anything-plugin/packages/dashboard/src/utils/layout.ts b/understand-anything-plugin/packages/dashboard/src/utils/layout.ts
index b35328c5..4d2c89e8 100644
--- a/understand-anything-plugin/packages/dashboard/src/utils/layout.ts
+++ b/understand-anything-plugin/packages/dashboard/src/utils/layout.ts
@@ -12,8 +12,8 @@ import type { SimulationNodeDatum, SimulationLinkDatum } from "d3-force";
import type { Node, Edge } from "@xyflow/react";
import type { ElkInput } from "./elk-layout";
-export const NODE_WIDTH = 280;
-export const NODE_HEIGHT = 120;
+export const NODE_WIDTH = 330;
+export const NODE_HEIGHT = 150;
export const LAYER_CLUSTER_WIDTH = 320;
export const LAYER_CLUSTER_HEIGHT = 180;
export const PORTAL_NODE_WIDTH = 240;