From 13327b794557ded2661a9d88c85e068f6ef27ca2 Mon Sep 17 00:00:00 2001 From: arkaigrowth Date: Mon, 27 Apr 2026 17:02:34 -0500 Subject: [PATCH 1/4] feat(dashboard): add source code viewer --- pnpm-lock.yaml | 29 +- .../packages/dashboard/package.json | 1 + .../packages/dashboard/src/App.tsx | 87 ++++-- .../dashboard/src/components/CodeViewer.tsx | 282 ++++++++++++++---- .../dashboard/src/components/FileExplorer.tsx | 204 +++++++++++++ .../dashboard/src/components/NodeInfo.tsx | 30 +- .../packages/dashboard/src/store.ts | 16 +- .../packages/dashboard/vite.config.ts | 201 +++++++++++-- understand-anything-plugin/pnpm-lock.yaml | 25 ++ 9 files changed, 749 insertions(+), 126 deletions(-) create mode 100644 understand-anything-plugin/packages/dashboard/src/components/FileExplorer.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d5c2b8..b026d0b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -118,6 +118,9 @@ importers: hast-util-to-jsx-runtime: specifier: ^2.3.6 version: 2.3.6 + prism-react-renderer: + specifier: ^2.4.1 + version: 2.4.1(react@19.2.4) react: specifier: ^19.0.0 version: 19.2.4 @@ -1089,6 +1092,9 @@ packages: '@types/node@25.5.0': resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} + '@types/prismjs@1.26.6': + resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==} + '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: @@ -2150,6 +2156,11 @@ packages: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} + prism-react-renderer@2.4.1: + resolution: {integrity: sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==} + peerDependencies: + react: '>=16.0.0' + prismjs@1.30.0: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} @@ -3590,6 +3601,8 @@ snapshots: dependencies: undici-types: 7.18.2 + '@types/prismjs@1.26.6': {} + '@types/react-dom@19.2.3(@types/react@19.2.14)': dependencies: '@types/react': 19.2.14 @@ -3643,14 +3656,6 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) - '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 3.2.4 @@ -4994,6 +4999,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + prism-react-renderer@2.4.1(react@19.2.4): + dependencies: + '@types/prismjs': 1.26.6 + clsx: 2.1.1 + react: 19.2.4 + prismjs@1.30.0: {} property-information@7.1.0: {} @@ -5605,7 +5616,7 @@ snapshots: dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)) + '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 diff --git a/understand-anything-plugin/packages/dashboard/package.json b/understand-anything-plugin/packages/dashboard/package.json index 2702091..8bb9196 100644 --- a/understand-anything-plugin/packages/dashboard/package.json +++ b/understand-anything-plugin/packages/dashboard/package.json @@ -16,6 +16,7 @@ "d3-force": "^3.0.0", "devlop": "^1.1.0", "hast-util-to-jsx-runtime": "^2.3.6", + "prism-react-renderer": "^2.4.1", "react": "^19.0.0", "react-dom": "^19.0.0", "react-markdown": "^10.1.0", diff --git a/understand-anything-plugin/packages/dashboard/src/App.tsx b/understand-anything-plugin/packages/dashboard/src/App.tsx index 2c16f53..0e21e17 100644 --- a/understand-anything-plugin/packages/dashboard/src/App.tsx +++ b/understand-anything-plugin/packages/dashboard/src/App.tsx @@ -13,6 +13,7 @@ import FilterPanel from "./components/FilterPanel"; import ExportMenu from "./components/ExportMenu"; import PersonaSelector from "./components/PersonaSelector"; import ProjectOverview from "./components/ProjectOverview"; +import FileExplorer from "./components/FileExplorer"; import WarningBanner from "./components/WarningBanner"; import TokenGate from "./components/TokenGate"; import { useKeyboardShortcuts } from "./hooks/useKeyboardShortcuts"; @@ -31,6 +32,7 @@ const KeyboardShortcutsHelp = lazy( const DEMO_MODE = import.meta.env.VITE_DEMO_MODE === "true"; const SESSION_TOKEN_KEY = "understand-anything-token"; +type SidebarTab = "info" | "files"; /** Resolve data file URL — in demo mode, use env var URLs; otherwise use local paths with token. */ function dataUrl(fileName: string, token: string | null): string { @@ -97,7 +99,9 @@ function Dashboard({ accessToken }: { accessToken: string }) { const tourActive = useDashboardStore((s) => s.tourActive); const persona = useDashboardStore((s) => s.persona); const codeViewerOpen = useDashboardStore((s) => s.codeViewerOpen); - const closeCodeViewer = useDashboardStore((s) => s.closeCodeViewer); + const codeViewerExpanded = useDashboardStore((s) => s.codeViewerExpanded); + const expandCodeViewer = useDashboardStore((s) => s.expandCodeViewer); + const collapseCodeViewer = useDashboardStore((s) => s.collapseCodeViewer); const setDiffOverlay = useDashboardStore((s) => s.setDiffOverlay); const pathFinderOpen = useDashboardStore((s) => s.pathFinderOpen); const togglePathFinder = useDashboardStore((s) => s.togglePathFinder); @@ -107,6 +111,7 @@ function Dashboard({ accessToken }: { accessToken: string }) { const [graphIssues, setGraphIssues] = useState([]); const [showKeyboardHelp, setShowKeyboardHelp] = useState(false); const [metaTheme, setMetaTheme] = useState(null); + const [sidebarTab, setSidebarTab] = useState("info"); const viewMode = useDashboardStore((s) => s.viewMode); const setViewMode = useDashboardStore((s) => s.setViewMode); const isKnowledgeGraph = useDashboardStore((s) => s.isKnowledgeGraph); @@ -122,6 +127,10 @@ function Dashboard({ accessToken }: { accessToken: string }) { .catch(() => {}); }, []); + useEffect(() => { + if (selectedNodeId) setSidebarTab("info"); + }, [selectedNodeId]); + // Define keyboard shortcuts const shortcuts = useMemo( () => [ @@ -146,6 +155,8 @@ function Dashboard({ accessToken }: { accessToken: string }) { state.toggleFilterPanel(); } else if (state.exportMenuOpen) { state.toggleExportMenu(); + } else if (state.codeViewerExpanded) { + state.collapseCodeViewer(); } else if (state.codeViewerOpen) { state.closeCodeViewer(); } else if (state.selectedNodeId) { @@ -322,7 +333,7 @@ function Dashboard({ accessToken }: { accessToken: string }) { // NodeInfo always takes priority when a node is selected. // Learn mode adds LearnPanel below it; otherwise ProjectOverview shows when idle. const isLearnMode = tourActive || persona === "junior"; - const sidebarContent = ( + const infoSidebarContent = ( <> {selectedNodeId && } {isLearnMode && ( @@ -334,6 +345,34 @@ function Dashboard({ accessToken }: { accessToken: string }) { ); + const sidebarContent = codeViewerOpen ? ( + + + + ) : ( +
+
+ {(["info", "files"] as const).map((tab) => ( + + ))} +
+
+ {sidebarTab === "files" ? : infoSidebarContent} +
+
+ ); + return (
@@ -499,32 +538,32 @@ function Dashboard({ accessToken }: { accessToken: string }) {
{/* Right sidebar */} -