From 2248cb85c327abd834163b89341b671da65a37f7 Mon Sep 17 00:00:00 2001 From: Humza Khan Date: Fri, 13 Mar 2026 14:01:07 -0400 Subject: [PATCH 1/5] feat: add React, Framer Motion, React Flow, and resizable panels Add new dependencies for the React-based frontend: - react, react-dom, @vitejs/plugin-react - @xyflow/react for graph visualization - framer-motion for animations - react-resizable-panels for VS Code-like layout - react-shiki for code highlighting Update vite.config.ts with React plugin and API proxy. Update tsconfig.json with JSX support. --- report/index.html | 2 +- report/package-lock.json | 1584 +++++++++++++++++++++++++++++++++++++- report/package.json | 11 +- report/tsconfig.json | 1 + report/vite.config.ts | 10 +- 5 files changed, 1570 insertions(+), 38 deletions(-) diff --git a/report/index.html b/report/index.html index 730058e..2404b8b 100644 --- a/report/index.html +++ b/report/index.html @@ -9,6 +9,6 @@
- + diff --git a/report/package-lock.json b/report/package-lock.json index 345d649..08af052 100644 --- a/report/package-lock.json +++ b/report/package-lock.json @@ -1,18 +1,24 @@ { "name": "repomap-report", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "repomap-report", - "version": "0.1.0", + "version": "0.2.0", "dependencies": { + "@xyflow/react": "^12.10.1", "cytoscape": "^3.30.4", "cytoscape-dagre": "^2.5.0", "d3": "^7.9.0", + "framer-motion": "^12.36.0", "fuse.js": "^7.0.0", "mermaid": "^11.4.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-resizable-panels": "^2.1.7", + "react-shiki": "^0.7.0", "shiki": "^1.24.4" }, "devDependencies": { @@ -20,6 +26,9 @@ "@tailwindcss/vite": "^4.0.0", "@types/cytoscape": "^3.21.8", "@types/d3": "^7.4.3", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", "postcss": "^8.4.49", "tailwindcss": "^4.0.0", @@ -53,6 +62,288 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@braintree/sanitize-url": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", @@ -616,6 +907,13 @@ "langium": "^4.0.0" } }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", @@ -1321,6 +1619,51 @@ "vite": "^5.2.0 || ^6 || ^7" } }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, "node_modules/@types/cytoscape": { "version": "3.21.9", "resolved": "https://registry.npmjs.org/@types/cytoscape/-/cytoscape-3.21.9.tgz", @@ -1581,13 +1924,30 @@ "@types/d3-selection": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/@types/geojson": { "version": "7946.0.16", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", @@ -1612,6 +1972,40 @@ "@types/unist": "*" } }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -1631,6 +2025,59 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@xyflow/react": { + "version": "12.10.1", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.1.tgz", + "integrity": "sha512-5eSWtIK/+rkldOuFbOOz44CRgQRjtS9v5nufk77DV+XBnfCGL9HAQ8PG00o2ZYKqkEU/Ak6wrKC95Tu+2zuK3Q==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.75", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.75", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.75.tgz", + "integrity": "sha512-iXs+AGFLi8w/VlAoc/iSxk+CxfT6o64Uw/k0CKASOPqjqz6E0rb5jFZgJtXGZCpfQI6OQpu5EnumP5fGxQheaQ==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -1758,6 +2205,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/character-entities-html4": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", @@ -1778,6 +2235,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chevrotain": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.2.tgz", @@ -1804,6 +2271,21 @@ "chevrotain": "^11.0.0" } }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -1829,6 +2311,13 @@ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", "license": "MIT" }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cose-base": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", @@ -1838,6 +2327,13 @@ "layout-base": "^1.0.0" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/cytoscape": { "version": "3.33.1", "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", @@ -2365,6 +2861,36 @@ "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", "license": "MIT" }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/delaunator": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", @@ -2497,6 +3023,16 @@ "node": ">=6" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2529,7 +3065,34 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fsevents": { + "node_modules/framer-motion": { + "version": "12.36.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.36.0.tgz", + "integrity": "sha512-4PqYHAT7gev0ke0wos+PyrcFxI0HScjm3asgU8nSYa8YzJFuwgIvdj3/s3ZaxLq0bUSboIn19A2WS/MHwLCvfw==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.36.0", + "motion-utils": "^12.36.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", @@ -2553,6 +3116,16 @@ "node": ">=10" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2598,6 +3171,33 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-whitespace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", @@ -2633,6 +3233,12 @@ "node": ">=0.10.0" } }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -2642,6 +3248,50 @@ "node": ">=12" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -2652,6 +3302,38 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/katex": { "version": "0.16.38", "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.38.tgz", @@ -2978,6 +3660,38 @@ "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", "license": "MIT" }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -3000,6 +3714,104 @@ "node": ">= 20" } }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-to-hast": { "version": "13.2.1", "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", @@ -3021,38 +3833,347 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mermaid": { - "version": "11.12.3", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.3.tgz", - "integrity": "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==", + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mermaid": { + "version": "11.12.3", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.3.tgz", + "integrity": "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.1", + "@mermaid-js/parser": "^1.0.0", + "@types/d3": "^7.4.3", + "cytoscape": "^3.29.3", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.13", + "dayjs": "^1.11.18", + "dompurify": "^3.2.5", + "katex": "^0.16.22", + "khroma": "^2.1.0", + "lodash-es": "^4.17.23", + "marked": "^16.2.1", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "@braintree/sanitize-url": "^7.1.1", - "@iconify/utils": "^3.0.1", - "@mermaid-js/parser": "^1.0.0", - "@types/d3": "^7.4.3", - "cytoscape": "^3.29.3", - "cytoscape-cose-bilkent": "^4.1.0", - "cytoscape-fcose": "^2.2.0", - "d3": "^7.9.0", - "d3-sankey": "^0.12.3", - "dagre-d3-es": "7.0.13", - "dayjs": "^1.11.18", - "dompurify": "^3.2.5", - "katex": "^0.16.22", - "khroma": "^2.1.0", - "lodash-es": "^4.17.23", - "marked": "^16.2.1", - "roughjs": "^4.6.6", - "stylis": "^4.3.6", - "ts-dedent": "^2.2.0", - "uuid": "^11.1.0" + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", "funding": [ { "type": "GitHub Sponsors", @@ -3065,8 +4186,10 @@ ], "license": "MIT", "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, "node_modules/micromark-util-encode": { @@ -3085,6 +4208,60 @@ ], "license": "MIT" }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, "node_modules/micromark-util-sanitize-uri": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", @@ -3106,6 +4283,28 @@ "micromark-util-symbol": "^2.0.0" } }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, "node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", @@ -3150,6 +4349,27 @@ "ufo": "^1.6.3" } }, + "node_modules/motion-dom": { + "version": "12.36.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.36.0.tgz", + "integrity": "sha512-Ep1pq8P88rGJ75om8lTCA13zqd7ywPGwCqwuWwin6BKc0hMLkVfcS6qKlRqEo2+t0DwoUcgGJfXwaiFn4AOcQA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.36.0" + } + }, + "node_modules/motion-utils": { + "version": "12.36.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz", + "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -3176,6 +4396,12 @@ "dev": true, "license": "MIT" }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "license": "MIT" + }, "node_modules/oniguruma-to-es": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-2.3.0.tgz", @@ -3193,6 +4419,31 @@ "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", "license": "MIT" }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/path-data-parser": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", @@ -3298,6 +4549,184 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-resizable-panels": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.9.tgz", + "integrity": "sha512-z77+X08YDIrgAes4jl8xhnUu1LNIRp4+E7cv4xHmLOxxUPO/ML7PSrE813b90vj7xvQ1lcf7g2uA9GeMZonjhQ==", + "license": "MIT", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/react-shiki": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/react-shiki/-/react-shiki-0.7.4.tgz", + "integrity": "sha512-L5E9pWh0MqRID7bcRVZ4fx0+X/3i9i7iGdXfR18OZ8leTuTclVoycTsYaNRpFnY4kyDLYDpfk6K6UHe4NRaEcg==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1", + "dequal": "^2.0.3", + "hast-util-to-jsx-runtime": "^2.3.6", + "shiki": "^3.11.0", + "unist-util-visit": "^5.0.0" + }, + "peerDependencies": { + "@types/react": ">=16.8.0", + "@types/react-dom": ">=16.8.0", + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/react-shiki/node_modules/@shikijs/core": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.23.0.tgz", + "integrity": "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/react-shiki/node_modules/@shikijs/engine-javascript": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.23.0.tgz", + "integrity": "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + } + }, + "node_modules/react-shiki/node_modules/@shikijs/engine-oniguruma": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", + "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/react-shiki/node_modules/@shikijs/langs": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", + "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0" + } + }, + "node_modules/react-shiki/node_modules/@shikijs/themes": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", + "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0" + } + }, + "node_modules/react-shiki/node_modules/@shikijs/types": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", + "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/react-shiki/node_modules/oniguruma-to-es": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", + "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/react-shiki/node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/react-shiki/node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/react-shiki/node_modules/shiki": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.23.0.tgz", + "integrity": "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.23.0", + "@shikijs/engine-javascript": "3.23.0", + "@shikijs/engine-oniguruma": "3.23.0", + "@shikijs/langs": "3.23.0", + "@shikijs/themes": "3.23.0", + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, "node_modules/regex": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/regex/-/regex-5.1.1.tgz", @@ -3398,6 +4827,25 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/shiki": { "version": "1.29.2", "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.29.2.tgz", @@ -3448,6 +4896,24 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, "node_modules/stylis": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", @@ -3520,6 +4986,12 @@ "node": ">=6.10" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -3639,6 +5111,15 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", @@ -3804,6 +5285,41 @@ "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", "license": "MIT" }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/report/package.json b/report/package.json index e165511..dd5b81d 100644 --- a/report/package.json +++ b/report/package.json @@ -1,6 +1,6 @@ { "name": "repomap-report", - "version": "0.1.0", + "version": "0.2.0", "private": true, "type": "module", "scripts": { @@ -9,11 +9,17 @@ "preview": "vite preview" }, "dependencies": { + "@xyflow/react": "^12.10.1", "cytoscape": "^3.30.4", "cytoscape-dagre": "^2.5.0", "d3": "^7.9.0", + "framer-motion": "^12.36.0", "fuse.js": "^7.0.0", "mermaid": "^11.4.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-resizable-panels": "^2.1.7", + "react-shiki": "^0.7.0", "shiki": "^1.24.4" }, "devDependencies": { @@ -21,6 +27,9 @@ "@tailwindcss/vite": "^4.0.0", "@types/cytoscape": "^3.21.8", "@types/d3": "^7.4.3", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", "postcss": "^8.4.49", "tailwindcss": "^4.0.0", diff --git a/report/tsconfig.json b/report/tsconfig.json index caffdde..72a399b 100644 --- a/report/tsconfig.json +++ b/report/tsconfig.json @@ -10,6 +10,7 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, + "jsx": "react-jsx", "lib": ["ES2020", "DOM", "DOM.Iterable"] }, "include": ["src"] diff --git a/report/vite.config.ts b/report/vite.config.ts index 26cc9cc..0294f2d 100644 --- a/report/vite.config.ts +++ b/report/vite.config.ts @@ -1,13 +1,14 @@ import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ - plugins: [tailwindcss()], + plugins: [react(), tailwindcss()], build: { outDir: "dist", emptyOutDir: true, rollupOptions: { - input: "src/main.ts", + input: "index.html", output: { format: "iife", entryFileNames: "bundle.js", @@ -22,4 +23,9 @@ export default defineConfig({ }, cssCodeSplit: false, }, + server: { + proxy: { + "/api": "http://localhost:4242", + }, + }, }); From aa115ac7c19efca2ce2b935a86c4a1344efa763a Mon Sep 17 00:00:00 2001 From: Humza Khan Date: Fri, 13 Mar 2026 14:01:14 -0400 Subject: [PATCH 2/5] feat: add shared layer color utilities and data fetching hook - layerColors.ts: centralized color constants and layer hints - useReportData.ts: React context and hook for loading report data from API, embedded JSON, or dev fixture --- report/src/hooks/useReportData.ts | 67 ++++++++++++++++++++++++++++++ report/src/utils/layerColors.ts | 68 +++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 report/src/hooks/useReportData.ts create mode 100644 report/src/utils/layerColors.ts diff --git a/report/src/hooks/useReportData.ts b/report/src/hooks/useReportData.ts new file mode 100644 index 0000000..3693386 --- /dev/null +++ b/report/src/hooks/useReportData.ts @@ -0,0 +1,67 @@ +import { useState, useEffect, createContext, useContext } from "react"; +import type { RepoMapData, ModuleSummary } from "../types"; + +interface ReportDataState { + data: RepoMapData | null; + summaryMap: Map; + loading: boolean; + error: string | null; +} + +export const ReportDataContext = createContext({ + data: null, + summaryMap: new Map(), + loading: true, + error: null, +}); + +export function useReportData() { + return useContext(ReportDataContext); +} + +export function useLoadReportData(): ReportDataState { + const [state, setState] = useState({ + data: null, + summaryMap: new Map(), + loading: true, + error: null, + }); + + useEffect(() => { + loadData() + .then((data) => { + const summaryMap = new Map(); + for (const s of data.summaries) { + summaryMap.set(s.file_path, s); + } + setState({ data, summaryMap, loading: false, error: null }); + }) + .catch((err) => { + setState({ data: null, summaryMap: new Map(), loading: false, error: String(err) }); + }); + }, []); + + return state; +} + +async function loadData(): Promise { + // Try API endpoint first (local server mode) + try { + const resp = await fetch("/api/report"); + if (resp.ok) { + return await resp.json(); + } + } catch { + // Not running in server mode, fall through + } + + // Try embedded JSON (static HTML mode) + const scriptTag = document.getElementById("repomap-data"); + if (scriptTag && scriptTag.textContent && scriptTag.textContent.trim().length > 2) { + return JSON.parse(scriptTag.textContent); + } + + // Dev mode: load fixture + const fixture = await import("../fixtures/sample.json"); + return fixture.default as unknown as RepoMapData; +} diff --git a/report/src/utils/layerColors.ts b/report/src/utils/layerColors.ts new file mode 100644 index 0000000..1dd9f9b --- /dev/null +++ b/report/src/utils/layerColors.ts @@ -0,0 +1,68 @@ +import type { LayerType } from "../types"; + +export const LAYER_COLORS: Record = { + api: "#7c3aed", + service: "#06b6d4", + data: "#f59e0b", + util: "#64748b", + test: "#10b981", + config: "#ec4899", + unknown: "#94a3b8", +}; + +export const LAYER_THEME: Record< + string, + { bg: string; border: string; dot: string; text: string; badge: string } +> = { + api: { bg: "#1e1040", border: "#7c3aed", dot: "#a78bfa", text: "#c4b5fd", badge: "#7c3aed" }, + service: { bg: "#0e2a33", border: "#0891b2", dot: "#22d3ee", text: "#67e8f9", badge: "#0891b2" }, + data: { bg: "#2a1a00", border: "#d97706", dot: "#fbbf24", text: "#fcd34d", badge: "#d97706" }, + config: { bg: "#1a0a2e", border: "#db2777", dot: "#f472b6", text: "#fbcfe8", badge: "#db2777" }, + util: { bg: "#1a1a24", border: "#64748b", dot: "#94a3b8", text: "#cbd5e1", badge: "#64748b" }, + test: { bg: "#0a2a1a", border: "#059669", dot: "#34d399", text: "#6ee7b7", badge: "#059669" }, + unknown: { bg: "#1a1a24", border: "#475569", dot: "#94a3b8", text: "#cbd5e1", badge: "#475569" }, +}; + +export function getLayerColor(layer: string): string { + return LAYER_COLORS[layer] || LAYER_COLORS.unknown; +} + +export function getLayerTheme(layer: string) { + return LAYER_THEME[layer] || LAYER_THEME.unknown; +} + +export const LAYER_HINTS: Record = { + api: "api", + routes: "api", + handlers: "api", + middleware: "api", + pages: "api", + cmd: "api", + components: "api", + service: "service", + services: "service", + aiprovider: "service", + classifier: "service", + workers: "service", + hooks: "service", + context: "service", + parser: "service", + report: "service", + recurrence: "service", + model: "data", + models: "data", + domain: "data", + repository: "data", + storage: "data", + db: "data", + migrations: "data", + types: "data", + config: "config", + lib: "util", + pkg: "util", + util: "util", + utils: "util", + helpers: "util", + test: "test", + testutil: "test", +}; From 191b29317178c3c67ebb556ed49208e4dedfeab7 Mon Sep 17 00:00:00 2001 From: Humza Khan Date: Fri, 13 Mar 2026 14:01:20 -0400 Subject: [PATCH 3/5] feat: migrate all UI components to React functional components Port Toolbar, SearchBar, NavTree, DetailPanel, and StatusBar from vanilla TypeScript classes to React functional components with hooks. Same visual behavior, now composable with React props/state. --- report/src/components/DetailPanel.tsx | 261 ++++++++++++++++++++++++++ report/src/components/NavTree.tsx | 109 +++++++++++ report/src/components/SearchBar.tsx | 122 ++++++++++++ report/src/components/StatusBar.tsx | 32 ++++ report/src/components/Toolbar.tsx | 39 ++++ 5 files changed, 563 insertions(+) create mode 100644 report/src/components/DetailPanel.tsx create mode 100644 report/src/components/NavTree.tsx create mode 100644 report/src/components/SearchBar.tsx create mode 100644 report/src/components/StatusBar.tsx create mode 100644 report/src/components/Toolbar.tsx diff --git a/report/src/components/DetailPanel.tsx b/report/src/components/DetailPanel.tsx new file mode 100644 index 0000000..f49d624 --- /dev/null +++ b/report/src/components/DetailPanel.tsx @@ -0,0 +1,261 @@ +import React, { useEffect, useState } from "react"; +import type { ModuleSummary } from "../types"; +import type { ModuleNode } from "../utils/moduleGrouper"; +import { getLayerColor } from "../utils/layerColors"; + +interface DetailPanelProps { + selection: + | { type: "file"; summary: ModuleSummary } + | { type: "module"; moduleNode: ModuleNode; summaryMap: Map } + | null; + onNavigate: (filePath: string) => void; +} + +type Highlighter = { + codeToHtml: (code: string, opts: { lang: string; theme: string }) => string; +} | null; + +export function DetailPanel({ selection, onNavigate }: DetailPanelProps) { + const [highlighter, setHighlighter] = useState(null); + + useEffect(() => { + import("shiki") + .then((shiki) => + shiki.createHighlighter({ + themes: ["vitesse-dark"], + langs: ["typescript", "javascript", "python", "go", "rust", "java", "ruby"], + }), + ) + .then(setHighlighter) + .catch(() => setHighlighter(null)); + }, []); + + if (!selection) { + return ( +
+
+ Select a module from the graph or nav tree to view details +
+
+ ); + } + + if (selection.type === "module") { + return ( +
+ +
+ ); + } + + return ( +
+ +
+ ); +} + +function FileDetail({ + summary, + onNavigate, + highlighter, +}: { + summary: ModuleSummary; + onNavigate: (fp: string) => void; + highlighter: Highlighter; +}) { + const langMap: Record = { + typescript: "typescript", + javascript: "javascript", + python: "python", + go: "go", + rust: "rust", + java: "java", + ruby: "ruby", + }; + + return ( + <> +
+
+
{summary.summary}
+
+ {summary.responsibilities.length > 0 && ( +
+ +
+ )} + {summary.patterns.length > 0 && ( +
+ +
+ )} + {summary.exports.length > 0 && ( +
+ `${e.name} (${e.kind}, line ${e.line})`)} /> +
+ )} + {summary.dependencies_on.length > 0 && ( +
+ {summary.dependencies_on.map((dep) => ( + { + e.preventDefault(); + onNavigate(dep); + }} + > + {dep} + + ))} +
+ )} + {summary.depended_on_by_hint && summary.depended_on_by_hint.length > 0 && ( +
+ {summary.depended_on_by_hint.map((dep) => ( + { + e.preventDefault(); + onNavigate(dep); + }} + > + {dep} + + ))} +
+ )} + {summary.source_code && ( +
+ +
+ )} + + ); +} + +function ModuleDetail({ + moduleNode, + summaryMap, + onNavigate, +}: { + moduleNode: ModuleNode; + summaryMap: Map; + onNavigate: (fp: string) => void; +}) { + const allExports: string[] = []; + for (const fp of moduleNode.files) { + const s = summaryMap.get(fp); + if (s?.exports) { + for (const exp of s.exports) { + allExports.push(`${exp.name} (${exp.kind})`); + } + } + } + + return ( + <> +
+
+ {moduleNode.files.map((fp) => ( + { + e.preventDefault(); + onNavigate(fp); + }} + > + {fp.split("/").pop() || fp} + + ))} +
+ {moduleNode.summary && ( +
+
{moduleNode.summary}
+
+ )} + {allExports.length > 0 && ( +
+ + {allExports.length > 15 && ( +
+ ... and {allExports.length - 15} more +
+ )} +
+ )} + + ); +} + +function Header({ layer, title }: { layer: string; title: string }) { + return ( +
+ + {layer} + +
{title}
+
+ ); +} + +function Section({ title, children }: { title: string; children: React.ReactNode }) { + return ( +
+
{title}
+ {children} +
+ ); +} + +function List({ items }: { items: string[] }) { + return ( +
    + {items.map((item, i) => ( +
  • {item}
  • + ))} +
+ ); +} + +function CodeBlock({ + code, + language, + highlighter, +}: { + code: string; + language: string; + highlighter: Highlighter; +}) { + if (highlighter) { + try { + const html = highlighter.codeToHtml(code, { lang: language, theme: "vitesse-dark" }); + return
; + } catch { + // fall through to plain text + } + } + return ( +
+
{code}
+
+ ); +} diff --git a/report/src/components/NavTree.tsx b/report/src/components/NavTree.tsx new file mode 100644 index 0000000..a462e87 --- /dev/null +++ b/report/src/components/NavTree.tsx @@ -0,0 +1,109 @@ +import React, { useState, useMemo, useCallback } from "react"; +import type { ModuleSummary, LayerType } from "../types"; +import { getLayerColor, LAYER_HINTS } from "../utils/layerColors"; + +interface NavTreeProps { + summaries: ModuleSummary[]; + selectedPath: string | null; + onSelect: (filePath: string) => void; +} + +interface TreeGroup { + name: string; + moduleId: string; + layer: LayerType; + items: ModuleSummary[]; +} + +function getModuleDir(filePath: string): string { + const parts = filePath.split("/"); + if (parts.length <= 1) return parts[0]; + const dirParts = parts.slice(0, -1); + if (dirParts.length >= 3 && (dirParts[1] === "internal" || dirParts[1] === "src")) { + return dirParts.slice(0, 3).join("/"); + } + if (dirParts.length >= 3) return dirParts.slice(0, 3).join("/"); + if (dirParts.length >= 2) return dirParts.join("/"); + return dirParts[0]; +} + +function getModuleLabel(moduleId: string): string { + const parts = moduleId.split("/"); + if (parts.length >= 3 && (parts[1] === "internal" || parts[1] === "src")) { + return parts.slice(2).join("/"); + } + if (parts.length >= 2) return parts[parts.length - 1]; + return moduleId; +} + +export function NavTree({ summaries, selectedPath, onSelect }: NavTreeProps) { + const [collapsed, setCollapsed] = useState>({}); + + const groups = useMemo(() => { + const moduleGroups = new Map(); + for (const s of summaries) { + const moduleId = getModuleDir(s.file_path); + if (!moduleGroups.has(moduleId)) moduleGroups.set(moduleId, []); + moduleGroups.get(moduleId)!.push(s); + } + + return Array.from(moduleGroups.entries()) + .map(([moduleId, items]): TreeGroup => { + const dirName = moduleId.split("/").pop() || moduleId; + const layer = LAYER_HINTS[dirName.toLowerCase()] || "unknown"; + return { + name: getModuleLabel(moduleId), + moduleId, + layer, + items: items.sort((a, b) => a.file_path.localeCompare(b.file_path)), + }; + }) + .sort((a, b) => a.name.localeCompare(b.name)); + }, [summaries]); + + const toggleGroup = useCallback((moduleId: string) => { + setCollapsed((prev) => ({ ...prev, [moduleId]: !prev[moduleId] })); + }, []); + + // Start all collapsed by default + const isCollapsed = (moduleId: string) => collapsed[moduleId] !== false; + + return ( +
+ {groups.map((group) => ( +
+
toggleGroup(group.moduleId)}> + + {"\u25BE"} + + + + {group.name} ({group.items.length}) + +
+ {!isCollapsed(group.moduleId) && + group.items.map((item) => ( +
onSelect(item.file_path)} + > + + + {item.file_path.split("/").pop() || item.file_path} + +
+ ))} +
+ ))} +
+ ); +} diff --git a/report/src/components/SearchBar.tsx b/report/src/components/SearchBar.tsx new file mode 100644 index 0000000..88349c3 --- /dev/null +++ b/report/src/components/SearchBar.tsx @@ -0,0 +1,122 @@ +import React, { useState, useRef, useMemo, useCallback } from "react"; +import Fuse from "fuse.js"; +import type { ModuleSummary, SearchEntry } from "../types"; +import { getLayerColor } from "../utils/layerColors"; + +interface SearchBarProps { + summaries: ModuleSummary[]; + onSelect: (filePath: string) => void; +} + +export function SearchBar({ summaries, onSelect }: SearchBarProps) { + const [query, setQuery] = useState(""); + const [activeIndex, setActiveIndex] = useState(-1); + const [showResults, setShowResults] = useState(false); + const inputRef = useRef(null); + const hideTimer = useRef>(); + + const { fuse, entries } = useMemo(() => { + const entries: SearchEntry[] = summaries.map((s) => ({ + id: s.file_path, + label: s.file_path.split("/").pop() || s.file_path, + file_path: s.file_path, + kind: s.layer, + keywords: [ + s.summary, + ...(s.responsibilities || []), + ...(s.exports || []).map((e) => e.name), + ...(s.patterns || []), + ].join(" "), + })); + + const fuse = new Fuse(entries, { + keys: [ + { name: "label", weight: 2 }, + { name: "file_path", weight: 1.5 }, + { name: "keywords", weight: 1 }, + ], + threshold: 0.4, + includeScore: true, + }); + + return { fuse, entries }; + }, [summaries]); + + const results = useMemo(() => { + if (!query.trim()) return []; + return fuse.search(query, { limit: 10 }).map((r) => r.item); + }, [fuse, query]); + + const handleSelect = useCallback( + (filePath: string) => { + onSelect(filePath); + setQuery(""); + setShowResults(false); + }, + [onSelect], + ); + + const handleKeydown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === "ArrowDown") { + e.preventDefault(); + setActiveIndex((i) => Math.min(i + 1, results.length - 1)); + } else if (e.key === "ArrowUp") { + e.preventDefault(); + setActiveIndex((i) => Math.max(i - 1, 0)); + } else if (e.key === "Enter" && activeIndex >= 0 && results[activeIndex]) { + e.preventDefault(); + handleSelect(results[activeIndex].file_path); + } else if (e.key === "Escape") { + setQuery(""); + setShowResults(false); + inputRef.current?.blur(); + } + }, + [activeIndex, results, handleSelect], + ); + + return ( +
+ {"\u2315"} + { + setQuery(e.target.value); + setActiveIndex(-1); + setShowResults(true); + }} + onFocus={() => { + clearTimeout(hideTimer.current); + if (query.trim()) setShowResults(true); + }} + onBlur={() => { + hideTimer.current = setTimeout(() => setShowResults(false), 150); + }} + onKeyDown={handleKeydown} + /> + {showResults && results.length > 0 && ( +
+ {results.map((entry, i) => ( +
handleSelect(entry.file_path)} + > + + {entry.label} + {entry.file_path} +
+ ))} +
+ )} +
+ ); +} diff --git a/report/src/components/StatusBar.tsx b/report/src/components/StatusBar.tsx new file mode 100644 index 0000000..bceb273 --- /dev/null +++ b/report/src/components/StatusBar.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import type { PipelineStats, RepoGraph } from "../types"; + +interface StatusBarProps { + stats: PipelineStats; + graph: RepoGraph; +} + +export function StatusBar({ stats, graph }: StatusBarProps) { + const langs = (stats.languages || []) + .map((l) => `${l.language} ${l.percentage.toFixed(0)}%`) + .join(", "); + const duration = (stats.duration_ms / 1000).toFixed(1); + + return ( +
+ + + + + +
+ + + +
+ ); +} + +function Item({ text }: { text: string }) { + return {text}; +} diff --git a/report/src/components/Toolbar.tsx b/report/src/components/Toolbar.tsx new file mode 100644 index 0000000..5fb623f --- /dev/null +++ b/report/src/components/Toolbar.tsx @@ -0,0 +1,39 @@ +import React from "react"; + +export type ViewName = "architecture" | "dependencies" | "models" | "flows"; + +const TABS: { id: ViewName; label: string }[] = [ + { id: "architecture", label: "Architecture" }, + { id: "dependencies", label: "Dependencies" }, + { id: "models", label: "Data Models" }, + { id: "flows", label: "Call Flow" }, +]; + +interface ToolbarProps { + repoName: string; + activeView: ViewName; + onViewChange: (view: ViewName) => void; + children?: React.ReactNode; +} + +export function Toolbar({ repoName, activeView, onViewChange, children }: ToolbarProps) { + return ( +
+
repomap
+ {repoName} +
+ {TABS.map((tab) => ( + + ))} +
+
+ {children} +
+ ); +} From bd5821bd08d84eacc7a4077ae199929fa4dbc0d9 Mon Sep 17 00:00:00 2001 From: Humza Khan Date: Fri, 13 Mar 2026 14:01:28 -0400 Subject: [PATCH 4/5] feat: add React view components for all four visualizations - ArchitectureMap.tsx: Cytoscape.js graph with card overlays (ported) - DependencyGraph.tsx: placeholder for Phase 4 - DataModels.tsx: placeholder for Phase 5 - CallFlow.tsx: placeholder for Phase 6 All views integrated into the tab navigation system. --- report/src/views/ArchitectureMap.tsx | 253 +++++++++++++++++++++++++++ report/src/views/CallFlow.tsx | 25 +++ report/src/views/DataModels.tsx | 29 +++ report/src/views/DependencyGraph.tsx | 26 +++ 4 files changed, 333 insertions(+) create mode 100644 report/src/views/ArchitectureMap.tsx create mode 100644 report/src/views/CallFlow.tsx create mode 100644 report/src/views/DataModels.tsx create mode 100644 report/src/views/DependencyGraph.tsx diff --git a/report/src/views/ArchitectureMap.tsx b/report/src/views/ArchitectureMap.tsx new file mode 100644 index 0000000..374b4db --- /dev/null +++ b/report/src/views/ArchitectureMap.tsx @@ -0,0 +1,253 @@ +import React, { useEffect, useRef, useCallback } from "react"; +import cytoscape from "cytoscape"; +import cytoscapeDagre from "cytoscape-dagre"; +import type { RepoGraph, ModuleSummary } from "../types"; +import { groupIntoModules, type ModuleNode, type ModuleGraph } from "../utils/moduleGrouper"; +import { getLayerTheme } from "../utils/layerColors"; + +cytoscapeDagre(cytoscape); + +interface ArchitectureMapProps { + graph: RepoGraph; + summaries: ModuleSummary[]; + onNodeSelect: (moduleId: string | null, moduleNode?: ModuleNode) => void; +} + +export function ArchitectureMap({ graph, summaries, onNodeSelect }: ArchitectureMapProps) { + const containerRef = useRef(null); + const overlayRef = useRef(null); + const cyRef = useRef(null); + const moduleGraphRef = useRef(null); + const nodeMapRef = useRef>(new Map()); + + const updateOverlayPositions = useCallback(() => { + const cy = cyRef.current; + const overlay = overlayRef.current; + if (!cy || !overlay) return; + + const pan = cy.pan(); + const zoom = cy.zoom(); + + overlay.querySelectorAll(".module-card").forEach((card) => { + const nodeId = card.dataset.nodeId; + if (!nodeId) return; + + const cyNode = cy.getElementById(nodeId); + if (cyNode.length === 0) return; + + const pos = cyNode.position(); + const x = pos.x * zoom + pan.x; + const y = pos.y * zoom + pan.y; + + const w = card.offsetWidth; + const h = card.offsetHeight; + card.style.left = `${x - w / 2}px`; + card.style.top = `${y - h / 2}px`; + card.style.transform = `scale(${Math.min(1, zoom)})`; + card.style.transformOrigin = "center center"; + }); + }, []); + + useEffect(() => { + if (!containerRef.current || !overlayRef.current) return; + + const moduleGraph = groupIntoModules(summaries, graph); + moduleGraphRef.current = moduleGraph; + const nodeMap = new Map(); + for (const n of moduleGraph.nodes) nodeMap.set(n.id, n); + nodeMapRef.current = nodeMap; + + const elements: cytoscape.ElementDefinition[] = []; + + for (const node of moduleGraph.nodes) { + const colors = getLayerTheme(node.layer); + elements.push({ + data: { + id: node.id, + label: node.label, + layer: node.layer, + bgColor: colors.bg, + borderColor: colors.border, + textColor: colors.text, + }, + }); + } + + for (const edge of moduleGraph.edges) { + elements.push({ + data: { source: edge.source, target: edge.target, weight: edge.weight }, + }); + } + + const cy = cytoscape({ + container: containerRef.current, + elements, + style: [ + { + selector: "node", + style: { + shape: "roundrectangle", + width: 160, + height: 48, + "background-color": "data(bgColor)", + "border-width": 1.5, + "border-color": "data(borderColor)", + "border-opacity": 0.6, + label: "data(label)", + "font-size": "11px", + "font-family": "'JetBrains Mono', 'Fira Code', monospace", + "font-weight": "600" as unknown as number, + color: "data(textColor)", + "text-valign": "center", + "text-halign": "center", + "text-margin-y": -6, + "text-outline-width": 0, + }, + }, + { + selector: "node:selected", + style: { "border-width": 2, "border-color": "#ffffff", "border-opacity": 1 }, + }, + { + selector: "node:active", + style: { "overlay-opacity": 0 }, + }, + { + selector: "edge", + style: { + width: 1.5, + "line-color": "#1e293b", + "target-arrow-color": "#334155", + "target-arrow-shape": "triangle", + "arrow-scale": 0.8, + "curve-style": "bezier", + opacity: 0.5, + }, + }, + { + selector: "edge:selected", + style: { "line-color": "#64748b", "target-arrow-color": "#64748b", opacity: 1 }, + }, + ], + layout: { + name: "dagre", + rankDir: "TB", + spacingFactor: 1.6, + nodeSep: 60, + rankSep: 100, + animate: false, + } as cytoscape.LayoutOptions, + minZoom: 0.3, + maxZoom: 2.5, + wheelSensitivity: 0.3, + }); + + cyRef.current = cy; + + // Render card overlays + renderCardOverlays(overlayRef.current, cy, nodeMap, onNodeSelect); + updateOverlayPositions(); + + cy.on("viewport", updateOverlayPositions); + cy.on("position", updateOverlayPositions); + + cy.on("tap", "node", (evt) => { + const nodeId = evt.target.id(); + onNodeSelect(nodeId, nodeMap.get(nodeId)); + }); + + cy.on("tap", (evt) => { + if (evt.target === cy) onNodeSelect(null); + }); + + return () => { + cy.destroy(); + cyRef.current = null; + }; + }, [graph, summaries, onNodeSelect, updateOverlayPositions]); + + return ( +
+
+
+
+ ); +} + +function renderCardOverlays( + overlay: HTMLElement, + cy: cytoscape.Core, + nodeMap: Map, + onNodeSelect: (id: string, node?: ModuleNode) => void, +) { + overlay.innerHTML = ""; + + cy.nodes().forEach((cyNode) => { + const id = cyNode.id(); + const moduleNode = nodeMap.get(id); + if (!moduleNode) return; + + const colors = getLayerTheme(moduleNode.layer); + const card = document.createElement("div"); + card.className = "module-card"; + card.dataset.nodeId = id; + card.style.cssText = ` + position: absolute; + pointer-events: auto; + cursor: pointer; + min-width: 140px; + padding: 8px 14px; + border-radius: 10px; + background: ${colors.bg}; + border: 1.5px solid ${colors.border}88; + transition: border-color 0.15s, box-shadow 0.15s; + `; + + const row1 = document.createElement("div"); + row1.style.cssText = "display:flex;align-items:center;gap:7px;margin-bottom:3px;"; + const dot = document.createElement("div"); + dot.style.cssText = `width:7px;height:7px;border-radius:50%;background:${colors.dot};flex-shrink:0;`; + row1.appendChild(dot); + const label = document.createElement("span"); + label.style.cssText = `color:${colors.text};font-size:11px;font-family:'JetBrains Mono','Fira Code',monospace;font-weight:600;letter-spacing:0.02em;`; + label.textContent = moduleNode.label; + row1.appendChild(label); + card.appendChild(row1); + + const row2 = document.createElement("div"); + row2.style.cssText = "display:flex;gap:6px;align-items:center;"; + const fileCount = document.createElement("span"); + fileCount.style.cssText = "color:#475569;font-size:10px;font-family:monospace;"; + fileCount.textContent = `${moduleNode.fileCount} file${moduleNode.fileCount !== 1 ? "s" : ""}`; + row2.appendChild(fileCount); + const badge = document.createElement("span"); + badge.style.cssText = `color:${colors.border};font-size:9px;font-family:monospace;background:${colors.border}18;padding:1px 5px;border-radius:3px;`; + badge.textContent = moduleNode.layer; + row2.appendChild(badge); + card.appendChild(row2); + + card.addEventListener("click", (e) => { + e.stopPropagation(); + onNodeSelect(id, moduleNode); + }); + + card.addEventListener("mouseenter", () => { + card.style.borderColor = colors.border; + card.style.boxShadow = `0 0 0 3px ${colors.border}44, 0 8px 32px #00000080`; + }); + card.addEventListener("mouseleave", () => { + card.style.borderColor = `${colors.border}88`; + card.style.boxShadow = "none"; + }); + + overlay.appendChild(card); + }); +} diff --git a/report/src/views/CallFlow.tsx b/report/src/views/CallFlow.tsx new file mode 100644 index 0000000..bcd32b0 --- /dev/null +++ b/report/src/views/CallFlow.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import type { CriticalPath } from "../types"; + +interface CallFlowProps { + criticalPaths: CriticalPath[]; + onNodeSelect: (nodeId: string) => void; +} + +export function CallFlow({ criticalPaths, onNodeSelect }: CallFlowProps) { + return ( +
+
+
{"\uD83D\uDCE1"}
+
Call Flow Tracer
+
+ Trace execution paths from entry points through the stack. + Animated flow visualization with depth control. +
+
+ {criticalPaths.length} critical paths available +
+
+
+ ); +} diff --git a/report/src/views/DataModels.tsx b/report/src/views/DataModels.tsx new file mode 100644 index 0000000..53a5227 --- /dev/null +++ b/report/src/views/DataModels.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import type { ModuleSummary } from "../types"; + +interface DataModelsProps { + summaries: ModuleSummary[]; + onNodeSelect: (nodeId: string) => void; +} + +export function DataModels({ summaries, onNodeSelect }: DataModelsProps) { + const dataModels = summaries.filter( + (s) => s.layer === "data" || s.patterns.some((p) => p.toLowerCase().includes("model")), + ); + + return ( +
+
+
{"\uD83D\uDDC3\uFE0F"}
+
Data Models
+
+ ERD-style visualization of schemas, interfaces, types, and their relationships. + Cross-linked across the codebase. +
+
+ {dataModels.length} data-layer files detected +
+
+
+ ); +} diff --git a/report/src/views/DependencyGraph.tsx b/report/src/views/DependencyGraph.tsx new file mode 100644 index 0000000..1cc5ba0 --- /dev/null +++ b/report/src/views/DependencyGraph.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import type { RepoGraph, ModuleSummary } from "../types"; + +interface DependencyGraphProps { + graph: RepoGraph; + summaries: ModuleSummary[]; + onNodeSelect: (nodeId: string) => void; +} + +export function DependencyGraph({ graph, summaries, onNodeSelect }: DependencyGraphProps) { + return ( +
+
+
{"\uD83D\uDD78\uFE0F"}
+
Dependency Graph
+
+ Interactive import-relationship explorer with circular dependency detection, + fan-in/fan-out metrics, and dead code identification. +
+
+ {graph.nodes.length} nodes, {graph.edges.length} edges ready +
+
+
+ ); +} From 76f81055746edf0e4a48acfb6761b130eb77fc92 Mon Sep 17 00:00:00 2001 From: Humza Khan Date: Fri, 13 Mar 2026 14:01:35 -0400 Subject: [PATCH 5/5] feat: replace vanilla TS entry point with React app - App.tsx: main component with resizable panel layout, view switching with Framer Motion transitions, and coordinated selection state - main.tsx: React 18 createRoot entry point with StrictMode - Remove old vanilla TypeScript component and view files - Update CSS to work with React panel layout instead of CSS grid --- report/src/App.tsx | 160 +++++++++++++ report/src/components/DetailPanel.ts | 322 -------------------------- report/src/components/NavTree.ts | 189 ---------------- report/src/components/SearchBar.ts | 172 -------------- report/src/components/StatusBar.ts | 48 ---- report/src/components/Toolbar.ts | 82 ------- report/src/main.ts | 167 -------------- report/src/main.tsx | 23 ++ report/src/styles/main.css | 16 +- report/src/views/ArchitectureMap.ts | 323 --------------------------- report/src/views/FlowExplorer.ts | 151 ------------- report/src/views/ModelCanvas.ts | 258 --------------------- 12 files changed, 187 insertions(+), 1724 deletions(-) create mode 100644 report/src/App.tsx delete mode 100644 report/src/components/DetailPanel.ts delete mode 100644 report/src/components/NavTree.ts delete mode 100644 report/src/components/SearchBar.ts delete mode 100644 report/src/components/StatusBar.ts delete mode 100644 report/src/components/Toolbar.ts delete mode 100644 report/src/main.ts create mode 100644 report/src/main.tsx delete mode 100644 report/src/views/ArchitectureMap.ts delete mode 100644 report/src/views/FlowExplorer.ts delete mode 100644 report/src/views/ModelCanvas.ts diff --git a/report/src/App.tsx b/report/src/App.tsx new file mode 100644 index 0000000..2fa6cd2 --- /dev/null +++ b/report/src/App.tsx @@ -0,0 +1,160 @@ +import React, { useState, useCallback } from "react"; +import { AnimatePresence, motion } from "framer-motion"; +import { PanelGroup, Panel, PanelResizeHandle } from "react-resizable-panels"; +import { Toolbar, type ViewName } from "./components/Toolbar"; +import { SearchBar } from "./components/SearchBar"; +import { NavTree } from "./components/NavTree"; +import { DetailPanel } from "./components/DetailPanel"; +import { StatusBar } from "./components/StatusBar"; +import { ArchitectureMap } from "./views/ArchitectureMap"; +import { DependencyGraph } from "./views/DependencyGraph"; +import { DataModels } from "./views/DataModels"; +import { CallFlow } from "./views/CallFlow"; +import { useReportData } from "./hooks/useReportData"; +import type { ModuleSummary } from "./types"; +import type { ModuleNode } from "./utils/moduleGrouper"; + +export function App() { + const { data, summaryMap, loading, error } = useReportData(); + const [activeView, setActiveView] = useState("architecture"); + const [selectedPath, setSelectedPath] = useState(null); + const [selection, setSelection] = useState< + | { type: "file"; summary: ModuleSummary } + | { type: "module"; moduleNode: ModuleNode; summaryMap: Map } + | null + >(null); + + const selectFile = useCallback( + (filePath: string) => { + const summary = summaryMap.get(filePath); + if (!summary) return; + setSelectedPath(filePath); + setSelection({ type: "file", summary }); + }, + [summaryMap], + ); + + const selectModule = useCallback( + (moduleId: string | null, moduleNode?: ModuleNode) => { + if (!moduleNode) { + setSelection(null); + return; + } + setSelection({ type: "module", moduleNode, summaryMap }); + }, + [summaryMap], + ); + + if (loading) { + return ( +
+ Loading analysis data... +
+ ); + } + + if (error || !data) { + return ( +
+

Failed to load report

+
{error}
+
+ ); + } + + return ( +
+ + + + + + + + + + + + +
+ + + {renderView(activeView, data, selectModule, selectFile)} + + +
+
+
+
+ + + + +
+ + +
+ ); +} + +function renderView( + view: ViewName, + data: NonNullable["data"]>, + onModuleSelect: (id: string | null, node?: ModuleNode) => void, + onFileSelect: (fp: string) => void, +) { + switch (view) { + case "architecture": + return ( + + ); + case "dependencies": + return ( + + ); + case "models": + return ; + case "flows": + return ( + + ); + } +} diff --git a/report/src/components/DetailPanel.ts b/report/src/components/DetailPanel.ts deleted file mode 100644 index e57b232..0000000 --- a/report/src/components/DetailPanel.ts +++ /dev/null @@ -1,322 +0,0 @@ -import type { ModuleSummary } from "../types"; -import type { ModuleNode } from "../utils/moduleGrouper"; - -const LAYER_COLORS: Record = { - api: "#7c3aed", - service: "#06b6d4", - data: "#f59e0b", - util: "#64748b", - test: "#10b981", - config: "#ec4899", - unknown: "#94a3b8", -}; - -export interface DetailPanelOptions { - onNavigate: (filePath: string) => void; -} - -export class DetailPanel { - private el: HTMLElement; - private onNavigate: (filePath: string) => void; - private highlighterReady: Promise; - private highlighter: { - codeToHtml: (code: string, opts: { lang: string; theme: string }) => string; - } | null = null; - - constructor(options: DetailPanelOptions) { - this.onNavigate = options.onNavigate; - this.el = document.createElement("div"); - this.el.className = "detail-panel"; - - this.highlighterReady = this.initHighlighter(); - - this.showEmpty(); - } - - private async initHighlighter(): Promise { - try { - const shiki = await import("shiki"); - this.highlighter = await shiki.createHighlighter({ - themes: ["vitesse-dark"], - langs: ["typescript", "javascript", "python", "go", "rust", "java", "ruby"], - }); - } catch { - // Shiki may fail to load in some environments; fall back to plain text - this.highlighter = null; - } - } - - showEmpty(): void { - this.el.innerHTML = ""; - const empty = document.createElement("div"); - empty.className = "detail-panel__empty"; - empty.textContent = "Select a module from the graph or nav tree to view details"; - this.el.appendChild(empty); - } - - async show(summary: ModuleSummary): Promise { - this.el.innerHTML = ""; - - // Header - const header = document.createElement("div"); - header.className = "detail-panel__header"; - - const badge = document.createElement("span"); - badge.className = "detail-panel__badge"; - badge.textContent = summary.layer; - badge.style.backgroundColor = LAYER_COLORS[summary.layer] || LAYER_COLORS.unknown; - badge.style.color = "#fff"; - header.appendChild(badge); - - const title = document.createElement("div"); - title.className = "detail-panel__title"; - title.textContent = summary.file_path; - header.appendChild(title); - this.el.appendChild(header); - - // Summary - this.addSection("Summary", () => { - const p = document.createElement("div"); - p.className = "detail-panel__summary"; - p.textContent = summary.summary; - return p; - }); - - // Responsibilities - if (summary.responsibilities.length > 0) { - this.addSection("Responsibilities", () => { - const ul = document.createElement("ul"); - ul.className = "detail-panel__list"; - for (const r of summary.responsibilities) { - const li = document.createElement("li"); - li.textContent = r; - ul.appendChild(li); - } - return ul; - }); - } - - // Patterns - if (summary.patterns.length > 0) { - this.addSection("Patterns", () => { - const ul = document.createElement("ul"); - ul.className = "detail-panel__list"; - for (const p of summary.patterns) { - const li = document.createElement("li"); - li.textContent = p; - ul.appendChild(li); - } - return ul; - }); - } - - // Exports - if (summary.exports.length > 0) { - this.addSection("Exports", () => { - const ul = document.createElement("ul"); - ul.className = "detail-panel__list"; - for (const exp of summary.exports) { - const li = document.createElement("li"); - li.textContent = `${exp.name} (${exp.kind}, line ${exp.line})`; - ul.appendChild(li); - } - return ul; - }); - } - - // Dependencies (clickable) - if (summary.dependencies_on.length > 0) { - this.addSection("Depends On", () => { - const container = document.createElement("div"); - for (const dep of summary.dependencies_on) { - const link = document.createElement("a"); - link.className = "detail-panel__dep"; - link.textContent = dep; - link.href = "#"; - link.addEventListener("click", (e) => { - e.preventDefault(); - this.onNavigate(dep); - }); - container.appendChild(link); - } - return container; - }); - } - - // Depended on by - if (summary.depended_on_by_hint && summary.depended_on_by_hint.length > 0) { - this.addSection("Depended On By", () => { - const container = document.createElement("div"); - for (const dep of summary.depended_on_by_hint!) { - const link = document.createElement("a"); - link.className = "detail-panel__dep"; - link.textContent = dep; - link.href = "#"; - link.addEventListener("click", (e) => { - e.preventDefault(); - this.onNavigate(dep); - }); - container.appendChild(link); - } - return container; - }); - } - - // Source code - if (summary.source_code) { - this.addSection("Source Code", () => { - const codeBlock = document.createElement("div"); - codeBlock.className = "detail-panel__code"; - - // Try to highlight with shiki - if (this.highlighter) { - try { - const lang = this.mapLanguage(summary.language); - codeBlock.innerHTML = this.highlighter.codeToHtml(summary.source_code!, { - lang, - theme: "vitesse-dark", - }); - } catch { - const pre = document.createElement("pre"); - pre.textContent = summary.source_code!; - codeBlock.appendChild(pre); - } - } else { - const pre = document.createElement("pre"); - pre.textContent = summary.source_code!; - codeBlock.appendChild(pre); - } - return codeBlock; - }); - - // If highlighter not ready yet, try again once it loads - if (!this.highlighter) { - this.highlighterReady.then(() => { - if (this.highlighter && summary.source_code) { - const codeBlocks = this.el.querySelectorAll(".detail-panel__code"); - const lastBlock = codeBlocks[codeBlocks.length - 1] as HTMLElement; - if (lastBlock) { - try { - const lang = this.mapLanguage(summary.language); - lastBlock.innerHTML = this.highlighter.codeToHtml( - summary.source_code, - { lang, theme: "vitesse-dark" } - ); - } catch { - // keep plain text - } - } - } - }); - } - } - } - - private addSection(title: string, buildContent: () => HTMLElement): void { - const section = document.createElement("div"); - section.className = "detail-panel__section"; - - const heading = document.createElement("div"); - heading.className = "detail-panel__section-title"; - heading.textContent = title; - section.appendChild(heading); - - section.appendChild(buildContent()); - this.el.appendChild(section); - } - - private mapLanguage(lang: string): string { - const map: Record = { - typescript: "typescript", - javascript: "javascript", - python: "python", - go: "go", - rust: "rust", - java: "java", - ruby: "ruby", - }; - return map[lang.toLowerCase()] || "typescript"; - } - - showModule(moduleNode: ModuleNode, summaryMap: Map): void { - this.el.innerHTML = ""; - - // Header - const header = document.createElement("div"); - header.className = "detail-panel__header"; - - const badge = document.createElement("span"); - badge.className = "detail-panel__badge"; - badge.textContent = moduleNode.layer; - badge.style.backgroundColor = LAYER_COLORS[moduleNode.layer] || LAYER_COLORS.unknown; - badge.style.color = "#fff"; - header.appendChild(badge); - - const title = document.createElement("div"); - title.className = "detail-panel__title"; - title.textContent = moduleNode.label; - header.appendChild(title); - this.el.appendChild(header); - - // File count - this.addSection(`${moduleNode.fileCount} Files`, () => { - const container = document.createElement("div"); - for (const fp of moduleNode.files) { - const link = document.createElement("a"); - link.className = "detail-panel__dep"; - link.textContent = fp.split("/").pop() || fp; - link.title = fp; - link.href = "#"; - link.addEventListener("click", (e) => { - e.preventDefault(); - this.onNavigate(fp); - }); - container.appendChild(link); - } - return container; - }); - - // Module summary (combined from file summaries) - if (moduleNode.summary) { - this.addSection("Summary", () => { - const p = document.createElement("div"); - p.className = "detail-panel__summary"; - p.textContent = moduleNode.summary; - return p; - }); - } - - // Key exports across all files in the module - const allExports: string[] = []; - for (const fp of moduleNode.files) { - const s = summaryMap.get(fp); - if (s && s.exports) { - for (const exp of s.exports) { - allExports.push(`${exp.name} (${exp.kind})`); - } - } - } - if (allExports.length > 0) { - this.addSection("Exports", () => { - const ul = document.createElement("ul"); - ul.className = "detail-panel__list"; - for (const exp of allExports.slice(0, 15)) { - const li = document.createElement("li"); - li.textContent = exp; - ul.appendChild(li); - } - if (allExports.length > 15) { - const li = document.createElement("li"); - li.textContent = `... and ${allExports.length - 15} more`; - li.style.color = "#64748b"; - ul.appendChild(li); - } - return ul; - }); - } - } - - mount(parent: HTMLElement): void { - parent.appendChild(this.el); - } -} diff --git a/report/src/components/NavTree.ts b/report/src/components/NavTree.ts deleted file mode 100644 index 14d4a7b..0000000 --- a/report/src/components/NavTree.ts +++ /dev/null @@ -1,189 +0,0 @@ -import type { ModuleSummary, LayerType } from "../types"; - -export interface NavTreeOptions { - summaries: ModuleSummary[]; - onSelect: (filePath: string) => void; -} - -interface TreeGroup { - name: string; - layer: LayerType; - items: ModuleSummary[]; - collapsed: boolean; -} - -const LAYER_COLORS: Record = { - api: "#7c3aed", - service: "#06b6d4", - data: "#f59e0b", - util: "#64748b", - test: "#10b981", - config: "#ec4899", - unknown: "#94a3b8", -}; - -const LAYER_HINTS: Record = { - api: "api", - routes: "api", - handlers: "api", - middleware: "api", - pages: "api", - cmd: "api", - components: "api", - service: "service", - services: "service", - aiprovider: "service", - classifier: "service", - workers: "service", - hooks: "service", - context: "service", - parser: "service", - report: "service", - recurrence: "service", - model: "data", - models: "data", - domain: "data", - repository: "data", - storage: "data", - db: "data", - migrations: "data", - types: "data", - config: "config", - lib: "util", - pkg: "util", - util: "util", - utils: "util", - test: "test", - testutil: "test", -}; - -export class NavTree { - private el: HTMLElement; - private groups: TreeGroup[]; - private selectedPath: string | null = null; - private onSelect: (filePath: string) => void; - - constructor(options: NavTreeOptions) { - this.onSelect = options.onSelect; - this.el = document.createElement("div"); - this.el.className = "nav-tree"; - - // Group summaries by module directory - const moduleGroups = new Map(); - for (const s of options.summaries) { - const moduleId = this.getModuleDir(s.file_path); - if (!moduleGroups.has(moduleId)) moduleGroups.set(moduleId, []); - moduleGroups.get(moduleId)!.push(s); - } - - this.groups = Array.from(moduleGroups.entries()) - .map(([moduleId, items]) => { - const dirName = moduleId.split("/").pop() || moduleId; - const layer = LAYER_HINTS[dirName.toLowerCase()] || "unknown"; - return { - name: this.getModuleLabel(moduleId), - layer, - items: items.sort((a, b) => a.file_path.localeCompare(b.file_path)), - collapsed: true, - }; - }) - .sort((a, b) => a.name.localeCompare(b.name)); - - this.render(); - } - - private getModuleDir(filePath: string): string { - const parts = filePath.split("/"); - if (parts.length <= 1) return parts[0]; - const dirParts = parts.slice(0, -1); - if (dirParts.length >= 3 && (dirParts[1] === "internal" || dirParts[1] === "src")) { - return dirParts.slice(0, 3).join("/"); - } - if (dirParts.length >= 3) return dirParts.slice(0, 3).join("/"); - if (dirParts.length >= 2) return dirParts.join("/"); - return dirParts[0]; - } - - private getModuleLabel(moduleId: string): string { - const parts = moduleId.split("/"); - if (parts.length >= 3 && (parts[1] === "internal" || parts[1] === "src")) { - return parts.slice(2).join("/"); - } - if (parts.length >= 2) return parts[parts.length - 1]; - return moduleId; - } - - private render(): void { - this.el.innerHTML = ""; - - for (const group of this.groups) { - const groupEl = document.createElement("div"); - groupEl.className = "nav-tree__group"; - - // Group header - const header = document.createElement("div"); - header.className = "nav-tree__group-header"; - - const chevron = document.createElement("span"); - chevron.className = "nav-tree__chevron"; - if (group.collapsed) chevron.classList.add("nav-tree__chevron--collapsed"); - chevron.textContent = "\u25BE"; // down triangle - header.appendChild(chevron); - - const dot = document.createElement("span"); - dot.className = "nav-tree__dot"; - dot.style.backgroundColor = LAYER_COLORS[group.layer] || LAYER_COLORS.unknown; - header.appendChild(dot); - - const label = document.createElement("span"); - label.textContent = `${group.name} (${group.items.length})`; - header.appendChild(label); - - header.addEventListener("click", () => { - group.collapsed = !group.collapsed; - this.render(); - }); - - groupEl.appendChild(header); - - // Items - if (!group.collapsed) { - for (const item of group.items) { - const itemEl = document.createElement("div"); - itemEl.className = "nav-tree__item"; - if (item.file_path === this.selectedPath) { - itemEl.classList.add("nav-tree__item--selected"); - } - - const itemDot = document.createElement("span"); - itemDot.className = "nav-tree__dot"; - itemDot.style.backgroundColor = LAYER_COLORS[group.layer] || LAYER_COLORS.unknown; - itemEl.appendChild(itemDot); - - const name = document.createElement("span"); - name.textContent = item.file_path.split("/").pop() || item.file_path; - name.title = item.file_path; - itemEl.appendChild(name); - - itemEl.addEventListener("click", () => { - this.select(item.file_path); - }); - - groupEl.appendChild(itemEl); - } - } - - this.el.appendChild(groupEl); - } - } - - select(filePath: string): void { - this.selectedPath = filePath; - this.render(); - this.onSelect(filePath); - } - - mount(parent: HTMLElement): void { - parent.appendChild(this.el); - } -} diff --git a/report/src/components/SearchBar.ts b/report/src/components/SearchBar.ts deleted file mode 100644 index be26aa3..0000000 --- a/report/src/components/SearchBar.ts +++ /dev/null @@ -1,172 +0,0 @@ -import Fuse from "fuse.js"; -import type { ModuleSummary, SearchEntry } from "../types"; - -export interface SearchBarOptions { - summaries: ModuleSummary[]; - onSelect: (filePath: string) => void; -} - -export class SearchBar { - private el: HTMLElement; - private input: HTMLInputElement; - private resultsEl: HTMLElement; - private fuse: Fuse; - private entries: SearchEntry[]; - private activeIndex = -1; - private results: SearchEntry[] = []; - private onSelect: (filePath: string) => void; - - constructor(options: SearchBarOptions) { - this.onSelect = options.onSelect; - - // Build search index - this.entries = options.summaries.map((s) => ({ - id: s.file_path, - label: s.file_path.split("/").pop() || s.file_path, - file_path: s.file_path, - kind: s.layer, - keywords: [ - s.summary, - ...(s.responsibilities || []), - ...(s.exports || []).map((e) => e.name), - ...(s.patterns || []), - ].join(" "), - })); - - this.fuse = new Fuse(this.entries, { - keys: [ - { name: "label", weight: 2 }, - { name: "file_path", weight: 1.5 }, - { name: "keywords", weight: 1 }, - ], - threshold: 0.4, - includeScore: true, - }); - - // Build DOM - this.el = document.createElement("div"); - this.el.className = "search-bar"; - - const icon = document.createElement("span"); - icon.className = "search-bar__icon"; - icon.textContent = "\u2315"; // search icon - this.el.appendChild(icon); - - this.input = document.createElement("input"); - this.input.className = "search-bar__input"; - this.input.type = "text"; - this.input.placeholder = "Search files..."; - this.input.addEventListener("input", () => this.handleInput()); - this.input.addEventListener("keydown", (e) => this.handleKeydown(e)); - this.input.addEventListener("focus", () => this.handleInput()); - this.input.addEventListener("blur", () => { - // Delay to allow click on result - setTimeout(() => this.hideResults(), 150); - }); - this.el.appendChild(this.input); - - this.resultsEl = document.createElement("div"); - this.resultsEl.className = "search-bar__results"; - this.resultsEl.style.display = "none"; - this.el.appendChild(this.resultsEl); - } - - private handleInput(): void { - const query = this.input.value.trim(); - if (!query) { - this.hideResults(); - return; - } - - const fuseResults = this.fuse.search(query, { limit: 10 }); - this.results = fuseResults.map((r) => r.item); - this.activeIndex = -1; - this.renderResults(); - } - - private handleKeydown(e: KeyboardEvent): void { - if (e.key === "ArrowDown") { - e.preventDefault(); - this.activeIndex = Math.min(this.activeIndex + 1, this.results.length - 1); - this.renderResults(); - } else if (e.key === "ArrowUp") { - e.preventDefault(); - this.activeIndex = Math.max(this.activeIndex - 1, 0); - this.renderResults(); - } else if (e.key === "Enter" && this.activeIndex >= 0) { - e.preventDefault(); - const entry = this.results[this.activeIndex]; - if (entry) { - this.onSelect(entry.file_path); - this.input.value = ""; - this.hideResults(); - } - } else if (e.key === "Escape") { - this.input.value = ""; - this.hideResults(); - this.input.blur(); - } - } - - private renderResults(): void { - this.resultsEl.innerHTML = ""; - if (this.results.length === 0) { - this.resultsEl.style.display = "none"; - return; - } - this.resultsEl.style.display = "block"; - - this.results.forEach((entry, i) => { - const item = document.createElement("div"); - item.className = "search-bar__result"; - if (i === this.activeIndex) { - item.classList.add("search-bar__result--active"); - } - - const dot = document.createElement("span"); - dot.className = "nav-tree__dot"; - dot.style.backgroundColor = layerColor(entry.kind); - item.appendChild(dot); - - const label = document.createElement("span"); - label.textContent = entry.label; - item.appendChild(label); - - const path = document.createElement("span"); - path.className = "search-bar__result-path"; - path.textContent = entry.file_path; - item.appendChild(path); - - item.addEventListener("click", () => { - this.onSelect(entry.file_path); - this.input.value = ""; - this.hideResults(); - }); - - this.resultsEl.appendChild(item); - }); - } - - private hideResults(): void { - this.resultsEl.style.display = "none"; - this.results = []; - this.activeIndex = -1; - } - - getElement(): HTMLElement { - return this.el; - } -} - -function layerColor(layer: string): string { - const colors: Record = { - api: "#7c3aed", - service: "#06b6d4", - data: "#f59e0b", - util: "#64748b", - test: "#10b981", - config: "#ec4899", - unknown: "#94a3b8", - }; - return colors[layer] || colors.unknown; -} diff --git a/report/src/components/StatusBar.ts b/report/src/components/StatusBar.ts deleted file mode 100644 index 06819b5..0000000 --- a/report/src/components/StatusBar.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { PipelineStats, RepoGraph } from "../types"; - -export interface StatusBarOptions { - stats: PipelineStats; - graph: RepoGraph; -} - -export class StatusBar { - private el: HTMLElement; - - constructor(options: StatusBarOptions) { - const { stats, graph } = options; - this.el = document.createElement("div"); - this.el.className = "status-bar"; - - this.addItem(`${graph.nodes.length} nodes`); - this.addItem(`${graph.edges.length} edges`); - this.addItem(`${stats.total_modules} modules`); - this.addItem(`${stats.total_files} files`); - - const langs = (stats.languages || []) - .map((l) => `${l.language} ${l.percentage.toFixed(0)}%`) - .join(", "); - this.addItem(langs); - - // Spacer - const spacer = document.createElement("div"); - spacer.className = "status-bar__spacer"; - this.el.appendChild(spacer); - - this.addItem(`Model: ${stats.model_used}`); - this.addItem(`Cost: $${stats.total_cost.toFixed(2)}`); - - const duration = (stats.duration_ms / 1000).toFixed(1); - this.addItem(`${duration}s`); - } - - private addItem(text: string): void { - const item = document.createElement("span"); - item.className = "status-bar__item"; - item.textContent = text; - this.el.appendChild(item); - } - - mount(parent: HTMLElement): void { - parent.appendChild(this.el); - } -} diff --git a/report/src/components/Toolbar.ts b/report/src/components/Toolbar.ts deleted file mode 100644 index d170e51..0000000 --- a/report/src/components/Toolbar.ts +++ /dev/null @@ -1,82 +0,0 @@ -export type ViewName = "architecture"; - -export interface ToolbarOptions { - repoName: string; - onViewChange: (view: ViewName) => void; -} - -const TABS: { id: ViewName; label: string }[] = [ - { id: "architecture", label: "Architecture" }, -]; - -export class Toolbar { - private el: HTMLElement; - private activeView: ViewName = "architecture"; - private tabButtons: Map = new Map(); - private onViewChange: (view: ViewName) => void; - private mounted = false; - - constructor(options: ToolbarOptions) { - this.onViewChange = options.onViewChange; - this.el = document.createElement("div"); - this.el.className = "toolbar"; - - // Logo - const logo = document.createElement("div"); - logo.className = "toolbar__logo"; - logo.textContent = "repomap"; - this.el.appendChild(logo); - - // Repo name - const repoName = document.createElement("span"); - repoName.className = "toolbar__repo-name"; - repoName.textContent = options.repoName; - this.el.appendChild(repoName); - - // Tabs - const tabs = document.createElement("div"); - tabs.className = "toolbar__tabs"; - - for (const tab of TABS) { - const btn = document.createElement("button"); - btn.className = "toolbar__tab"; - btn.textContent = tab.label; - btn.addEventListener("click", () => this.setActive(tab.id)); - tabs.appendChild(btn); - this.tabButtons.set(tab.id, btn); - } - - this.el.appendChild(tabs); - - // Spacer - const spacer = document.createElement("div"); - spacer.className = "toolbar__spacer"; - this.el.appendChild(spacer); - - this.setActive("architecture"); - } - - setActive(view: ViewName): void { - this.activeView = view; - for (const [id, btn] of this.tabButtons) { - btn.classList.toggle("toolbar__tab--active", id === view); - } - if (this.mounted) { - this.onViewChange(view); - } - } - - getActiveView(): ViewName { - return this.activeView; - } - - /** Insert the search bar element into the toolbar (right side). */ - appendRight(element: HTMLElement): void { - this.el.appendChild(element); - } - - mount(parent: HTMLElement): void { - parent.appendChild(this.el); - this.mounted = true; - } -} diff --git a/report/src/main.ts b/report/src/main.ts deleted file mode 100644 index 258650e..0000000 --- a/report/src/main.ts +++ /dev/null @@ -1,167 +0,0 @@ -import "./styles/main.css"; -import type { RepoMapData, ModuleSummary } from "./types"; -import { Toolbar, type ViewName } from "./components/Toolbar"; -import { SearchBar } from "./components/SearchBar"; -import { NavTree } from "./components/NavTree"; -import { DetailPanel } from "./components/DetailPanel"; -import { StatusBar } from "./components/StatusBar"; -import { ArchitectureMap } from "./views/ArchitectureMap"; -import type { ModuleNode } from "./utils/moduleGrouper"; - -// Load data from API, embedded JSON, or fixture -async function loadData(): Promise { - // Try API endpoint first (local server mode) - try { - const resp = await fetch("/api/report"); - if (resp.ok) { - return await resp.json(); - } - } catch { - // Not running in server mode, fall through - } - - // Try embedded JSON (static HTML mode) - const scriptTag = document.getElementById("repomap-data"); - if (scriptTag && scriptTag.textContent && scriptTag.textContent.trim().length > 2) { - return JSON.parse(scriptTag.textContent); - } - - // Dev mode: load fixture - const fixture = await import("./fixtures/sample.json"); - return fixture.default as unknown as RepoMapData; -} - -class App { - private data!: RepoMapData; - private summaryMap: Map = new Map(); - - // Components - private toolbar!: Toolbar; - private searchBar!: SearchBar; - private navTree!: NavTree; - private detailPanel!: DetailPanel; - private statusBar!: StatusBar; - - // Views - private architectureMap: ArchitectureMap | null = null; - - private canvasEl!: HTMLElement; - private currentView: ViewName = "architecture"; - - async init(): Promise { - this.data = await loadData(); - - // Build lookup map - for (const s of this.data.summaries) { - this.summaryMap.set(s.file_path, s); - } - - const app = document.getElementById("app")!; - app.innerHTML = ""; - - // Toolbar - this.toolbar = new Toolbar({ - repoName: this.data.metadata.repo_name, - onViewChange: (view) => this.switchView(view), - }); - this.toolbar.mount(app); - - // Search bar (appended to toolbar) - this.searchBar = new SearchBar({ - summaries: this.data.summaries, - onSelect: (fp) => this.selectFile(fp), - }); - this.toolbar.appendRight(this.searchBar.getElement()); - - // Nav tree - this.navTree = new NavTree({ - summaries: this.data.summaries, - onSelect: (fp) => this.selectFile(fp), - }); - this.navTree.mount(app); - - // Canvas (views mount here) - this.canvasEl = document.createElement("div"); - this.canvasEl.className = "canvas"; - app.appendChild(this.canvasEl); - - // Detail panel - this.detailPanel = new DetailPanel({ - onNavigate: (fp) => this.selectFile(fp), - }); - this.detailPanel.mount(app); - - // Status bar - this.statusBar = new StatusBar({ - stats: this.data.stats, - graph: this.data.graph, - }); - this.statusBar.mount(app); - - // Initial view - this.switchView("architecture"); - } - - private switchView(view: ViewName): void { - // Unmount current view - this.unmountCurrentView(); - this.currentView = view; - - switch (view) { - case "architecture": - this.architectureMap = new ArchitectureMap({ - graph: this.data.graph, - summaries: this.data.summaries, - onNodeSelect: (id, moduleNode) => { - if (id && moduleNode) { - this.selectModuleNode(moduleNode); - } else { - this.detailPanel.showEmpty(); - } - }, - }); - this.architectureMap.mount(this.canvasEl); - break; - } - } - - private unmountCurrentView(): void { - if (this.architectureMap) { - this.architectureMap.unmount(); - this.architectureMap = null; - } - } - - private selectFile(filePath: string): void { - const summary = this.summaryMap.get(filePath); - if (!summary) return; - - this.navTree.select(filePath); - this.detailPanel.show(summary); - - if (this.architectureMap) { - this.architectureMap.highlightNode(filePath); - } - } - - private selectModuleNode(moduleNode: ModuleNode): void { - this.detailPanel.showModule(moduleNode, this.summaryMap); - - if (this.architectureMap) { - this.architectureMap.highlightNode(moduleNode.id); - } - } -} - -// Boot -const app = new App(); -app.init().catch((err) => { - console.error("Failed to initialize Repomap report:", err); - const appEl = document.getElementById("app"); - if (appEl) { - appEl.innerHTML = `
-

Failed to load report

-
${String(err)}
-
`; - } -}); diff --git a/report/src/main.tsx b/report/src/main.tsx new file mode 100644 index 0000000..6832d14 --- /dev/null +++ b/report/src/main.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { createRoot } from "react-dom/client"; +import "./styles/main.css"; +import { App } from "./App"; +import { ReportDataContext, useLoadReportData } from "./hooks/useReportData"; + +function Root() { + const reportData = useLoadReportData(); + return ( + + + + ); +} + +const container = document.getElementById("app"); +if (container) { + createRoot(container).render( + + + , + ); +} diff --git a/report/src/styles/main.css b/report/src/styles/main.css index d0e0461..7d9b3f1 100644 --- a/report/src/styles/main.css +++ b/report/src/styles/main.css @@ -34,20 +34,12 @@ body { } #app { - display: grid; - grid-template-rows: auto 1fr auto; - grid-template-columns: 260px 1fr 340px; - grid-template-areas: - "toolbar toolbar toolbar" - "nav canvas detail" - "status status status"; height: 100vh; width: 100vw; } /* Toolbar */ .toolbar { - grid-area: toolbar; display: flex; align-items: center; gap: 16px; @@ -110,8 +102,8 @@ body { /* Nav Tree */ .nav-tree { - grid-area: nav; background: var(--surface); + height: 100%; border-right: 1px solid var(--border); overflow-y: auto; padding: 8px 0; @@ -180,8 +172,9 @@ body { /* Canvas */ .canvas { - grid-area: canvas; position: relative; + width: 100%; + height: 100%; overflow: hidden; background: var(--bg); } @@ -218,8 +211,8 @@ body { /* Detail Panel */ .detail-panel { - grid-area: detail; background: var(--surface); + height: 100%; border-left: 1px solid var(--border); overflow-y: auto; padding: 16px; @@ -408,7 +401,6 @@ body { /* Status Bar */ .status-bar { - grid-area: status; display: flex; align-items: center; gap: 24px; diff --git a/report/src/views/ArchitectureMap.ts b/report/src/views/ArchitectureMap.ts deleted file mode 100644 index 3705d4d..0000000 --- a/report/src/views/ArchitectureMap.ts +++ /dev/null @@ -1,323 +0,0 @@ -import cytoscape from "cytoscape"; -import cytoscapeDagre from "cytoscape-dagre"; -import type { RepoGraph, ModuleSummary, LayerType } from "../types"; -import { groupIntoModules, type ModuleNode, type ModuleGraph } from "../utils/moduleGrouper"; - -// Register dagre layout -cytoscapeDagre(cytoscape); - -const LAYER_COLORS: Record = { - api: { bg: "#1e1040", border: "#7c3aed", dot: "#a78bfa", text: "#c4b5fd", badge: "#7c3aed" }, - service: { bg: "#0e2a33", border: "#0891b2", dot: "#22d3ee", text: "#67e8f9", badge: "#0891b2" }, - data: { bg: "#2a1a00", border: "#d97706", dot: "#fbbf24", text: "#fcd34d", badge: "#d97706" }, - config: { bg: "#1a0a2e", border: "#db2777", dot: "#f472b6", text: "#fbcfe8", badge: "#db2777" }, - util: { bg: "#1a1a24", border: "#64748b", dot: "#94a3b8", text: "#cbd5e1", badge: "#64748b" }, - test: { bg: "#0a2a1a", border: "#059669", dot: "#34d399", text: "#6ee7b7", badge: "#059669" }, - unknown: { bg: "#1a1a24", border: "#475569", dot: "#94a3b8", text: "#cbd5e1", badge: "#475569" }, -}; - -function getColors(layer: string) { - return LAYER_COLORS[layer] || LAYER_COLORS.unknown; -} - -export interface ArchitectureMapOptions { - graph: RepoGraph; - summaries: ModuleSummary[]; - onNodeSelect: (moduleId: string | null, moduleNode?: ModuleNode) => void; -} - -export class ArchitectureMap { - private cy: cytoscape.Core | null = null; - private container: HTMLElement; - private overlay: HTMLElement; - private onNodeSelect: (moduleId: string | null, moduleNode?: ModuleNode) => void; - private graph: RepoGraph; - private summaries: ModuleSummary[]; - private moduleGraph: ModuleGraph; - private nodeMap: Map = new Map(); - - constructor(options: ArchitectureMapOptions) { - this.graph = options.graph; - this.summaries = options.summaries; - this.onNodeSelect = options.onNodeSelect; - - this.container = document.createElement("div"); - this.container.className = "canvas__cytoscape"; - - this.overlay = document.createElement("div"); - this.overlay.className = "canvas__node-overlay"; - this.overlay.style.cssText = "position:absolute;inset:0;pointer-events:none;overflow:hidden;"; - - this.moduleGraph = groupIntoModules(this.summaries, this.graph); - for (const n of this.moduleGraph.nodes) { - this.nodeMap.set(n.id, n); - } - } - - mount(parent: HTMLElement): void { - parent.appendChild(this.container); - parent.appendChild(this.overlay); - this.initCytoscape(); - } - - private initCytoscape(): void { - const elements: cytoscape.ElementDefinition[] = []; - - for (const node of this.moduleGraph.nodes) { - const colors = getColors(node.layer); - elements.push({ - data: { - id: node.id, - label: node.label, - sublabel: `${node.fileCount} file${node.fileCount !== 1 ? "s" : ""}`, - layerLabel: node.layer, - layer: node.layer, - bgColor: colors.bg, - borderColor: colors.border, - dotColor: colors.dot, - textColor: colors.text, - }, - }); - } - - for (const edge of this.moduleGraph.edges) { - elements.push({ - data: { - source: edge.source, - target: edge.target, - weight: edge.weight, - }, - }); - } - - this.cy = cytoscape({ - container: this.container, - elements, - style: [ - { - selector: "node", - style: { - shape: "roundrectangle", - width: 160, - height: 48, - "background-color": "data(bgColor)", - "border-width": 1.5, - "border-color": "data(borderColor)", - "border-opacity": 0.6, - label: "data(label)", - "font-size": "11px", - "font-family": "'JetBrains Mono', 'Fira Code', monospace", - "font-weight": "600" as unknown as number, - color: "data(textColor)", - "text-valign": "center", - "text-halign": "center", - "text-margin-y": -6, - "text-outline-width": 0, - }, - }, - { - selector: "node:selected", - style: { - "border-width": 2, - "border-color": "#ffffff", - "border-opacity": 1, - }, - }, - { - selector: "node:active", - style: { - "overlay-opacity": 0, - }, - }, - { - selector: "edge", - style: { - width: 1.5, - "line-color": "#1e293b", - "target-arrow-color": "#334155", - "target-arrow-shape": "triangle", - "arrow-scale": 0.8, - "curve-style": "bezier", - opacity: 0.5, - }, - }, - { - selector: "edge:selected", - style: { - "line-color": "#64748b", - "target-arrow-color": "#64748b", - opacity: 1, - }, - }, - ], - layout: { - name: "dagre", - rankDir: "TB", - spacingFactor: 1.6, - nodeSep: 60, - rankSep: 100, - animate: false, - } as cytoscape.LayoutOptions, - minZoom: 0.3, - maxZoom: 2.5, - wheelSensitivity: 0.3, - }); - - // Render HTML card overlays after layout - this.renderCardOverlays(); - - // Update overlay positions on pan/zoom - this.cy.on("viewport", () => this.updateOverlayPositions()); - this.cy.on("position", () => this.updateOverlayPositions()); - - // Click handler - this.cy.on("tap", "node", (evt) => { - const nodeId = evt.target.id(); - const moduleNode = this.nodeMap.get(nodeId); - this.onNodeSelect(nodeId, moduleNode); - }); - - this.cy.on("tap", (evt) => { - if (evt.target === this.cy) { - this.onNodeSelect(null); - } - }); - } - - private renderCardOverlays(): void { - if (!this.cy) return; - this.overlay.innerHTML = ""; - - this.cy.nodes().forEach((cyNode) => { - const id = cyNode.id(); - const moduleNode = this.nodeMap.get(id); - if (!moduleNode) return; - - const colors = getColors(moduleNode.layer); - const card = document.createElement("div"); - card.className = "module-card"; - card.dataset.nodeId = id; - card.style.cssText = ` - position: absolute; - pointer-events: auto; - cursor: pointer; - min-width: 140px; - padding: 8px 14px; - border-radius: 10px; - background: ${colors.bg}; - border: 1.5px solid ${colors.border}88; - transition: border-color 0.15s, box-shadow 0.15s; - `; - - // Row 1: dot + label - const row1 = document.createElement("div"); - row1.style.cssText = "display:flex;align-items:center;gap:7px;margin-bottom:3px;"; - - const dot = document.createElement("div"); - dot.style.cssText = `width:7px;height:7px;border-radius:50%;background:${colors.dot};flex-shrink:0;`; - row1.appendChild(dot); - - const label = document.createElement("span"); - label.style.cssText = `color:${colors.text};font-size:11px;font-family:'JetBrains Mono','Fira Code',monospace;font-weight:600;letter-spacing:0.02em;`; - label.textContent = moduleNode.label; - row1.appendChild(label); - card.appendChild(row1); - - // Row 2: file count + layer badge - const row2 = document.createElement("div"); - row2.style.cssText = "display:flex;gap:6px;align-items:center;"; - - const fileCount = document.createElement("span"); - fileCount.style.cssText = "color:#475569;font-size:10px;font-family:monospace;"; - fileCount.textContent = `${moduleNode.fileCount} file${moduleNode.fileCount !== 1 ? "s" : ""}`; - row2.appendChild(fileCount); - - const badge = document.createElement("span"); - badge.style.cssText = `color:${colors.border};font-size:9px;font-family:monospace;background:${colors.border}18;padding:1px 5px;border-radius:3px;`; - badge.textContent = moduleNode.layer; - row2.appendChild(badge); - card.appendChild(row2); - - card.addEventListener("click", (e) => { - e.stopPropagation(); - this.onNodeSelect(id, moduleNode); - }); - - card.addEventListener("mouseenter", () => { - card.style.borderColor = colors.border; - card.style.boxShadow = `0 0 0 3px ${colors.border}44, 0 8px 32px #00000080`; - }); - card.addEventListener("mouseleave", () => { - card.style.borderColor = `${colors.border}88`; - card.style.boxShadow = "none"; - }); - - this.overlay.appendChild(card); - }); - - this.updateOverlayPositions(); - } - - private updateOverlayPositions(): void { - if (!this.cy) return; - - const pan = this.cy.pan(); - const zoom = this.cy.zoom(); - - this.overlay.querySelectorAll(".module-card").forEach((card) => { - const nodeId = card.dataset.nodeId; - if (!nodeId) return; - - const cyNode = this.cy!.getElementById(nodeId); - if (cyNode.length === 0) return; - - const pos = cyNode.position(); - const x = pos.x * zoom + pan.x; - const y = pos.y * zoom + pan.y; - - // Center the card on the node position - const w = card.offsetWidth; - const h = card.offsetHeight; - card.style.left = `${x - w / 2}px`; - card.style.top = `${y - h / 2}px`; - card.style.transform = `scale(${Math.min(1, zoom)})`; - card.style.transformOrigin = "center center"; - }); - } - - highlightNode(nodeId: string): void { - if (!this.cy) return; - this.cy.elements().unselect(); - if (nodeId) { - const node = this.cy.getElementById(nodeId); - if (node.length > 0) { - node.select(); - this.cy.animate({ - center: { eles: node }, - duration: 300, - }); - } - } - } - - getModuleGraph(): ModuleGraph { - return this.moduleGraph; - } - - destroy(): void { - if (this.cy) { - this.cy.destroy(); - this.cy = null; - } - } - - unmount(): void { - this.destroy(); - this.overlay.remove(); - this.container.remove(); - } - - getContainer(): HTMLElement { - return this.container; - } -} diff --git a/report/src/views/FlowExplorer.ts b/report/src/views/FlowExplorer.ts deleted file mode 100644 index c8afed9..0000000 --- a/report/src/views/FlowExplorer.ts +++ /dev/null @@ -1,151 +0,0 @@ -import type { CriticalPath } from "../types"; - -export interface FlowExplorerOptions { - criticalPaths: CriticalPath[]; - onNodeSelect: (nodeId: string) => void; -} - -export class FlowExplorer { - private container: HTMLElement; - private paths: CriticalPath[]; - private onNodeSelect: (nodeId: string) => void; - - constructor(options: FlowExplorerOptions) { - this.paths = options.criticalPaths; - this.onNodeSelect = options.onNodeSelect; - this.container = document.createElement("div"); - this.container.className = "canvas__mermaid"; - } - - async mount(parent: HTMLElement): Promise { - parent.appendChild(this.container); - await this.renderDiagrams(); - } - - private async renderDiagrams(): Promise { - if (this.paths.length === 0) { - const empty = document.createElement("div"); - empty.className = "canvas__empty"; - empty.textContent = "No critical paths available. Run analysis with architecture synthesis enabled."; - this.container.appendChild(empty); - return; - } - - const mermaid = (await import("mermaid")).default; - mermaid.initialize({ - startOnLoad: false, - theme: "dark", - themeVariables: { - primaryColor: "#7c3aed", - primaryTextColor: "#f1f5f9", - primaryBorderColor: "#1e1e2e", - lineColor: "#64748b", - secondaryColor: "#111118", - tertiaryColor: "#1a1a24", - background: "#0a0a0f", - mainBkg: "#111118", - nodeBorder: "#1e1e2e", - clusterBkg: "#111118", - titleColor: "#f1f5f9", - edgeLabelBackground: "#111118", - }, - sequence: { - actorMargin: 50, - noteMargin: 10, - messageMargin: 30, - }, - }); - - for (let i = 0; i < this.paths.length; i++) { - const path = this.paths[i]; - const wrapper = document.createElement("div"); - wrapper.className = "mermaid-diagram"; - - const title = document.createElement("div"); - title.className = "mermaid-diagram__title"; - title.textContent = path.name; - wrapper.appendChild(title); - - const desc = document.createElement("div"); - desc.className = "mermaid-diagram__desc"; - desc.textContent = path.description; - wrapper.appendChild(desc); - - const diagramDef = this.buildSequenceDiagram(path); - const diagramEl = document.createElement("div"); - diagramEl.className = "mermaid-render"; - - try { - const { svg } = await mermaid.render(`mermaid-${i}`, diagramDef); - diagramEl.innerHTML = svg; - } catch { - diagramEl.textContent = "Failed to render diagram"; - diagramEl.style.color = "#64748b"; - } - - wrapper.appendChild(diagramEl); - - // Add clickable step list below diagram - const stepList = document.createElement("div"); - stepList.style.marginTop = "12px"; - for (const step of path.steps) { - const stepEl = document.createElement("div"); - stepEl.className = "detail-panel__dep"; - stepEl.textContent = `${step.module} - ${step.action}: ${step.description}`; - stepEl.addEventListener("click", () => { - this.onNodeSelect(step.module); - }); - stepList.appendChild(stepEl); - } - wrapper.appendChild(stepList); - - this.container.appendChild(wrapper); - } - } - - private buildSequenceDiagram(path: CriticalPath): string { - const lines: string[] = ["sequenceDiagram"]; - - // Collect unique participants in order - const seen = new Set(); - const participants: string[] = []; - for (const step of path.steps) { - const alias = this.sanitizeAlias(step.module); - if (!seen.has(alias)) { - seen.add(alias); - participants.push(alias); - const shortName = step.module.split("/").pop() || step.module; - lines.push(` participant ${alias} as ${shortName}`); - } - } - - // Add arrows between consecutive steps - for (let i = 0; i < path.steps.length - 1; i++) { - const from = this.sanitizeAlias(path.steps[i].module); - const to = this.sanitizeAlias(path.steps[i + 1].module); - const action = path.steps[i].action.replace(/"/g, "'"); - lines.push(` ${from}->>+${to}: ${action}`); - } - - // Add return arrows - for (let i = path.steps.length - 2; i >= 0; i--) { - const from = this.sanitizeAlias(path.steps[i + 1].module); - const to = this.sanitizeAlias(path.steps[i].module); - lines.push(` ${from}-->>-${to}: response`); - } - - return lines.join("\n"); - } - - private sanitizeAlias(module: string): string { - return module.replace(/[^a-zA-Z0-9]/g, "_"); - } - - unmount(): void { - this.container.remove(); - } - - getContainer(): HTMLElement { - return this.container; - } -} diff --git a/report/src/views/ModelCanvas.ts b/report/src/views/ModelCanvas.ts deleted file mode 100644 index 7e4585b..0000000 --- a/report/src/views/ModelCanvas.ts +++ /dev/null @@ -1,258 +0,0 @@ -import * as d3 from "d3"; -import type { PipelineStats, RepoGraph, ModuleSummary, LayerType } from "../types"; - -const LAYER_COLORS: Record = { - api: "#7c3aed", - service: "#06b6d4", - data: "#f59e0b", - util: "#64748b", - test: "#10b981", - config: "#ec4899", - unknown: "#94a3b8", -}; - -export interface ModelCanvasOptions { - stats: PipelineStats; - graph: RepoGraph; - summaries: ModuleSummary[]; - onNodeSelect: (nodeId: string) => void; -} - -export class ModelCanvas { - private container: HTMLElement; - private stats: PipelineStats; - private graph: RepoGraph; - private summaries: ModuleSummary[]; - private onNodeSelect: (nodeId: string) => void; - - constructor(options: ModelCanvasOptions) { - this.stats = options.stats; - this.graph = options.graph; - this.summaries = options.summaries; - this.onNodeSelect = options.onNodeSelect; - this.container = document.createElement("div"); - this.container.className = "canvas__d3"; - this.container.style.overflow = "auto"; - } - - mount(parent: HTMLElement): void { - parent.appendChild(this.container); - this.renderLanguageBreakdown(); - this.renderFilesByLayer(); - } - - private renderLanguageBreakdown(): void { - const title = document.createElement("div"); - title.className = "model-canvas__title"; - title.textContent = "Language Breakdown"; - this.container.appendChild(title); - - const subtitle = document.createElement("div"); - subtitle.className = "model-canvas__subtitle"; - subtitle.textContent = `${this.stats.total_files} files across ${this.stats.languages.length} languages`; - this.container.appendChild(subtitle); - - const chartContainer = document.createElement("div"); - chartContainer.className = "model-canvas__chart"; - this.container.appendChild(chartContainer); - - const width = 600; - const height = 300; - const margin = { top: 20, right: 30, bottom: 40, left: 120 }; - - const svg = d3 - .select(chartContainer) - .append("svg") - .attr("width", width) - .attr("height", height) - .attr("viewBox", `0 0 ${width} ${height}`); - - const data = this.stats.languages.sort((a, b) => b.percentage - a.percentage); - - const y = d3 - .scaleBand() - .domain(data.map((d) => d.language)) - .range([margin.top, height - margin.bottom]) - .padding(0.3); - - const x = d3 - .scaleLinear() - .domain([0, 100]) - .range([margin.left, width - margin.right]); - - // Color scale - const colorScale = d3 - .scaleOrdinal() - .domain(data.map((d) => d.language)) - .range(["#7c3aed", "#06b6d4", "#f59e0b", "#10b981", "#ec4899", "#64748b"]); - - // Bars - svg - .selectAll("rect.bar") - .data(data) - .join("rect") - .attr("class", "bar") - .attr("x", margin.left) - .attr("y", (d) => y(d.language)!) - .attr("width", (d) => x(d.percentage) - margin.left) - .attr("height", y.bandwidth()) - .attr("rx", 4) - .attr("fill", (d) => colorScale(d.language)) - .attr("opacity", 0.85); - - // Labels - svg - .selectAll("text.label") - .data(data) - .join("text") - .attr("class", "label") - .attr("x", margin.left - 8) - .attr("y", (d) => y(d.language)! + y.bandwidth() / 2) - .attr("dy", "0.35em") - .attr("text-anchor", "end") - .attr("fill", "#f1f5f9") - .attr("font-size", "12px") - .text((d) => d.language); - - // Percentage labels - svg - .selectAll("text.pct") - .data(data) - .join("text") - .attr("class", "pct") - .attr("x", (d) => x(d.percentage) + 6) - .attr("y", (d) => y(d.language)! + y.bandwidth() / 2) - .attr("dy", "0.35em") - .attr("fill", "#64748b") - .attr("font-size", "11px") - .text((d) => `${d.percentage.toFixed(1)}% (${d.file_count})`); - } - - private renderFilesByLayer(): void { - const title = document.createElement("div"); - title.className = "model-canvas__title"; - title.style.marginTop = "32px"; - title.textContent = "Modules by Layer"; - this.container.appendChild(title); - - const subtitle = document.createElement("div"); - subtitle.className = "model-canvas__subtitle"; - subtitle.textContent = "Size represents token count"; - this.container.appendChild(subtitle); - - const chartContainer = document.createElement("div"); - chartContainer.className = "model-canvas__chart"; - this.container.appendChild(chartContainer); - - const width = 700; - const height = 400; - - // Build treemap data - interface TreeNode { - name: string; - children?: TreeNode[]; - value?: number; - layer?: LayerType; - filePath?: string; - } - - const layerGroups = new Map(); - for (const s of this.summaries) { - const layer = s.layer || "unknown"; - if (!layerGroups.has(layer)) layerGroups.set(layer, []); - layerGroups.get(layer)!.push({ - name: s.file_path.split("/").pop() || s.file_path, - value: s.token_count, - layer: s.layer, - filePath: s.file_path, - }); - } - - const rootData: TreeNode = { - name: "root", - children: Array.from(layerGroups.entries()).map(([layer, children]) => ({ - name: layer, - children, - })), - }; - - const root = d3 - .hierarchy(rootData) - .sum((d) => (d as TreeNode).value || 0) - .sort((a, b) => (b.value || 0) - (a.value || 0)); - - d3.treemap().size([width, height]).padding(3).round(true)(root); - - const svg = d3 - .select(chartContainer) - .append("svg") - .attr("width", width) - .attr("height", height) - .attr("viewBox", `0 0 ${width} ${height}`); - - const leaves = root.leaves(); - - const cells = svg - .selectAll("g") - .data(leaves) - .join("g") - .attr("transform", (d) => { - const td = d as d3.HierarchyRectangularNode; - return `translate(${td.x0},${td.y0})`; - }) - .style("cursor", "pointer") - .on("click", (_event, d) => { - const filePath = (d.data as TreeNode).filePath; - if (filePath) { - this.onNodeSelect(filePath); - } - }); - - cells - .append("rect") - .attr("width", (d) => { - const td = d as d3.HierarchyRectangularNode; - return Math.max(0, td.x1 - td.x0); - }) - .attr("height", (d) => { - const td = d as d3.HierarchyRectangularNode; - return Math.max(0, td.y1 - td.y0); - }) - .attr("rx", 3) - .attr("fill", (d) => { - const layer = (d.data as TreeNode).layer || "unknown"; - return LAYER_COLORS[layer] || LAYER_COLORS.unknown; - }) - .attr("opacity", 0.7) - .on("mouseover", function () { - d3.select(this).attr("opacity", 1); - }) - .on("mouseout", function () { - d3.select(this).attr("opacity", 0.7); - }); - - cells - .append("text") - .attr("x", 4) - .attr("y", 14) - .attr("fill", "#f1f5f9") - .attr("font-size", "10px") - .attr("font-weight", "500") - .text((d) => { - const td = d as d3.HierarchyRectangularNode; - const w = td.x1 - td.x0; - const name = (d.data as TreeNode).name; - if (w < 40) return ""; - if (w < 80) return name.substring(0, 5) + "..."; - return name; - }); - } - - unmount(): void { - this.container.remove(); - } - - getContainer(): HTMLElement { - return this.container; - } -}