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/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/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.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/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.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/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.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/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.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/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} +
+ ); +} 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/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/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", +}; 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/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 +
+
+
+ ); +} 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; - } -} 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", + }, + }, });