diff --git a/understand-anything-plugin/packages/dashboard/src/App.tsx b/understand-anything-plugin/packages/dashboard/src/App.tsx index 8abc0e8..4aed9bf 100644 --- a/understand-anything-plugin/packages/dashboard/src/App.tsx +++ b/understand-anything-plugin/packages/dashboard/src/App.tsx @@ -109,6 +109,10 @@ function Dashboard({ accessToken }: { accessToken: string }) { const togglePathFinder = useDashboardStore((s) => s.togglePathFinder); const nodeTypeFilters = useDashboardStore((s) => s.nodeTypeFilters); const toggleNodeTypeFilter = useDashboardStore((s) => s.toggleNodeTypeFilter); + const detailLevel = useDashboardStore((s) => s.detailLevel); + const setDetailLevel = useDashboardStore((s) => s.setDetailLevel); + const showFunctionsInClassView = useDashboardStore((s) => s.showFunctionsInClassView); + const toggleShowFunctionsInClassView = useDashboardStore((s) => s.toggleShowFunctionsInClassView); const [loadError, setLoadError] = useState(null); const [graphIssues, setGraphIssues] = useState([]); const [showKeyboardHelp, setShowKeyboardHelp] = useState(false); @@ -443,6 +447,52 @@ function Dashboard({ accessToken }: { accessToken: string }) {
+ {/* Detail level: file view (architecture) / class view (code structure) */} + {!isKnowledgeGraph && viewMode !== "domain" && ( + <> +
+
+ + +
+ {detailLevel === "class" && ( + + )} + + )}
{(isKnowledgeGraph ? [ { key: "knowledge" as const, label: "All", color: "var(--color-node-article)" }, diff --git a/understand-anything-plugin/packages/dashboard/src/components/GraphView.tsx b/understand-anything-plugin/packages/dashboard/src/components/GraphView.tsx index 4ba8d1b..2a5037a 100644 --- a/understand-anything-plugin/packages/dashboard/src/components/GraphView.tsx +++ b/understand-anything-plugin/packages/dashboard/src/components/GraphView.tsx @@ -366,6 +366,7 @@ function useLayerDetailTopology(): LayerDetailTopology & { layoutStatus: "computing" | "ready"; } { const graph = useDashboardStore((s) => s.graph); + const nodesById = useDashboardStore((s) => s.nodesById); const activeLayerId = useDashboardStore((s) => s.activeLayerId); const selectNode = useDashboardStore((s) => s.selectNode); const persona = useDashboardStore((s) => s.persona); @@ -375,6 +376,8 @@ function useLayerDetailTopology(): LayerDetailTopology & { const focusNodeId = useDashboardStore((s) => s.focusNodeId); const nodeTypeFilters = useDashboardStore((s) => s.nodeTypeFilters); const drillIntoLayer = useDashboardStore((s) => s.drillIntoLayer); + const detailLevel = useDashboardStore((s) => s.detailLevel); + const showFunctionsInClassView = useDashboardStore((s) => s.showFunctionsInClassView); const handleNodeSelect = useCallback( (nodeId: string) => { @@ -404,10 +407,20 @@ function useLayerDetailTopology(): LayerDetailTopology & { // Expand layer membership to include sub-file nodes (function/class) // whose parent file is in this layer. Joined via "contains" edges. + // File view: file nodes only (architecture-level dependencies). + // Class view: file nodes + class nodes, functions only when toggle is on. const expandedLayerNodeIds = new Set(layerNodeIds); - for (const edge of graph.edges) { - if (edge.type === "contains" && layerNodeIds.has(edge.source)) { - expandedLayerNodeIds.add(edge.target); + if (detailLevel !== "file") { + for (const edge of graph.edges) { + if (edge.type === "contains" && layerNodeIds.has(edge.source)) { + const child = nodesById.get(edge.target); + if (!child) continue; + if (child.type === "class") { + expandedLayerNodeIds.add(edge.target); + } else if (child.type === "function" && showFunctionsInClassView) { + expandedLayerNodeIds.add(edge.target); + } + } } } @@ -626,6 +639,7 @@ function useLayerDetailTopology(): LayerDetailTopology & { }; }, [ graph, + nodesById, activeLayerId, persona, diffMode, @@ -634,6 +648,8 @@ function useLayerDetailTopology(): LayerDetailTopology & { focusNodeId, nodeTypeFilters, drillIntoLayer, + detailLevel, + showFunctionsInClassView, handleNodeSelect, handleContainerToggle, ]); diff --git a/understand-anything-plugin/packages/dashboard/src/store.ts b/understand-anything-plugin/packages/dashboard/src/store.ts index ea398a1..b3b2a96 100644 --- a/understand-anything-plugin/packages/dashboard/src/store.ts +++ b/understand-anything-plugin/packages/dashboard/src/store.ts @@ -15,6 +15,7 @@ export type NodeType = "file" | "function" | "class" | "module" | "concept" | "c export type Complexity = "simple" | "moderate" | "complex"; export type EdgeCategory = "structural" | "behavioral" | "data-flow" | "dependencies" | "semantic" | "infrastructure" | "domain" | "knowledge"; export type ViewMode = "structural" | "domain" | "knowledge"; +export type DetailLevel = "file" | "class"; export interface FilterState { nodeTypes: Set; @@ -146,6 +147,13 @@ interface DashboardStore { nodeTypeFilters: Record; toggleNodeTypeFilter: (category: NodeCategory) => void; + // Detail level: "file" shows only file nodes (architecture view), + // "class" shows files + class nodes (code structure view) with optional function expansion. + detailLevel: DetailLevel; + setDetailLevel: (level: DetailLevel) => void; + showFunctionsInClassView: boolean; + toggleShowFunctionsInClassView: () => void; + setGraph: (graph: KnowledgeGraph) => void; selectNode: (nodeId: string | null) => void; navigateToNode: (nodeId: string) => void; @@ -331,6 +339,29 @@ export const useDashboardStore = create()((set, get) => ({ pendingFocusContainer: null, })), + detailLevel: "file", + setDetailLevel: (level) => + set({ + detailLevel: level, + // Detail level changes which nodes are visible; cached positions stale. + // Reset fn toggle so it doesn't resurrect when re-entering class view. + showFunctionsInClassView: false, + containerLayoutCache: new Map(), + containerSizeMemory: new Map(), + expandedContainers: new Set(), + pendingFocusContainer: null, + }), + + showFunctionsInClassView: false, + toggleShowFunctionsInClassView: () => + set((state) => ({ + showFunctionsInClassView: !state.showFunctionsInClassView, + containerLayoutCache: new Map(), + containerSizeMemory: new Map(), + expandedContainers: new Set(), + pendingFocusContainer: null, + })), + setGraph: (graph) => { const searchEngine = new SearchEngine(graph.nodes); const query = get().searchQuery;