diff --git a/understand-anything-plugin/packages/dashboard/src/components/Breadcrumb.tsx b/understand-anything-plugin/packages/dashboard/src/components/Breadcrumb.tsx
index f35de41..4db7629 100644
--- a/understand-anything-plugin/packages/dashboard/src/components/Breadcrumb.tsx
+++ b/understand-anything-plugin/packages/dashboard/src/components/Breadcrumb.tsx
@@ -1,29 +1,22 @@
import { useCallback, useEffect } from "react";
import { useDashboardStore } from "../store";
-import type { ViewMode } from "../store";
export default function Breadcrumb() {
const navigationLevel = useDashboardStore((s) => s.navigationLevel);
const activeLayerId = useDashboardStore((s) => s.activeLayerId);
- const viewMode = useDashboardStore((s) => s.viewMode);
const graph = useDashboardStore((s) => s.graph);
const navigateToOverview = useDashboardStore((s) => s.navigateToOverview);
- const setViewMode = useDashboardStore((s) => s.setViewMode);
const activeLayer = graph?.layers.find((l) => l.id === activeLayerId);
- // Escape key to go back to overview (only in graph mode)
+ // Escape key to go back to overview
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
- if (
- e.key === "Escape" &&
- viewMode === "graph" &&
- navigationLevel === "layer-detail"
- ) {
+ if (e.key === "Escape" && navigationLevel === "layer-detail") {
navigateToOverview();
}
},
- [viewMode, navigationLevel, navigateToOverview],
+ [navigationLevel, navigateToOverview],
);
useEffect(() => {
@@ -31,48 +24,15 @@ export default function Breadcrumb() {
return () => document.removeEventListener("keydown", handleKeyDown);
}, [handleKeyDown]);
- const handleViewToggle = useCallback(
- (mode: ViewMode) => {
- setViewMode(mode);
- },
- [setViewMode],
- );
-
return (
- {/* View mode toggle */}
-
-
-
-
-
-
- {/* Navigation breadcrumb (only in graph mode) */}
- {viewMode === "graph" && navigationLevel === "overview" && (
+ {navigationLevel === "overview" && (
Project Overview
)}
- {viewMode === "graph" && navigationLevel === "layer-detail" && (
+ {navigationLevel === "layer-detail" && (
)}
-
- {viewMode === "flow" && (
-
- Request Flow
-
- )}
);
}
diff --git a/understand-anything-plugin/packages/dashboard/src/components/GraphView.tsx b/understand-anything-plugin/packages/dashboard/src/components/GraphView.tsx
index 5efb517..f57ec8f 100644
--- a/understand-anything-plugin/packages/dashboard/src/components/GraphView.tsx
+++ b/understand-anything-plugin/packages/dashboard/src/components/GraphView.tsx
@@ -23,7 +23,6 @@ import Breadcrumb from "./Breadcrumb";
import { useDashboardStore } from "../store";
import {
applyDagreLayout,
- applySwimLaneLayout,
NODE_WIDTH,
NODE_HEIGHT,
LAYER_CLUSTER_WIDTH,
@@ -416,169 +415,12 @@ function useLayerDetailGraph() {
]);
}
-// ── Flow (swim-lane) view: all layers as columns ──────────────────────
-
-function useFlowViewGraph() {
- const graph = useDashboardStore((s) => s.graph);
- const selectedNodeId = useDashboardStore((s) => s.selectedNodeId);
- const searchResults = useDashboardStore((s) => s.searchResults);
- const selectNode = useDashboardStore((s) => s.selectNode);
- const tourHighlightedNodeIds = useDashboardStore(
- (s) => s.tourHighlightedNodeIds,
- );
- const diffMode = useDashboardStore((s) => s.diffMode);
- const changedNodeIds = useDashboardStore((s) => s.changedNodeIds);
- const affectedNodeIds = useDashboardStore((s) => s.affectedNodeIds);
-
- const handleNodeSelect = useCallback(
- (nodeId: string) => {
- selectNode(nodeId);
- },
- [selectNode],
- );
-
- return useMemo(() => {
- if (!graph || graph.layers.length === 0)
- return { nodes: [] as Node[], edges: [] as Edge[] };
-
- const allLayerNodeIds = new Set(
- graph.layers.flatMap((l) => l.nodeIds),
- );
-
- const fileGraphNodes = graph.nodes.filter(
- (n) => n.type === "file" && allLayerNodeIds.has(n.id),
- );
-
- const fileNodeIds = new Set(fileGraphNodes.map((n) => n.id));
- const neighborNodeIds = new Set();
- if (selectedNodeId) {
- for (const edge of graph.edges) {
- if (edge.source === selectedNodeId && fileNodeIds.has(edge.target))
- neighborNodeIds.add(edge.target);
- if (edge.target === selectedNodeId && fileNodeIds.has(edge.source))
- neighborNodeIds.add(edge.source);
- }
- neighborNodeIds.add(selectedNodeId);
- }
-
- const diffNodeIds = diffMode
- ? new Set([...changedNodeIds, ...affectedNodeIds])
- : new Set();
-
- const flowNodes: CustomFlowNode[] = fileGraphNodes.map((node) => {
- const matchResult = searchResults.find((r) => r.nodeId === node.id);
- const hasSelection = !!selectedNodeId;
- return {
- id: node.id,
- type: "custom" as const,
- position: { x: 0, y: 0 },
- data: {
- label: node.name ?? node.filePath?.split("/").pop() ?? node.id,
- nodeType: node.type,
- summary: node.summary,
- complexity: node.complexity,
- isHighlighted: !!matchResult,
- searchScore: matchResult?.score,
- isSelected: selectedNodeId === node.id,
- isTourHighlighted: tourHighlightedNodeIds.includes(node.id),
- isDiffChanged: diffMode && changedNodeIds.has(node.id),
- isDiffAffected: diffMode && affectedNodeIds.has(node.id),
- isDiffFaded:
- diffMode &&
- !changedNodeIds.has(node.id) &&
- !affectedNodeIds.has(node.id),
- isNeighbor:
- hasSelection &&
- neighborNodeIds.has(node.id) &&
- selectedNodeId !== node.id,
- isSelectionFaded: hasSelection && !neighborNodeIds.has(node.id),
- onNodeClick: handleNodeSelect,
- },
- };
- });
-
- const flowEdges: Edge[] = [];
- let edgeIdx = 0;
- for (const edge of graph.edges) {
- if (!fileNodeIds.has(edge.source) || !fileNodeIds.has(edge.target))
- continue;
-
- const isSelectedEdge =
- !!selectedNodeId &&
- (edge.source === selectedNodeId || edge.target === selectedNodeId);
- const hasSelection = !!selectedNodeId;
- const sourceInDiff = diffMode && diffNodeIds.has(edge.source);
- const targetInDiff = diffMode && diffNodeIds.has(edge.target);
- const isImpacted = diffMode && (sourceInDiff || targetInDiff);
-
- let edgeStyle: React.CSSProperties;
- let edgeLabelStyle: React.CSSProperties;
- let edgeAnimated: boolean;
-
- if (isImpacted) {
- edgeStyle = {
- stroke:
- sourceInDiff && targetInDiff
- ? "rgba(224, 82, 82, 0.7)"
- : "rgba(212, 160, 48, 0.5)",
- strokeWidth: 2.5,
- };
- edgeLabelStyle = { fill: "#a39787", fontSize: 10 };
- edgeAnimated = true;
- } else if (diffMode) {
- edgeStyle = { stroke: "rgba(212,165,116,0.08)", strokeWidth: 1 };
- edgeLabelStyle = { fill: "rgba(163,151,135,0.3)", fontSize: 10 };
- edgeAnimated = false;
- } else if (isSelectedEdge) {
- edgeStyle = { stroke: "rgba(212,165,116,0.8)", strokeWidth: 2.5 };
- edgeLabelStyle = { fill: "#d4a574", fontSize: 11, fontWeight: 600 };
- edgeAnimated = true;
- } else if (hasSelection) {
- edgeStyle = { stroke: "rgba(212,165,116,0.08)", strokeWidth: 1 };
- edgeLabelStyle = { fill: "rgba(163,151,135,0.2)", fontSize: 10 };
- edgeAnimated = false;
- } else {
- edgeStyle = { stroke: "rgba(212,165,116,0.25)", strokeWidth: 1 };
- edgeLabelStyle = { fill: "#a39787", fontSize: 9 };
- edgeAnimated = false;
- }
-
- flowEdges.push({
- id: `fe-${edgeIdx++}`,
- source: edge.source,
- target: edge.target,
- label: edge.type,
- animated: edgeAnimated,
- style: edgeStyle,
- labelStyle: edgeLabelStyle,
- });
- }
-
- const result = applySwimLaneLayout(
- graph,
- flowNodes as unknown as Node[],
- flowEdges,
- );
- return { nodes: result.nodes, edges: result.edges };
- }, [
- graph,
- selectedNodeId,
- searchResults,
- tourHighlightedNodeIds,
- handleNodeSelect,
- diffMode,
- changedNodeIds,
- affectedNodeIds,
- ]);
-}
-
// ── Main inner component (must be inside ReactFlowProvider) ────────────
function GraphViewInner() {
const graph = useDashboardStore((s) => s.graph);
const navigationLevel = useDashboardStore((s) => s.navigationLevel);
const activeLayerId = useDashboardStore((s) => s.activeLayerId);
- const viewMode = useDashboardStore((s) => s.viewMode);
const selectNode = useDashboardStore((s) => s.selectNode);
const drillIntoLayer = useDashboardStore((s) => s.drillIntoLayer);
const focusNodeId = useDashboardStore((s) => s.focusNodeId);
@@ -586,14 +428,9 @@ function GraphViewInner() {
const overviewGraph = useOverviewGraph();
const detailGraph = useLayerDetailGraph();
- const flowGraph = useFlowViewGraph();
const { nodes: initialNodes, edges: initialEdges } =
- viewMode === "flow"
- ? flowGraph
- : navigationLevel === "overview"
- ? overviewGraph
- : detailGraph;
+ navigationLevel === "overview" ? overviewGraph : detailGraph;
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
@@ -608,22 +445,16 @@ function GraphViewInner() {
setEdges(initialEdges);
}, [initialEdges, setEdges]);
- // Fit view on level/layer/view-mode transitions
+ // Fit view on level/layer transitions
useEffect(() => {
const timer = setTimeout(() => {
fitView({ duration: 400, padding: 0.2 });
}, 50);
return () => clearTimeout(timer);
- }, [navigationLevel, activeLayerId, viewMode, fitView]);
+ }, [navigationLevel, activeLayerId, fitView]);
const onNodeClick = useCallback(
(_: React.MouseEvent, node: { id: string }) => {
- // In flow view, all clicks are selections (no drill-in)
- if (viewMode === "flow") {
- if (node.id.startsWith("lane:")) return;
- selectNode(node.id);
- return;
- }
if (navigationLevel === "overview") {
drillIntoLayer(node.id);
} else if (node.id.startsWith("portal:")) {
@@ -633,7 +464,7 @@ function GraphViewInner() {
selectNode(node.id);
}
},
- [viewMode, navigationLevel, drillIntoLayer, selectNode],
+ [navigationLevel, drillIntoLayer, selectNode],
);
const onPaneClick = useCallback(() => {
diff --git a/understand-anything-plugin/packages/dashboard/src/store.ts b/understand-anything-plugin/packages/dashboard/src/store.ts
index 4354e69..ae6a064 100644
--- a/understand-anything-plugin/packages/dashboard/src/store.ts
+++ b/understand-anything-plugin/packages/dashboard/src/store.ts
@@ -8,7 +8,7 @@ import type {
export type Persona = "non-technical" | "junior" | "experienced";
export type NavigationLevel = "overview" | "layer-detail";
-export type ViewMode = "graph" | "flow";
+
/** Find which layer a node belongs to. Returns layerId or null. */
function findNodeLayer(graph: KnowledgeGraph, nodeId: string): string | null {
@@ -33,9 +33,6 @@ interface DashboardStore {
navigationLevel: NavigationLevel;
activeLayerId: string | null;
- // View mode: graph (default free-form) or flow (swim-lane)
- viewMode: ViewMode;
-
codeViewerOpen: boolean;
codeViewerNodeId: string | null;
@@ -65,7 +62,6 @@ interface DashboardStore {
goBackNode: () => void;
drillIntoLayer: (layerId: string) => void;
navigateToOverview: () => void;
- setViewMode: (mode: ViewMode) => void;
setFocusNode: (nodeId: string | null) => void;
setSearchQuery: (query: string) => void;
toggleLayers: () => void;
@@ -117,8 +113,6 @@ export const useDashboardStore = create()((set, get) => ({
navigationLevel: "overview",
activeLayerId: null,
- viewMode: "graph",
-
codeViewerOpen: false,
codeViewerNodeId: null,
@@ -243,16 +237,6 @@ export const useDashboardStore = create()((set, get) => ({
codeViewerNodeId: null,
}),
- setViewMode: (mode) =>
- set({
- viewMode: mode,
- // When switching to flow view, go to overview level (flow shows all layers)
- navigationLevel: mode === "flow" ? "overview" : get().navigationLevel,
- activeLayerId: mode === "flow" ? null : get().activeLayerId,
- selectedNodeId: null,
- focusNodeId: null,
- }),
-
setFocusNode: (nodeId) => set({ focusNodeId: nodeId, selectedNodeId: nodeId }),
setSearchMode: (mode) => set({ searchMode: mode }),
setSearchQuery: (query) => {
diff --git a/understand-anything-plugin/packages/dashboard/src/utils/layout.ts b/understand-anything-plugin/packages/dashboard/src/utils/layout.ts
index 548ebfc..b084db8 100644
--- a/understand-anything-plugin/packages/dashboard/src/utils/layout.ts
+++ b/understand-anything-plugin/packages/dashboard/src/utils/layout.ts
@@ -1,6 +1,5 @@
import dagre from "@dagrejs/dagre";
import type { Node, Edge } from "@xyflow/react";
-import type { KnowledgeGraph } from "@understand-anything/core/types";
import type { LayoutMessage, LayoutResult } from "./layout.worker";
export const NODE_WIDTH = 280;
@@ -10,12 +9,6 @@ export const LAYER_CLUSTER_HEIGHT = 180;
export const PORTAL_NODE_WIDTH = 240;
export const PORTAL_NODE_HEIGHT = 80;
-// Swim-lane constants
-export const LANE_WIDTH = 320;
-export const LANE_GAP = 40;
-export const LANE_PADDING = 30;
-export const LANE_HEADER_HEIGHT = 40;
-
/**
* Synchronous dagre layout — used for small graphs.
*/
@@ -144,146 +137,3 @@ export function applyDagreLayoutAsync(
});
}
-// ── Swim-lane layout ───────────────────────────────────────────────────
-
-/**
- * Preferred order of layers for the swim-lane flow view.
- * Reflects a typical request lifecycle: entry → middleware → logic → data → external.
- */
-const LAYER_FLOW_ORDER = [
- "API Layer",
- "UI Layer",
- "Middleware Layer",
- "Service Layer",
- "External Services",
- "Data Layer",
- "Background Tasks",
- "Utility Layer",
- "Configuration Layer",
- "Test Layer",
-];
-
-function getLayerSortIndex(layerName: string): number {
- const idx = LAYER_FLOW_ORDER.indexOf(layerName);
- return idx >= 0 ? idx : LAYER_FLOW_ORDER.length;
-}
-
-export interface SwimLaneResult {
- nodes: Node[];
- edges: Edge[];
- lanes: Array<{ layerId: string; layerName: string; columnIndex: number }>;
-}
-
-/**
- * Swim-lane layout: each layer becomes a vertical column (lane), ordered
- * by the request lifecycle. Within each lane, dagre handles vertical
- * positioning. File nodes are children of their lane group node.
- */
-export function applySwimLaneLayout(
- graph: KnowledgeGraph,
- fileNodes: Node[],
- allEdges: Edge[],
-): SwimLaneResult {
- const sortedLayers = [...graph.layers].sort(
- (a, b) => getLayerSortIndex(a.name) - getLayerSortIndex(b.name),
- );
-
- const nodeToLayerId = new Map();
- for (const layer of sortedLayers) {
- for (const nid of layer.nodeIds) {
- nodeToLayerId.set(nid, layer.id);
- }
- }
-
- const nodesByLayer = new Map();
- for (const layer of sortedLayers) {
- nodesByLayer.set(layer.id, []);
- }
- for (const node of fileNodes) {
- const lid = nodeToLayerId.get(node.id);
- if (lid && nodesByLayer.has(lid)) {
- nodesByLayer.get(lid)!.push(node);
- }
- }
-
- const laneHeights = new Map();
-
- for (const [layerId, nodes] of nodesByLayer) {
- if (nodes.length === 0) {
- laneHeights.set(layerId, LANE_HEADER_HEIGHT + LANE_PADDING * 2);
- continue;
- }
-
- const g = new dagre.graphlib.Graph();
- g.setDefaultEdgeLabel(() => ({}));
- g.setGraph({ rankdir: "TB", nodesep: 20, ranksep: 40, marginx: 0, marginy: 0 });
-
- const laneNodeIds = new Set(nodes.map((n) => n.id));
- for (const node of nodes) {
- g.setNode(node.id, { width: NODE_WIDTH, height: NODE_HEIGHT });
- }
-
- for (const edge of allEdges) {
- if (laneNodeIds.has(edge.source) && laneNodeIds.has(edge.target)) {
- g.setEdge(edge.source, edge.target);
- }
- }
-
- dagre.layout(g);
-
- let maxY = 0;
- for (const node of nodes) {
- const pos = g.node(node.id);
- if (pos) {
- (node as Node & { _laneY: number })._laneY = pos.y - NODE_HEIGHT / 2;
- maxY = Math.max(maxY, pos.y + NODE_HEIGHT / 2);
- }
- }
-
- laneHeights.set(layerId, LANE_HEADER_HEIGHT + maxY + LANE_PADDING * 2);
- }
-
- const maxLaneHeight = Math.max(200, ...Array.from(laneHeights.values()));
-
- const resultNodes: Node[] = [];
- const lanes: SwimLaneResult["lanes"] = [];
-
- sortedLayers.forEach((layer, colIdx) => {
- const laneX = colIdx * (LANE_WIDTH + LANE_GAP);
- const laneId = `lane:${layer.id}`;
-
- lanes.push({ layerId: layer.id, layerName: layer.name, columnIndex: colIdx });
-
- resultNodes.push({
- id: laneId,
- type: "group",
- position: { x: laneX, y: 0 },
- data: { label: layer.name },
- style: {
- width: LANE_WIDTH,
- height: maxLaneHeight,
- backgroundColor: "rgba(212,165,116,0.03)",
- borderRadius: 12,
- border: "1px solid rgba(212,165,116,0.1)",
- padding: 0,
- },
- });
-
- const nodes = nodesByLayer.get(layer.id) ?? [];
- for (const node of nodes) {
- const laneY = (node as Node & { _laneY?: number })._laneY ?? 0;
- resultNodes.push({
- ...node,
- parentId: laneId,
- extent: "parent" as const,
- position: {
- x: (LANE_WIDTH - NODE_WIDTH) / 2,
- y: LANE_HEADER_HEIGHT + LANE_PADDING + laneY,
- },
- });
- delete (node as Node & { _laneY?: number })._laneY;
- }
- });
-
- return { nodes: resultNodes, edges: allEdges, lanes };
-}