diff --git a/electron.vite.config.ts b/electron.vite.config.ts index 4660798..29f24de 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -29,6 +29,8 @@ export default defineConfig({ allow: [resolve('.')] } }, + // electron-vite leaves the renderer unminified: 3.9 MB vs 1.8 MB, ~60 ms later first paint. + build: { minify: true }, plugins: [react()] } }); diff --git a/package-lock.json b/package-lock.json index f21ac9b..8316b7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "hasInstallScript": true, "license": "SEE LICENSE IN LICENSE", - "dependencies": { + "devDependencies": { "@codemirror/autocomplete": "^6.20.3", "@codemirror/language": "^6.12.4", "@codemirror/state": "^6.7.4", @@ -25,26 +25,20 @@ "@mui/icons-material": "^9.4.0", "@mui/material": "^9.2.0", "@mui/x-tree-view": "^9.15.0", - "@northern.tech/themes": "^0.17.0", - "@reduxjs/toolkit": "^2.12.0", - "@uiw/react-codemirror": "^4.25.11", - "@xyflow/react": "^12.12.0", - "react": "^19.3.0", - "react-dom": "^19.3.0", - "react-redux": "^9.3.0", - "zod": "^4.6.5" - }, - "devDependencies": { "@northern.tech/eslint-config": "^0.7.2", "@northern.tech/prettier-config": "^0.2.1", + "@northern.tech/themes": "^0.17.0", "@playwright/test": "^1.63.0", + "@reduxjs/toolkit": "^2.12.0", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.3", "@types/node": "^26.6.4", "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", + "@uiw/react-codemirror": "^4.25.11", "@vitejs/plugin-react": "^5.2.0", "@vitest/coverage-v8": "^5.0.3", + "@xyflow/react": "^12.12.0", "electron": "^44.5.1", "electron-builder": "^26.17.0", "electron-vite": "^5.0.0", @@ -52,9 +46,13 @@ "globals": "^17.13.0", "jsdom": "^30.1.2", "prettier": "^3.9.9", + "react": "^19.3.0", + "react-dom": "^19.3.0", + "react-redux": "^9.3.0", "typescript": "^6.0.3", "vite": "^7.3.7", - "vitest": "^5.0.1" + "vitest": "^5.0.1", + "zod": "^4.6.5" }, "engines": { "node": ">=22.12.0" @@ -124,6 +122,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", @@ -196,6 +195,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.29.7", @@ -239,6 +239,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -248,6 +249,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.29.7", @@ -289,6 +291,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -298,6 +301,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -331,6 +335,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.7" @@ -394,6 +399,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -403,6 +409,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", @@ -417,6 +424,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", @@ -435,6 +443,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -448,6 +457,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/@base-ui/utils/-/utils-0.4.0.tgz", "integrity": "sha512-bO9fz25kKtPf+aZVyfQrC0PDmJdmVni31W2hCS5/Owb+inwdIL3XU26pCPRPlt4LSxZrBgLwubXQXQlKaFEZzw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.7", @@ -554,6 +564,7 @@ "version": "6.20.3", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.3.tgz", "integrity": "sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==", + "dev": true, "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", @@ -566,6 +577,7 @@ "version": "6.11.1", "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.11.1.tgz", "integrity": "sha512-O/4hG3SC1YwcmQ0d2UVNDs+AsaNWd1iHVxbTeEBuqH+6bExAiPK3iS/BvpY6rZGURALv4ZD3sIgcCmRvw3ehBg==", + "dev": true, "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", @@ -578,6 +590,7 @@ "version": "6.12.4", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz", "integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==", + "dev": true, "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", @@ -592,6 +605,7 @@ "version": "6.9.7", "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.7.tgz", "integrity": "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==", + "dev": true, "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", @@ -603,6 +617,7 @@ "version": "6.7.2", "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.2.tgz", "integrity": "sha512-gUYkYhT2+n/+VGZ+8EzE5WFkYZUZYm1VOKDudIsNqh42uRVQJ0a6Yss9sdKT3MeOYfuL1N6AZA57oza0Oyr0LA==", + "dev": true, "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", @@ -614,6 +629,7 @@ "version": "6.7.6", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.6.tgz", "integrity": "sha512-kAz+AncRtKuIknedxT1bq4XwXv4UowhbkHU1myPrtVb/jZtImWuV5BXzv5vK6i3kYACsdiZiQKFQQ5Mq7elW8w==", + "dev": true, "license": "MIT", "dependencies": { "@marijn/find-cluster-break": "^1.0.0" @@ -623,6 +639,7 @@ "version": "6.1.3", "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", + "dev": true, "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", @@ -635,6 +652,7 @@ "version": "6.43.13", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.13.tgz", "integrity": "sha512-sihaFrUzAsYBQsL9J2t69y8nfMQGwcYmggAZsk+kjPbjYZMyuf2hU8tUNTZ+P+isb6XRr8JE22TZlJxBoVdH1A==", + "dev": true, "license": "MIT", "dependencies": { "@codemirror/state": "^6.7.0", @@ -787,6 +805,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@dagrejs/dagre/-/dagre-3.1.1.tgz", "integrity": "sha512-zroZB1dFOFiGgv4Xcrn1DckB1o4aOikPqD2NDQPV0WM//CXGcS6xiD0rNkqHmw6FEg4tabt4nxPLwgCWT+Vb2A==", + "dev": true, "license": "MIT", "dependencies": { "@dagrejs/graphlib": "4.0.5" @@ -796,12 +815,14 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/@dagrejs/graphlib/-/graphlib-4.0.5.tgz", "integrity": "sha512-7xrBTqIts3o+PMUZX97wSc+7TUbW+/rULzGNCTP6yooNVDXbzw4Wutg/H/xOutTB/c/k0YqOAavgPh4/Zk9PFA==", + "dev": true, "license": "MIT" }, "node_modules/@dnd-kit/accessibility": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -814,6 +835,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "dev": true, "license": "MIT", "dependencies": { "@dnd-kit/accessibility": "^3.1.1", @@ -829,6 +851,7 @@ "version": "10.0.0", "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "dev": true, "license": "MIT", "dependencies": { "@dnd-kit/utilities": "^3.2.2", @@ -843,6 +866,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -1195,6 +1219,7 @@ "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.16.7", @@ -1214,6 +1239,7 @@ "version": "11.14.0", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "dev": true, "license": "MIT", "dependencies": { "@emotion/memoize": "^0.9.0", @@ -1227,12 +1253,14 @@ "version": "0.9.2", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "dev": true, "license": "MIT" }, "node_modules/@emotion/is-prop-valid": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "dev": true, "license": "MIT", "dependencies": { "@emotion/memoize": "^0.9.0" @@ -1242,12 +1270,14 @@ "version": "0.9.0", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "dev": true, "license": "MIT" }, "node_modules/@emotion/react": { "version": "11.14.0", "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", @@ -1272,6 +1302,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "dev": true, "license": "MIT", "dependencies": { "@emotion/hash": "^0.9.2", @@ -1285,12 +1316,14 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "dev": true, "license": "MIT" }, "node_modules/@emotion/styled": { "version": "11.14.1", "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", @@ -1314,12 +1347,14 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "dev": true, "license": "MIT" }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "dev": true, "license": "MIT", "peerDependencies": { "react": ">=16.8.0" @@ -1329,12 +1364,14 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "dev": true, "license": "MIT" }, "node_modules/@emotion/weak-memoize": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "dev": true, "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { @@ -1895,12 +1932,14 @@ "version": "0.2.12", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "dev": true, "license": "MIT" }, "node_modules/@fontsource/red-hat-text": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@fontsource/red-hat-text/-/red-hat-text-5.3.0.tgz", "integrity": "sha512-/kgNIUqdoc9uyd4zK+T7gEwY6bYF+hSCZeQTl8Ni/oKtv+a7YeCyQwAE94BeLwud6UfLqn90gBFKa3gCpiZnRA==", + "dev": true, "license": "OFL-1.1", "funding": { "url": "https://github.com/sponsors/ayuhito" @@ -1910,6 +1949,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.3.tgz", "integrity": "sha512-TA//nWMqPhbfdfneACk6t5a9eqbS9lABEPyKn0/xZTah3H3U2XaVg85rJFl0/Fyit0I552YDHgXGVSf3GwqbUw==", + "dev": true, "license": "MIT", "engines": { "node": ">=20" @@ -2104,6 +2144,7 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -2125,6 +2166,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -2134,12 +2176,14 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -2157,12 +2201,14 @@ "version": "1.5.3", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.3.tgz", "integrity": "sha512-H0iErY4e43LpXbYDyBci5W4v/RwTgGV3YzYOlJPiGZ8RY8w51kVE+xLn1tg3Z6UmSfyETvtsUK3r7YsgDQEI7Q==", + "dev": true, "license": "MIT" }, "node_modules/@lezer/highlight": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.5.tgz", "integrity": "sha512-O1GMVKgtf5YspFaRzpmqkVgMtIk2HG9uMAbH6wdAIdNh3TVzCsch/+cRi+6l2//UGDKe6NB9BW3cvuR5ezMQ0w==", + "dev": true, "license": "MIT", "dependencies": { "@lezer/common": "^1.3.0" @@ -2172,6 +2218,7 @@ "version": "1.4.10", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "dev": true, "license": "MIT", "dependencies": { "@lezer/common": "^1.0.0" @@ -2236,12 +2283,14 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.4.tgz", "integrity": "sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==", + "dev": true, "license": "MIT" }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.32.1", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.32.1.tgz", "integrity": "sha512-2DdE+SJDtzLEEWzY1ZjY7Q+VcPhcV1KisD3zI4u0XZyktsjHum1mwbMI+JaulUBi2OZk+KJAi2uPXzxichPkdw==", + "dev": true, "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9 || ^2.0.5", @@ -2282,6 +2331,7 @@ "version": "8.20.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -2298,12 +2348,14 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, "license": "MIT" }, "node_modules/@mui/core-downloads-tracker": { "version": "9.4.0", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-9.4.0.tgz", "integrity": "sha512-d+gIYM1raJP87yrVQcVg+Q3vycVMArMkgu/TjJLi2vTvO9FHKWNRR1xs9nnN4eLpM9nGxxm02UMvc9BrmcUKvQ==", + "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -2314,6 +2366,7 @@ "version": "9.4.0", "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-9.4.0.tgz", "integrity": "sha512-5PVgBYtLOXTk6u0YUAjoV9GoTUQDbeZ8g+pus2h0GphLT/rpmftRnB5D/Kw6QCAovB7EyBX7UxKdZqPBBAHUKw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.7" @@ -2340,6 +2393,7 @@ "version": "9.4.0", "resolved": "https://registry.npmjs.org/@mui/material/-/material-9.4.0.tgz", "integrity": "sha512-6KKUIvkQ2r/s48s9VZV9JEZ1W2dRa7RndVsF+Ipx4CYxDOQeozeRsS5r9NiheFf8A5hpbqJJIGoX7hjg5X4XUw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.7", @@ -2389,6 +2443,7 @@ "version": "9.4.0", "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-9.4.0.tgz", "integrity": "sha512-qQpX/2XPGDz5F2OIWzo4kkorURlDeXGKytRQudXCdLS5TuwKsMKaZyx77LZEVSB73c7o3UrLWwQ/goRuHBdtjw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.7", @@ -2416,6 +2471,7 @@ "version": "9.4.0", "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-9.4.0.tgz", "integrity": "sha512-SlqVEYqnyEgXMFW4JuIDMelyhKzoWBJ85v2mNCQd0XtU0uk7U9E1zaJZ06XqodhA8oJrb+4Q+3E/JlwBe4jWkw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.7", @@ -2450,6 +2506,7 @@ "version": "9.4.0", "resolved": "https://registry.npmjs.org/@mui/system/-/system-9.4.0.tgz", "integrity": "sha512-AZz9z13oGS1J0BYeAFHTbV7PegCtPiE/uMEIi7TJ1kTx3Vkt+jUpLyXF/2uV8B6Y79iTh98kVeOH9fX3x3zXXg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.7", @@ -2490,6 +2547,7 @@ "version": "9.4.0", "resolved": "https://registry.npmjs.org/@mui/types/-/types-9.4.0.tgz", "integrity": "sha512-13DH0Oniua4WmGqeYbQAZqmiN7r4nfd0zwIoGJ5QeJwyIHgEmf+LkRw/jV1HZb6AiUoHX8Oxf4xgalq3vYHcIw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.7" @@ -2507,6 +2565,7 @@ "version": "9.4.0", "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-9.4.0.tgz", "integrity": "sha512-WkzY8uYtqUDyGKPShrRQRBomfMQHddlsyKu/gRRESYO24tDPD2z0xRE8CBoUsWqpBa+YrFs9KixiS7kQRjsPAQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.7", @@ -2537,6 +2596,7 @@ "version": "9.15.0", "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-9.15.0.tgz", "integrity": "sha512-1ELNplhYtGt+54f6o3CHcP7Oz7tyLSgYt20D7WItCyD8ssui3fah7gzZdPGGPggSE/it0UbNwQNOHlqJfLAVgQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.7 || ^8.0.0", @@ -2560,6 +2620,7 @@ "version": "9.15.0", "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-9.15.0.tgz", "integrity": "sha512-1Qsu/1oViKGFU/sF2w32c8OydY3GDozx85bNEsr0f8A53q9kzomrbOwPiW0EHKXjBZ/OeQuiBhxPn6D5m8YsDQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.7 || ^8.0.0", @@ -3138,6 +3199,7 @@ "version": "0.17.0", "resolved": "https://registry.npmjs.org/@northern.tech/themes/-/themes-0.17.0.tgz", "integrity": "sha512-BXOimCKEJ4mVBNgrcAfszM/yGk6zV4S7TceSR52b/LK32Ks+CJvLaFnWOYWWNNvlYCkV2sRmEmV2vTWdaNhiuA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@mui/material": "^9.x" @@ -4296,6 +4358,7 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -4306,6 +4369,7 @@ "version": "2.13.0", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.13.0.tgz", "integrity": "sha512-f6jwVRfwW30+tnllz4YcsHHpPYYOE7abcHkyUCovD+P7DyhdAI5prz2Fh/8TLhakjpN+7aIEion1s5L/tvlYlA==", + "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -4709,12 +4773,14 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, "license": "MIT" }, "node_modules/@standard-schema/utils": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "dev": true, "license": "MIT" }, "node_modules/@storybook/global": { @@ -4986,12 +5052,14 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-drag": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/d3-selection": "*" @@ -5001,6 +5069,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dev": true, "license": "MIT", "dependencies": { "@types/d3-color": "*" @@ -5010,12 +5079,14 @@ "version": "3.0.12", "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.12.tgz", "integrity": "sha512-Qe/KWYhEiIIxGs7HrAAjMfShxKldx19SJtr5zu53f3afPsdZNz7HHtdTLXo/kqeiWNXVycI24kSnfzBYkTzpgw==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-transition": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "dev": true, "license": "MIT", "dependencies": { "@types/d3-selection": "*" @@ -5025,6 +5096,7 @@ "version": "3.0.9", "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.9.tgz", "integrity": "sha512-0sE1406XBYJGiqD3AusTl9ZqC//2mIXix51tbom25gDCA8ri4xnSZg28CaSE8Srl6FClqABUKfsn0qghgdepMA==", + "dev": true, "license": "MIT", "dependencies": { "@types/d3-interpolate": "*", @@ -5161,18 +5233,21 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "dev": true, "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==", + "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "19.3.0", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", + "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -5182,7 +5257,7 @@ "version": "19.3.0", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", - "devOptional": true, + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.3.0" @@ -5192,6 +5267,7 @@ "version": "4.4.12", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "*" @@ -5225,6 +5301,7 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "dev": true, "license": "MIT" }, "node_modules/@typescript-eslint/scope-manager": { @@ -5294,6 +5371,7 @@ "version": "4.25.12", "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.25.12.tgz", "integrity": "sha512-wR5lz7jbVcBGyiiLEObAmDikcc39KNqrrd4bdUhav85WZu48ttgnSf1YV+grHuMsj5l/WCw6u+r9ZuSfURJgiA==", + "dev": true, "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.0.0", @@ -5321,6 +5399,7 @@ "version": "4.25.12", "resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.25.12.tgz", "integrity": "sha512-RE0Gj/QesD7/JWPnJREG8Kq8brGv2NAH17iUECcBrLkqXzzEAgsj/4Qqvz/OlK8XWKmzlsenLRkObT9H+gYQ0w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.6", @@ -5822,6 +5901,7 @@ "version": "12.12.0", "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.12.0.tgz", "integrity": "sha512-74oXI0Rgm1Eu33cHQAd74J6oVgCH9mbFyTVQbsGgp2kmWwh0HdtBr2VP04z8mbPD+M/xO69nqG5dDEt7/EN2rQ==", + "dev": true, "license": "MIT", "dependencies": { "@xyflow/system": "0.0.83", @@ -5847,6 +5927,7 @@ "version": "0.0.83", "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.83.tgz", "integrity": "sha512-a//eDjez3WZF47aaPTfVUDD0OINOq+WVwBZCRTqG+m0a5euX8EwlJ4jMY1Wxz0Ocj4xOeekJ70V22Pq0ll8RJw==", + "dev": true, "license": "MIT", "dependencies": { "@types/d3-drag": "^3.0.7", @@ -5874,6 +5955,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, "license": "MIT", "dependencies": { "mime-types": "^3.0.0", @@ -5887,6 +5969,7 @@ "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -5896,6 +5979,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -5962,6 +6046,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -5979,6 +6064,7 @@ "version": "8.20.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -5995,6 +6081,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, "license": "MIT" }, "node_modules/ansi-regex": { @@ -6542,6 +6629,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5", @@ -6629,6 +6717,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "dev": true, "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -6653,6 +6742,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -6799,6 +6889,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -6900,6 +6991,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -6913,6 +7005,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -6929,6 +7022,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -7085,6 +7179,7 @@ "version": "5.0.5", "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "dev": true, "license": "MIT" }, "node_modules/cliui": { @@ -7119,6 +7214,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -7128,6 +7224,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "dev": true, "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.0.0", @@ -7234,6 +7331,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -7247,6 +7345,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -7256,12 +7355,14 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, "license": "MIT" }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -7271,6 +7372,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.6.0" @@ -7287,6 +7389,7 @@ "version": "2.8.6", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dev": true, "license": "MIT", "dependencies": { "object-assign": "^4", @@ -7304,6 +7407,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, "license": "MIT", "dependencies": { "@types/parse-json": "^4.0.0", @@ -7320,6 +7424,7 @@ "version": "1.10.3", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "dev": true, "license": "ISC", "engines": { "node": ">= 6" @@ -7329,6 +7434,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", + "dev": true, "license": "MIT" }, "node_modules/cross-dirname": { @@ -7344,6 +7450,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -7379,12 +7486,14 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, "license": "MIT" }, "node_modules/d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -7394,6 +7503,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -7403,6 +7513,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dev": true, "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -7416,6 +7527,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=12" @@ -7425,6 +7537,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dev": true, "license": "ISC", "dependencies": { "d3-color": "1 - 3" @@ -7437,6 +7550,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -7446,6 +7560,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -7455,6 +7570,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dev": true, "license": "ISC", "dependencies": { "d3-color": "1 - 3", @@ -7474,6 +7590,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dev": true, "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -7558,6 +7675,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -7745,6 +7863,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -7862,6 +7981,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.7", @@ -7901,6 +8021,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -7932,6 +8053,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, "license": "MIT" }, "node_modules/ejs": { @@ -8635,6 +8757,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -8687,6 +8810,7 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -8784,6 +8908,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8793,6 +8918,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8837,6 +8963,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -8959,12 +9086,14 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -9551,6 +9680,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -9560,6 +9690,7 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, "license": "MIT", "dependencies": { "eventsource-parser": "^3.0.1" @@ -9572,6 +9703,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18.0.0" @@ -9598,6 +9730,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dev": true, "license": "MIT", "dependencies": { "accepts": "^2.0.0", @@ -9641,6 +9774,7 @@ "version": "8.7.1", "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.1.tgz", "integrity": "sha512-2FXbqkygil3mZiXzEtM0IjE/UBmBNot0J1GEVrJT4JbrvwOSoEtqtO+wHbiMQELWe1eNvHoYRg2dkjOid+6JkA==", + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.4.3", @@ -9660,6 +9794,7 @@ "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -9669,6 +9804,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -9692,6 +9828,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-json-stable-stringify": { @@ -9712,6 +9849,7 @@ "version": "3.1.8", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "dev": true, "funding": [ { "type": "github", @@ -9796,6 +9934,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -9817,6 +9956,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "dev": true, "license": "MIT" }, "node_modules/find-up": { @@ -9922,6 +10062,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -9931,6 +10072,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -9977,6 +10119,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10057,6 +10200,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -10081,6 +10225,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -10256,6 +10401,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10353,6 +10499,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10394,6 +10541,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -10423,6 +10571,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "react-is": "^16.7.0" @@ -10432,12 +10581,14 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, "license": "MIT" }, "node_modules/hono": { "version": "4.13.13", "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.13.tgz", "integrity": "sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==", + "dev": true, "license": "MIT", "engines": { "node": ">=16.9.0" @@ -10507,6 +10658,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, "license": "MIT", "dependencies": { "depd": "~2.0.0", @@ -10569,6 +10721,7 @@ "version": "0.7.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -10585,6 +10738,7 @@ "version": "11.1.18", "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.18.tgz", "integrity": "sha512-EQyQtLiYW029lyoczMl/Hh4Xu7cDecSc58JRYpHyL4tIAu3eqd1yJzQX04d2BZHDkzFFvm6qJEJWOtfDSWAXbQ==", + "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -10595,6 +10749,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -10654,6 +10809,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, "license": "ISC" }, "node_modules/ini": { @@ -10685,6 +10841,7 @@ "version": "10.7.3", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.3.tgz", "integrity": "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 12" @@ -10694,6 +10851,7 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.10" @@ -10747,6 +10905,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, "license": "MIT" }, "node_modules/is-async-function": { @@ -10829,6 +10988,7 @@ "version": "2.16.2", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.3" @@ -11093,6 +11253,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, "license": "MIT" }, "node_modules/is-regex": { @@ -11281,6 +11442,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, "node_modules/iterator.prototype": { @@ -11356,6 +11518,7 @@ "version": "6.2.12", "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -11365,6 +11528,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -11454,6 +11618,7 @@ "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" @@ -11473,6 +11638,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { @@ -11486,6 +11652,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "dev": true, "license": "BSD-2-Clause" }, "node_modules/json-stable-stringify-without-jsonify": { @@ -11598,6 +11765,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, "license": "MIT" }, "node_modules/load-plugin": { @@ -11660,6 +11828,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -11747,6 +11916,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -11920,6 +12090,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -11933,6 +12104,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -12688,6 +12860,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -12736,6 +12909,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "dev": true, "license": "MIT", "dependencies": { "content-type": "^2.1.0" @@ -12752,6 +12926,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -13054,6 +13229,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13063,6 +13239,7 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -13189,6 +13366,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -13201,6 +13379,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -13386,6 +13565,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -13425,6 +13605,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", @@ -13456,6 +13637,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -13485,6 +13667,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -13494,6 +13677,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, "license": "MIT" }, "node_modules/path-scurry": { @@ -13524,6 +13708,7 @@ "version": "8.4.2", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -13534,6 +13719,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -13569,6 +13755,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -13588,6 +13775,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=16.20.0" @@ -13843,6 +14031,7 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -13854,6 +14043,7 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, "license": "MIT" }, "node_modules/proper-lockfile": { @@ -13872,6 +14062,7 @@ "version": "2.0.8", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "dev": true, "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -13950,6 +14141,7 @@ "version": "6.16.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "es-define-property": "^1.0.1", @@ -13979,6 +14171,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -13992,6 +14185,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dev": true, "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -14007,6 +14201,7 @@ "version": "19.3.0", "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14016,6 +14211,7 @@ "version": "19.3.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", + "dev": true, "license": "MIT", "dependencies": { "scheduler": "^0.28.0" @@ -14028,12 +14224,14 @@ "version": "19.3.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.3.0.tgz", "integrity": "sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA==", + "dev": true, "license": "MIT" }, "node_modules/react-redux": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", + "dev": true, "license": "MIT", "dependencies": { "@types/use-sync-external-store": "^0.0.6", @@ -14067,6 +14265,7 @@ "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@babel/runtime": "^7.5.5", @@ -14186,12 +14385,14 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "dev": true, "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "dev": true, "license": "MIT", "peerDependencies": { "redux": "^5.0.0" @@ -14330,6 +14531,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14357,12 +14559,14 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.3.0.tgz", "integrity": "sha512-XGoLeRAVzUTcJ1qkxPQhDJyIZ5d6zzZD9nT7AEZOaaU9UbWclhycElmhO+VD5bFeLuzhPBaOV2oXC8uG35ZSpg==", + "dev": true, "license": "MIT" }, "node_modules/resolve": { "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -14391,6 +14595,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -14513,6 +14718,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -14605,6 +14811,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, "license": "MIT" }, "node_modules/sanitize-filename": { @@ -14644,6 +14851,7 @@ "version": "0.28.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "dev": true, "license": "MIT" }, "node_modules/scslre": { @@ -14686,6 +14894,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.4.3", @@ -14712,6 +14921,7 @@ "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -14721,6 +14931,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -14754,6 +14965,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "dev": true, "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -14822,12 +15034,14 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -14840,6 +15054,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -14849,6 +15064,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -14868,6 +15084,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -14884,6 +15101,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -14902,6 +15120,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -14941,6 +15160,7 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -15045,6 +15265,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -15450,12 +15671,14 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.4.tgz", "integrity": "sha512-XXWIQt633/EpAFx8aZDOTjBzrCaGmhvEQlQo6MVPfa2OzO2cWo+4hV9h+6UkHYlXGfy+ODXKUdP7Pthmcu5ATw==", + "dev": true, "license": "MIT" }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "dev": true, "license": "MIT" }, "node_modules/sumchecker": { @@ -15488,6 +15711,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -15695,6 +15919,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.6" @@ -15790,6 +16015,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, "license": "0BSD" }, "node_modules/type-check": { @@ -15823,6 +16049,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "dev": true, "license": "MIT", "dependencies": { "content-type": "^2.0.0", @@ -15841,6 +16068,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -15854,6 +16082,7 @@ "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -15863,6 +16092,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -16301,6 +16531,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -16418,6 +16649,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz", "integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==", + "dev": true, "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -16462,6 +16694,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -16787,6 +17020,7 @@ "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "dev": true, "license": "MIT" }, "node_modules/w3c-xmlserializer": { @@ -16862,6 +17096,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -17026,6 +17261,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, "license": "ISC" }, "node_modules/ws": { @@ -17174,6 +17410,7 @@ "version": "4.6.5", "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -17183,6 +17420,7 @@ "version": "3.25.2", "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "dev": true, "license": "ISC", "peerDependencies": { "zod": "^3.25.28 || ^4" @@ -17205,6 +17443,7 @@ "version": "4.5.7", "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "dev": true, "license": "MIT", "dependencies": { "use-sync-external-store": "^1.2.2" diff --git a/package.json b/package.json index f1cb40e..00825ec 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "test:watch": "vitest --project renderer --project main", "test:e2e": "electron-vite build && playwright test -c e2e/playwright.config.ts" }, - "dependencies": { + "devDependencies": { "@codemirror/autocomplete": "^6.20.3", "@codemirror/language": "^6.12.4", "@codemirror/state": "^6.7.4", @@ -59,26 +59,20 @@ "@mui/icons-material": "^9.4.0", "@mui/material": "^9.2.0", "@mui/x-tree-view": "^9.15.0", - "@northern.tech/themes": "^0.17.0", - "@reduxjs/toolkit": "^2.12.0", - "@uiw/react-codemirror": "^4.25.11", - "@xyflow/react": "^12.12.0", - "react": "^19.3.0", - "react-dom": "^19.3.0", - "react-redux": "^9.3.0", - "zod": "^4.6.5" - }, - "devDependencies": { "@northern.tech/eslint-config": "^0.7.2", "@northern.tech/prettier-config": "^0.2.1", + "@northern.tech/themes": "^0.17.0", "@playwright/test": "^1.63.0", + "@reduxjs/toolkit": "^2.12.0", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.3", "@types/node": "^26.6.4", "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", + "@uiw/react-codemirror": "^4.25.11", "@vitejs/plugin-react": "^5.2.0", "@vitest/coverage-v8": "^5.0.3", + "@xyflow/react": "^12.12.0", "electron": "^44.5.1", "electron-builder": "^26.17.0", "electron-vite": "^5.0.0", @@ -86,8 +80,12 @@ "globals": "^17.13.0", "jsdom": "^30.1.2", "prettier": "^3.9.9", + "react": "^19.3.0", + "react-dom": "^19.3.0", + "react-redux": "^9.3.0", "typescript": "^6.0.3", "vite": "^7.3.7", - "vitest": "^5.0.1" + "vitest": "^5.0.1", + "zod": "^4.6.5" } } diff --git a/src/main/mcp.ts b/src/main/mcp.ts index 0caffb6..cf63335 100644 --- a/src/main/mcp.ts +++ b/src/main/mcp.ts @@ -1,5 +1,3 @@ -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { randomBytes, timingSafeEqual } from 'crypto'; import { BrowserWindow, type IpcMainInvokeEvent, type WebContents, type WebFrameMain, app, ipcMain } from 'electron'; import { promises as fs } from 'fs'; @@ -7,7 +5,6 @@ import { type IncomingMessage, type Server, type ServerResponse, createServer } import type { AddressInfo } from 'net'; import { join } from 'path'; -import { MCP_INSTRUCTIONS, registerBuilderTools } from './mcpTools/index'; import { type McpPermissions, checkedPermissions, isToolAllowed } from './mcpTools/permissions'; /** @@ -18,6 +15,7 @@ import { type McpPermissions, checkedPermissions, isToolAllowed } from './mcpToo const HOST = '127.0.0.1'; const TOOL_TIMEOUT_MS = 30_000; +const SERVER_NAME = 'cfengine-policy-builder'; // Tools that wait on cfbs, Docker or the agent: a test run builds, starts hosts and runs cf-agent. const SLOW_TOOLS: Record = { create_project: 180_000, @@ -27,7 +25,6 @@ const SLOW_TOOLS: Record = { run_tests: 900_000, stop_test_environment: 300_000 }; -const SERVER_NAME = 'cfengine-policy-builder'; interface McpSettings { enabled: boolean; @@ -137,16 +134,8 @@ async function handle(req: IncomingMessage, res: ServerResponse, port: number, t res.writeHead(404).end(); return; } - // Stateless: a server and transport per request. - const mcp = new McpServer({ name: SERVER_NAME, version: app.getVersion() }, { instructions: MCP_INSTRUCTIONS }); - registerBuilderTools(mcp, callWindowTool, permissions); - const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); - res.on('close', () => { - void transport.close(); - void mcp.close(); - }); - await mcp.connect(transport); - await transport.handleRequest(req, res); + const { answerMcpRequest } = await import('./mcpTools/serve'); + await answerMcpRequest(req, res, { name: SERVER_NAME, version: app.getVersion() }, callWindowTool, permissions); } function listen(port: number, token: string): Promise { diff --git a/src/main/mcpTools/serve.ts b/src/main/mcpTools/serve.ts new file mode 100644 index 0000000..6a6a906 --- /dev/null +++ b/src/main/mcpTools/serve.ts @@ -0,0 +1,27 @@ +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import type { IncomingMessage, ServerResponse } from 'http'; + +import { MCP_INSTRUCTIONS, registerBuilderTools } from './index'; +import type { McpPermissions } from './permissions'; +import type { CallWindow } from './shared'; + +// Loaded on the first request (mcp.ts): the SDK and zod take ~300 ms, too much for every launch. +// Stateless: a server and transport per request. +export async function answerMcpRequest( + req: IncomingMessage, + res: ServerResponse, + info: { name: string; version: string }, + call: CallWindow, + permissions: McpPermissions +) { + const mcp = new McpServer(info, { instructions: MCP_INSTRUCTIONS }); + registerBuilderTools(mcp, call, permissions); + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); + res.on('close', () => { + void transport.close(); + void mcp.close(); + }); + await mcp.connect(transport); + await transport.handleRequest(req, res); +} diff --git a/src/renderer/src/canvas/stableItems.ts b/src/renderer/src/canvas/stableItems.ts new file mode 100644 index 0000000..010837c --- /dev/null +++ b/src/renderer/src/canvas/stableItems.ts @@ -0,0 +1,32 @@ +const isPlainObject = (value: unknown): value is Record => + typeof value === 'object' && value !== null && Object.getPrototypeOf(value) === Object.prototype; + +// Equal down to `depth` levels of plain objects and arrays; any two functions count as equal. +export function sameShape(a: unknown, b: unknown, depth: number): boolean { + if (Object.is(a, b)) return true; + if (typeof a === 'function' && typeof b === 'function') return true; + if (depth === 0) return false; + if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((item, index) => sameShape(item, b[index], depth - 1)); + if (!isPlainObject(a) || !isPlainObject(b)) return false; + const keys = Object.keys(a); + return keys.length === Object.keys(b).length && keys.every(key => key in b && sameShape(a[key], b[key], depth - 1)); +} + +/** + * React Flow re-renders a node or edge whenever its object changes. These are rebuilt on every + * canvas render, so each is swapped for last call's object when nothing in it changed (three + * levels deep, callbacks ignored). The callbacks must only capture what the item itself holds, + * and reach the canvas's handlers through a latest-ref (useLatest), or a reused one goes stale. + * Create one per list and keep it for the component's life: `useState(() => stableItems())`. + */ +export function stableItems(): (items: T[]) => T[] { + let previous = new Map(); + return items => { + const stable = items.map(item => { + const before = previous.get(item.id); + return before && sameShape(before, item, 3) ? before : item; + }); + previous = new Map(stable.map(item => [item.id, item])); + return stable; + }; +} diff --git a/src/renderer/src/components/BlockGroupRow.tsx b/src/renderer/src/components/BlockGroupRow.tsx index b986128..763359b 100644 --- a/src/renderer/src/components/BlockGroupRow.tsx +++ b/src/renderer/src/components/BlockGroupRow.tsx @@ -1,7 +1,6 @@ -import { useState } from 'react'; - import { Box, Button, Divider, ListItemText, Menu, MenuItem, Typography, useTheme } from '@mui/material'; +import { useMenuAnchor } from '../hooks/useMenuAnchor'; import type { BlockInstance } from '../store/canvasSlice/types'; import type { BlockGroup } from '../store/groupsSlice/types'; @@ -23,7 +22,7 @@ export function BlockGroupRow({ onOpen: (groupId: string) => void; }) { const theme = useTheme(); - const [anchorEl, setAnchorEl] = useState(null); + const menu = useMenuAnchor(); const group = groups.find(candidate => candidate.id === instance.groupId); const others = groups.filter(candidate => candidate.id !== instance.groupId); @@ -41,33 +40,35 @@ export function BlockGroupRow({ ) : ( - )} - setAnchorEl(null)}> - {others.map(candidate => ( + {menu.mounted && ( + + {others.map(candidate => ( + { + onJoin(candidate.id); + menu.close(); + }} + > + + + + ))} + {others.length > 0 && } { - onJoin(candidate.id); - setAnchorEl(null); + onNewGroup(); + menu.close(); }} > - - + - ))} - {others.length > 0 && } - { - onNewGroup(); - setAnchorEl(null); - }} - > - - - + + )} ); } diff --git a/src/renderer/src/components/BlockPalette.tsx b/src/renderer/src/components/BlockPalette.tsx index 2a12759..23801ca 100644 --- a/src/renderer/src/components/BlockPalette.tsx +++ b/src/renderer/src/components/BlockPalette.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react'; +import { memo, useMemo, useState } from 'react'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import SearchIcon from '@mui/icons-material/Search'; @@ -75,7 +75,7 @@ function CategoryGroup({ category, blocks, onAdd }: { blocks: BlockDescriptor[]; ); } -export function BlockPalette({ onAddBlock }: { onAddBlock: (block: BlockDescriptor) => void }) { +export const BlockPalette = memo(function BlockPalette({ onAddBlock }: { onAddBlock: (block: BlockDescriptor) => void }) { const [search, setSearch] = useState(''); const groups = useMemo(() => { @@ -119,4 +119,4 @@ export function BlockPalette({ onAddBlock }: { onAddBlock: (block: BlockDescript ); -} +}); diff --git a/src/renderer/src/components/DataNode.tsx b/src/renderer/src/components/DataNode.tsx index 71eebc3..2a4c081 100644 --- a/src/renderer/src/components/DataNode.tsx +++ b/src/renderer/src/components/DataNode.tsx @@ -1,4 +1,4 @@ -import { type ReactNode, useMemo, useState } from 'react'; +import { type ReactNode, useMemo } from 'react'; import CloseIcon from '@mui/icons-material/Close'; import DataObjectIcon from '@mui/icons-material/DataObject'; @@ -12,7 +12,9 @@ import { currentChainType, decoratorsById } from '../blocks/decorators'; import type { DecoratorValue } from '../blocks/evaluateDecorator'; import { runDecoratorChain } from '../blocks/runDecoratorChain'; import { DATA_NODE_WIDTH, type DataChain } from '../canvas/dataChains'; +import { useMenuAnchor } from '../hooks/useMenuAnchor'; import { AddDecoratorButton } from './PropertiesPanel'; +import { contentSized, plainTextarea } from './contentSized'; import { stopCanvasEvents } from './stopCanvasEvents'; export interface DataNodeData extends Record { @@ -35,27 +37,29 @@ function formatValue(value: DecoratorValue): string { const oneLine = { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } as const; function StepMenu({ index, count, onMove, onRemove }: { count: number; index: number; onMove: (to: number) => void; onRemove: () => void }) { - const [anchor, setAnchor] = useState(null); + const menu = useMenuAnchor(); const close = (action: () => void) => () => { action(); - setAnchor(null); + menu.close(); }; return ( <> - setAnchor(event.currentTarget)} sx={{ p: 0 }}> + menu.open(event.currentTarget)} sx={{ p: 0 }}> - setAnchor(null)}> - onMove(index - 1))}> - - - onMove(index + 1))}> - - - - - - + {menu.mounted && ( + + onMove(index - 1))}> + + + onMove(index + 1))}> + + + + + + + )} ); } @@ -186,12 +190,11 @@ export function DataNode({ data, selected }: NodeProps) { className="nodrag nopan" size="small" multiline - maxRows={2} value={chain.sampleInput} onChange={event => onSampleChange(event.target.value)} placeholder="Sample input, to preview the steps…" sx={{ mb: 0.5, ml: 2.75, width: 'calc(100% - 22px)', '& .MuiInputBase-root': { py: 0.5, px: 0.75 } }} - slotProps={{ htmlInput: { style: { fontSize: 10, fontFamily: 'monospace', lineHeight: 1.3 } } }} + slotProps={{ input: plainTextarea, htmlInput: { style: { ...contentSized(1, 2), fontSize: 10, fontFamily: 'monospace', lineHeight: 1.3 } } }} /> )} {chain.steps.map((step, index) => { diff --git a/src/renderer/src/components/FlowCanvas.tsx b/src/renderer/src/components/FlowCanvas.tsx index fdad3bf..a44e0f6 100644 --- a/src/renderer/src/components/FlowCanvas.tsx +++ b/src/renderer/src/components/FlowCanvas.tsx @@ -38,6 +38,8 @@ import { GATE_EDGE_PREFIX, GATE_WIDTH, type Gate, deriveGates } from '../canvas/ import { GROUP_NODE_PREFIX, type GroupFrame, type Rect, deriveGroupFrames, frameAt, settleGroup } from '../canvas/groupFrames'; import { GRID_SIZE, NODE_WIDTH, estimateNodeHeight } from '../canvas/layout'; import { defaultOutcomesFor, describeOutcomes } from '../canvas/outcomes'; +import { stableItems } from '../canvas/stableItems'; +import { useLatest, useStableCallback } from '../hooks/useLatest'; import { useBlockProblems } from '../project/testRuns'; import { useAppSelector } from '../store'; import type { BlockInstance } from '../store/canvasSlice/types'; @@ -339,6 +341,23 @@ export function FlowCanvas({ // Where dragged blocks started, to carry a resized frame along when its whole group moves. const dragStartRef = useRef>(new Map()); + // Unchanged nodes and edges keep their objects, so React Flow re-renders only the changed ones. + const [stableNodes] = useState(() => stableItems()); + const [stableEdges] = useState(() => stableItems()); + // What their callbacks call: a reused item keeps its first closures. + const handlers = useLatest({ + dataCallbacks, + onEdgeOutcomesChange, + onEdgeRemove, + onGateLinkRemove, + onGateModeChange, + onGateRemove, + onGroupResize, + onNodeDragStart, + onNodeDragStop, + onRemove + }); + const order = useMemo(() => executionOrder(instances, edges, blockDescriptorsById), [instances, edges]); // Blocks whose promises failed in the last test run, and on which hosts. const testEnvironments = useAppSelector(state => state.testEnvironments); @@ -374,7 +393,7 @@ export function FlowCanvas({ descriptor, duplicateKeys, orderNumber: orderNumbers.get(instance.instanceId), - onRemove: () => onRemove(instance.instanceId), + onRemove: () => handlers.current.onRemove(instance.instanceId), cutPending: instance.instanceId === cutPendingId, highlighted: instance.instanceId === highlightedId, hasDataInput: fedBlocks.has(instance.instanceId), @@ -406,8 +425,8 @@ export function FlowCanvas({ source: conditionSourceOf(gate.condition.className, allInstances, files, currentFileId), fileRelation: relationToFileCondition(gate.condition, fileCondition), onHighlight: setHighlightedId, - onModeChange: mode => onGateModeChange(gate, mode), - onRemove: () => onGateRemove(gate) + onModeChange: mode => handlers.current.onGateModeChange(gate, mode), + onRemove: () => handlers.current.onGateRemove(gate) } })); const gateEdges: GateFlowEdge[] = gates.flatMap(gate => @@ -419,7 +438,7 @@ export function FlowCanvas({ targetHandle: 'gate', type: 'gate' as const, selected: `${GATE_EDGE_PREFIX}${instanceId}` === selectedEdgeId, - data: { onRemove: () => onGateLinkRemove(instanceId) } + data: { onRemove: () => handlers.current.onGateLinkRemove(instanceId) } })) ); @@ -481,12 +500,12 @@ export function FlowCanvas({ dropTarget: frame.group.id === dropGroupId, onResizeStart: () => { resizingRef.current = true; - onNodeDragStart(); + handlers.current.onNodeDragStart(); }, - onResize: rect => onGroupResize(frame.group.id, rect), + onResize: rect => handlers.current.onGroupResize(frame.group.id, rect), onResizeEnd: () => { resizingRef.current = false; - onNodeDragStop(); + handlers.current.onNodeDragStop(); } } })); @@ -502,7 +521,11 @@ export function FlowCanvas({ type: 'outcome', selected: edge.id === selectedEdgeId, markerEnd: { type: MarkerType.ArrowClosed, color, width: 18, height: 18 }, - data: { outcomes: edge.outcomes, onOutcomesChange: outcomes => onEdgeOutcomesChange(edge.id, outcomes), onDelete: () => onEdgeRemove(edge.id) } + data: { + outcomes: edge.outcomes, + onOutcomesChange: outcomes => handlers.current.onEdgeOutcomesChange(edge.id, outcomes), + onDelete: () => handlers.current.onEdgeRemove(edge.id) + } }; }); const dataNodes: DataFlowNode[] = chains.map(chain => ({ @@ -514,11 +537,11 @@ export function FlowCanvas({ selected: chain.owner.instanceId === selectedInstanceId && chain.owner.entryId === selectedChainEntryId, data: { chain, - onAddStep: decoratorId => dataCallbacks.addStep(chain.owner, decoratorId), - onMoveStep: (fromIndex, toIndex) => dataCallbacks.moveStep(chain.owner, fromIndex, toIndex), - onRemove: () => dataCallbacks.remove(chain.owner), - onRemoveStep: stepId => dataCallbacks.removeStep(chain.owner, stepId), - onSampleChange: value => dataCallbacks.sampleChange(chain.owner, value) + onAddStep: decoratorId => handlers.current.dataCallbacks.addStep(chain.owner, decoratorId), + onMoveStep: (fromIndex, toIndex) => handlers.current.dataCallbacks.moveStep(chain.owner, fromIndex, toIndex), + onRemove: () => handlers.current.dataCallbacks.remove(chain.owner), + onRemoveStep: stepId => handlers.current.dataCallbacks.removeStep(chain.owner, stepId), + onSampleChange: value => handlers.current.dataCallbacks.sampleChange(chain.owner, value) } })); // Solid info-blue arrows: data flowing into what a chain produces — unlike @@ -535,8 +558,8 @@ export function FlowCanvas({ })); // Later nodes draw on top: a chain's controls stay usable even where it overlaps a block. - const nodes: CanvasNode[] = [...groupNodes, ...gateNodes, ...blockNodes, ...dataNodes]; - const flowEdges: CanvasEdge[] = [...gateEdges, ...dataEdges, ...outcomeEdges]; + const nodes = stableNodes([...groupNodes, ...gateNodes, ...blockNodes, ...dataNodes]); + const flowEdges = stableEdges([...gateEdges, ...dataEdges, ...outcomeEdges]); // Positions and measured sizes live outside React Flow (Redux / the // parent), so only those two change kinds matter; selection is driven by @@ -715,6 +738,20 @@ export function FlowCanvas({ return null; }; + // Stable: React Flow hands them to every node and edge wrapper, which re-render when they change. + const handleNodeClick = useStableCallback((event: React.MouseEvent, node: CanvasNode) => { + const frame = frameByNodeId.get(node.id); + if (frame) return onSelectGroup(frame.group.id); + const gate = gateByNodeId.get(node.id); + const chain = chainByNodeId.get(node.id); + if (event.shiftKey && byId.has(node.id)) toggleSelected(node.id); + else if (gate) onSelectGate(gate.key); + else if (chain) dataCallbacks.open(chain.owner); + else onSelectInstance(node.id); + }); + // Data arrows aren't selectable, but React Flow still reports their clicks. + const handleEdgeClick = useStableCallback((_event: React.MouseEvent, edge: CanvasEdge) => edge.selectable !== false && onSelectEdge(edge.id)); + return ( { - const frame = frameByNodeId.get(node.id); - if (frame) return onSelectGroup(frame.group.id); - const gate = gateByNodeId.get(node.id); - const chain = chainByNodeId.get(node.id); - if (event.shiftKey && byId.has(node.id)) toggleSelected(node.id); - else if (gate) onSelectGate(gate.key); - else if (chain) dataCallbacks.open(chain.owner); - else onSelectInstance(node.id); - }} - // Data arrows aren't selectable, but React Flow still reports their clicks. - onEdgeClick={(_event, edge) => edge.selectable !== false && onSelectEdge(edge.id)} + onNodeClick={handleNodeClick} + onEdgeClick={handleEdgeClick} onPaneClick={clearSelection} onConnect={connection => { if (connectionProblem(connection)) return; diff --git a/src/renderer/src/components/GateNode.tsx b/src/renderer/src/components/GateNode.tsx index 32109de..049a082 100644 --- a/src/renderer/src/components/GateNode.tsx +++ b/src/renderer/src/components/GateNode.tsx @@ -1,5 +1,3 @@ -import { useState } from 'react'; - import FilterAltOutlinedIcon from '@mui/icons-material/FilterAltOutlined'; import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; import { Box, Divider, IconButton, ListItemText, Menu, MenuItem, Typography, useTheme } from '@mui/material'; @@ -8,6 +6,7 @@ import { Handle, type Node, type NodeProps, Position, useStore } from '@xyflow/r import type { FileConditionRelation } from '../canvas/fileCondition'; import { GATE_WIDTH, type Gate } from '../canvas/gates'; +import { useMenuAnchor } from '../hooks/useMenuAnchor'; import { COMPACT_ZOOM, type ConditionSource } from './BlockCard'; import { stopCanvasEvents } from './stopCanvasEvents'; @@ -43,7 +42,7 @@ function GateCaption({ fileRelation, fileLabel }: { fileLabel?: string; fileRela export function GateNode({ data, selected }: NodeProps) { const { gate, source, fileRelation, onHighlight, onModeChange, onRemove } = data; const theme = useTheme(); - const [menuAnchor, setMenuAnchor] = useState(null); + const menu = useMenuAnchor(); const { mode, className } = gate.condition; const blocks = gate.instanceIds.length; const compact = useStore(state => state.transform[2] < COMPACT_ZOOM); @@ -88,7 +87,7 @@ export function GateNode({ data, selected }: NodeProps) { {!compact && } - setMenuAnchor(event.currentTarget)} sx={{ p: 0.25 }}> + menu.open(event.currentTarget)} sx={{ p: 0.25 }}> ) { title="Drag onto another block's left edge to gate it too" style={{ width: 10, height: 10, background: theme.palette.text.secondary, border: `2px solid ${theme.palette.background.default}` }} /> - setMenuAnchor(null)}> - { - onModeChange('if'); - setMenuAnchor(null); - }} - > - - - { - onModeChange('unless'); - setMenuAnchor(null); - }} - > - - - - { - onRemove(); - setMenuAnchor(null); - }} - > - 1 ? `Remove from all ${blocks} blocks` : 'Remove condition'} /> - - + {menu.mounted && ( + + { + onModeChange('if'); + menu.close(); + }} + > + + + { + onModeChange('unless'); + menu.close(); + }} + > + + + + { + onRemove(); + menu.close(); + }} + > + 1 ? `Remove from all ${blocks} blocks` : 'Remove condition'} /> + + + )} ); } diff --git a/src/renderer/src/components/OutcomeEdge.tsx b/src/renderer/src/components/OutcomeEdge.tsx index 77c2cc9..c4dd861 100644 --- a/src/renderer/src/components/OutcomeEdge.tsx +++ b/src/renderer/src/components/OutcomeEdge.tsx @@ -1,10 +1,9 @@ -import { useState } from 'react'; - import { Box, useTheme } from '@mui/material'; import { BaseEdge, type Edge, EdgeLabelRenderer, type EdgeProps, type InternalNode, Position, getBezierPath, useInternalNode } from '@xyflow/react'; import { describeOutcomes } from '../canvas/outcomes'; +import { useMenuAnchor } from '../hooks/useMenuAnchor'; import type { BlockOutcome } from '../store/edgesSlice/types'; import { OutcomeMenu } from './OutcomeMenu'; @@ -68,7 +67,7 @@ function floatingEndpoints(source: ReturnType, target: ReturnType export function OutcomeEdge(props: EdgeProps) { const { id, source, target, data, selected, markerEnd } = props; const theme = useTheme(); - const [anchorEl, setAnchorEl] = useState(null); + const menu = useMenuAnchor(); const sourceNode = useInternalNode(source); const targetNode = useInternalNode(target); // Until both blocks are measured, fall back to React Flow's handle positions. @@ -84,7 +83,7 @@ export function OutcomeEdge(props: EdgeProps) { setAnchorEl(event.currentTarget)} + onClick={event => menu.open(event.currentTarget)} title="Change or remove this arrow" sx={{ position: 'absolute', @@ -105,7 +104,9 @@ export function OutcomeEdge(props: EdgeProps) { {port.symbol} {port.label} - setAnchorEl(null)} outcomes={data.outcomes} onChange={data.onOutcomesChange} onRemove={data.onDelete} /> + {menu.mounted && ( + + )} ); } diff --git a/src/renderer/src/components/PolicyFileExplorer.test.tsx b/src/renderer/src/components/PolicyFileExplorer.test.tsx index a01c8f9..642d40e 100644 --- a/src/renderer/src/components/PolicyFileExplorer.test.tsx +++ b/src/renderer/src/components/PolicyFileExplorer.test.tsx @@ -8,10 +8,12 @@ import { fileConditionEnabled, fileConditionModeChanged, fileDescriptionChanged, + fileMoved, fileRemoved, fileRenamed, fileSelected, folderAdded, + folderMoved, folderRemoved, folderRenamed } from '../store/filesSlice'; @@ -26,17 +28,20 @@ function ConnectedExplorer() { const dispatch = useAppDispatch(); const { files, folders, currentFileId } = useAppSelector(state => state.files); const instances = useAppSelector(state => state.canvas); + const blockCounts: Record = {}; + for (const instance of instances) blockCounts[instance.fileId] = (blockCounts[instance.fileId] ?? 0) + 1; return ( dispatch(fileSelected({ fileId }))} onAddFile={(name, parentId) => dispatch(fileAdded(name, parentId))} onAddFolder={(name, parentId) => dispatch(folderAdded(name, parentId))} onRenameFile={(fileId, name) => dispatch(fileRenamed({ fileId, name }))} onRenameFolder={(folderId, name) => dispatch(folderRenamed({ folderId, name }))} + onMove={(kind, id, parentId) => dispatch(kind === 'file' ? fileMoved({ fileId: id, parentId }) : folderMoved({ folderId: id, parentId }))} onDeleteFile={fileId => inOneStep(dispatch, () => { dispatch(fileRemoved({ fileId })); diff --git a/src/renderer/src/components/PolicyFileExplorer.tsx b/src/renderer/src/components/PolicyFileExplorer.tsx index 8b575a3..e68891f 100644 --- a/src/renderer/src/components/PolicyFileExplorer.tsx +++ b/src/renderer/src/components/PolicyFileExplorer.tsx @@ -1,4 +1,4 @@ -import { type ReactNode, useEffect, useRef, useState } from 'react'; +import { type HTMLAttributes, type ReactNode, memo, useEffect, useRef, useState } from 'react'; import CreateNewFolderOutlinedIcon from '@mui/icons-material/CreateNewFolderOutlined'; import DeleteOutlineIcon from '@mui/icons-material/DeleteOutlineOutlined'; @@ -12,20 +12,21 @@ import { Box, IconButton, Menu, MenuItem, TextField, Tooltip, Typography } from import { SimpleTreeView, TreeItem } from '@mui/x-tree-view'; import { describeFileCondition } from '../canvas/fileCondition'; -import type { BlockInstance } from '../store/canvasSlice/types'; import { type FileTreeNode, MAX_NAME_LENGTH, buildFileTree, collectFolderDescendants } from '../store/filesSlice/fileTree'; import type { PolicyFile, PolicyFolder } from '../store/filesSlice/types'; import { ConfirmDialog } from './dialogs/ConfirmDialog'; interface PolicyFileExplorerProps { + // Blocks per file id. + blockCounts: Record; currentFileId: string | null; files: PolicyFile[]; folders: PolicyFolder[]; - instances: BlockInstance[]; onAddFile: (name: string, parentId: string | null) => void; onAddFolder: (name: string, parentId: string | null) => void; onDeleteFile: (fileId: string) => void; onDeleteFolder: (folderId: string) => void; + onMove: (kind: 'file' | 'folder', id: string, parentId: string | null) => void; onRenameFile: (fileId: string, name: string) => void; onRenameFolder: (folderId: string, name: string) => void; onSelectFile: (fileId: string) => void; @@ -53,6 +54,14 @@ interface DeleteTarget { kind: 'file' | 'folder'; } +type DragProps = Pick, 'draggable' | 'onDragEnd' | 'onDragOver' | 'onDragStart' | 'onDrop'>; + +// The drop target meaning the project's root (null is "no target"). +const ROOT_TARGET = '__root__'; +const DRAG_MIME_TYPE = 'application/x-cfpb-tree-item'; +// Hovering a collapsed folder this long while dragging opens it. +const EXPAND_DELAY_MS = 600; + function ancestorFolderIds(folders: PolicyFolder[], parentId: string | null): string[] { const ids: string[] = []; let current = parentId; @@ -128,6 +137,7 @@ function RowActions({ onRename, onDelete, disabled, name }: { disabled?: boolean interface FileRowProps { blockCount: number; canDelete: boolean; + dragProps: DragProps; editingName: string; file: PolicyFile; isEditing: boolean; @@ -154,11 +164,13 @@ function FileRow({ onCommitRename, onCancelRename, onContextMenu, - onDeleteRequest + onDeleteRequest, + dragProps }: FileRowProps) { const gate = describeFileCondition(file.condition); return ( !isEditing && onSelect()} onDoubleClick={onStartRename} onContextMenu={onContextMenu} @@ -220,6 +232,8 @@ function FileRow({ interface FolderRowProps { canDelete: boolean; + dragProps: DragProps; + dropTarget: boolean; editingName: string; expanded: boolean; folder: PolicyFolder; @@ -245,14 +259,21 @@ function FolderRow({ onCommitRename, onCancelRename, onContextMenu, - onDeleteRequest + onDeleteRequest, + dragProps, + dropTarget }: FolderRowProps) { return ( !isEditing && onToggleExpanded()} onDoubleClick={onStartRename} onContextMenu={onContextMenu} - sx={{ ...rowSx, cursor: isEditing ? 'default' : 'pointer' }} + sx={{ + ...rowSx, + cursor: isEditing ? 'default' : 'pointer', + ...(dropTarget && { bgcolor: 'action.selected', outline: '1px solid', outlineColor: 'primary.main' }) + }} > {expanded ? ( @@ -347,18 +368,19 @@ function DraftRow({ kind, name, onNameChange, onCommit, onCancel }: DraftRowProp * namespace (see architecture-plan.md's Namespaces section). Folders are * purely organizational — see PolicyFile.parentId. */ -export function PolicyFileExplorer({ +export const PolicyFileExplorer = memo(function PolicyFileExplorer({ files, folders, currentFileId, - instances, + blockCounts, onSelectFile, onAddFile, onAddFolder, onRenameFile, onRenameFolder, onDeleteFile, - onDeleteFolder + onDeleteFolder, + onMove }: PolicyFileExplorerProps) { const [expandedItems, setExpandedItems] = useState([]); const [editing, setEditing] = useState(null); @@ -367,6 +389,9 @@ export function PolicyFileExplorer({ const [draftName, setDraftName] = useState(''); const [contextMenu, setContextMenu] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); + const [dragging, setDragging] = useState(null); + const [dropTarget, setDropTarget] = useState(null); + const expandTimer = useRef<{ folderId: string; timer: ReturnType } | null>(null); // Keeps the current file's folder visible after switching files (e.g. // loading a project, or a cross-file jump) — same idea as an editor's @@ -397,7 +422,7 @@ export function PolicyFileExplorer({ return () => cancelAnimationFrame(frame); }, [currentFileId]); - const blockCountFor = (fileId: string) => instances.filter(instance => instance.fileId === fileId).length; + const blockCountFor = (fileId: string) => blockCounts[fileId] ?? 0; const startRename = (kind: 'file' | 'folder', id: string, name: string) => { setEditing({ kind, id, name }); @@ -457,6 +482,71 @@ export function PolicyFileExplorer({ setDeleteTarget(null); }; + const cancelExpandTimer = () => { + if (expandTimer.current) clearTimeout(expandTimer.current.timer); + expandTimer.current = null; + }; + + const endDrag = () => { + cancelExpandTimer(); + setDragging(null); + setDropTarget(null); + }; + + // A file can't land where it already is; a folder neither, nor inside itself. + const canDropInto = (parentId: string | null) => { + if (!dragging) return false; + if (dragging.kind === 'file') return files.find(file => file.id === dragging.id)?.parentId !== parentId; + if (folders.find(folder => folder.id === dragging.id)?.parentId === parentId) return false; + return parentId !== dragging.id && !ancestorFolderIds(folders, parentId).includes(dragging.id); + }; + + // Hovering a folder opens it after a moment, so a drag can reach its nested folders. + const scheduleExpand = (folderId: string) => { + if (expandTimer.current?.folderId === folderId) return; + cancelExpandTimer(); + if (expandedItems.includes(folderId)) return; + expandTimer.current = { + folderId, + timer: setTimeout(() => setExpandedItems(current => (current.includes(folderId) ? current : [...current, folderId])), EXPAND_DELAY_MS) + }; + }; + + // `item` is what the row drags; `parentId` is where a drop on the row moves things. + const dragPropsFor = (item: DeleteTarget, parentId: string | null, isEditing: boolean): DragProps => ({ + draggable: !isEditing, + onDragStart: event => { + event.stopPropagation(); + event.dataTransfer.setData(DRAG_MIME_TYPE, item.id); + event.dataTransfer.effectAllowed = 'move'; + setDragging(item); + }, + onDragEnd: endDrag, + onDragOver: event => { + if (!dragging) return; + event.stopPropagation(); + if (item.kind === 'folder' && item.id !== dragging.id) scheduleExpand(item.id); + else cancelExpandTimer(); + if (!canDropInto(parentId)) { + event.dataTransfer.dropEffect = 'none'; + setDropTarget(null); + return; + } + event.preventDefault(); + event.dataTransfer.dropEffect = 'move'; + setDropTarget(parentId ?? ROOT_TARGET); + }, + onDrop: event => { + event.preventDefault(); + event.stopPropagation(); + if (dragging && canDropInto(parentId)) { + onMove(dragging.kind, dragging.id, parentId); + if (parentId) setExpandedItems(current => [...new Set([...current, ...ancestorFolderIds(folders, parentId)])]); + } + endDrag(); + } + }); + function renderChildren(parentId: string | null, nodes: FileTreeNode[]): ReactNode { return ( <> @@ -496,6 +586,8 @@ export function PolicyFileExplorer({ setContextMenu({ mouseX: event.clientX, mouseY: event.clientY, target: { kind: 'folder', id: folder.id } }); }} onDeleteRequest={() => setDeleteTarget({ kind: 'folder', id: folder.id })} + dragProps={dragPropsFor({ kind: 'folder', id: folder.id }, folder.id, isEditing)} + dropTarget={dropTarget === folder.id} /> } > @@ -530,6 +622,7 @@ export function PolicyFileExplorer({ setContextMenu({ mouseX: event.clientX, mouseY: event.clientY, target: { kind: 'file', id: file.id } }); }} onDeleteRequest={() => setDeleteTarget({ kind: 'file', id: file.id })} + dragProps={dragPropsFor({ kind: 'file', id: file.id }, file.parentId, isEditing)} /> } /> @@ -562,7 +655,33 @@ export function PolicyFileExplorer({ - + { + if (!dragging) return; + cancelExpandTimer(); + if (!canDropInto(null)) { + setDropTarget(null); + return; + } + event.preventDefault(); + event.dataTransfer.dropEffect = 'move'; + setDropTarget(ROOT_TARGET); + }} + onDrop={event => { + event.preventDefault(); + if (dragging && canDropInto(null)) onMove(dragging.kind, dragging.id, null); + endDrag(); + }} + sx={{ + flex: 1, + overflowY: 'auto', + px: 1, + borderRadius: '4px', + ...(dropTarget === ROOT_TARGET && { outline: '1px solid', outlineColor: 'primary.main', outlineOffset: -1 }) + }} + > setExpandedItems(itemIds)} @@ -631,4 +750,4 @@ export function PolicyFileExplorer({ /> ); -} +}); diff --git a/src/renderer/src/components/PropertiesPanel.tsx b/src/renderer/src/components/PropertiesPanel.tsx index 2fe5d4f..f689f7a 100644 --- a/src/renderer/src/components/PropertiesPanel.tsx +++ b/src/renderer/src/components/PropertiesPanel.tsx @@ -14,6 +14,7 @@ import { BINDING_TARGET_PREFIX } from '../store/canvasSlice'; import type { BlockInstance, Condition, DefinitionEntry, ParamBinding } from '../store/canvasSlice/types'; import type { BlockOutcome } from '../store/edgesSlice/types'; import type { PolicyFile } from '../store/filesSlice/types'; +import { contentSized, plainTextarea } from './contentSized'; import { ConditionSection, type ConditionSlot, FileConditionNotice } from './properties/ConditionSection'; import { EntryList } from './properties/EntryList'; import type { NewClassDefinition } from './properties/NewClassModal'; @@ -138,8 +139,7 @@ export function PropertiesPanel(props: PropertiesPanelProps) { placeholder="Why it's there, or a decision behind it" helperText="Kept as the generated promise's comment." multiline - minRows={1} - maxRows={6} + slotProps={{ input: plainTextarea, htmlInput: { style: contentSized(1, 6) } }} fullWidth size="small" sx={inputSx} diff --git a/src/renderer/src/components/contentSized.ts b/src/renderer/src/components/contentSized.ts new file mode 100644 index 0000000..80fe0cb --- /dev/null +++ b/src/renderer/src/components/contentSized.ts @@ -0,0 +1,12 @@ +import type { CSSProperties } from 'react'; + +// Multi-line fields sized by Chromium (CSS field-sizing) instead of MUI's TextareaAutosize, which +// re-measures after every render, forcing a layout of the page: ~200 ms opening a 150-block file. +// Use both: `slotProps={{ input: plainTextarea, htmlInput: { style: contentSized(1, 6) } }}`. +export const plainTextarea = { inputComponent: 'textarea' } as const; + +export const contentSized = (minRows: number, maxRows: number): CSSProperties => ({ + fieldSizing: 'content', + minHeight: `${minRows}lh`, + maxHeight: `${maxRows}lh` +}); diff --git a/src/renderer/src/components/properties/FileSettingsPanel.tsx b/src/renderer/src/components/properties/FileSettingsPanel.tsx index 50f775c..b86fe99 100644 --- a/src/renderer/src/components/properties/FileSettingsPanel.tsx +++ b/src/renderer/src/components/properties/FileSettingsPanel.tsx @@ -2,6 +2,7 @@ import { Box, Divider, Stack, TextField, Typography } from '@mui/material'; import type { BlockInstance } from '../../store/canvasSlice/types'; import type { PolicyFile } from '../../store/filesSlice/types'; +import { contentSized, plainTextarea } from '../contentSized'; import { ConditionSection } from './ConditionSection'; import { buildClassNameOptions, buildTemplateTokens } from './classOptions'; @@ -39,8 +40,7 @@ export function FileSettingsPanel({ label="Description" size="small" multiline - minRows={2} - maxRows={8} + slotProps={{ input: plainTextarea, htmlInput: { style: contentSized(2, 8) } }} placeholder="What this file is for" helperText="Shown on hovering the file's name." value={file.description ?? ''} diff --git a/src/renderer/src/components/properties/dataBinding.tsx b/src/renderer/src/components/properties/dataBinding.tsx index 9942fc2..7c2cc27 100644 --- a/src/renderer/src/components/properties/dataBinding.tsx +++ b/src/renderer/src/components/properties/dataBinding.tsx @@ -1,8 +1,9 @@ -import { type ReactNode, useState } from 'react'; +import { type ReactNode } from 'react'; import { Box, Button, ListItemText, ListSubheader, Menu, MenuItem, Stack, Typography } from '@mui/material'; import type { BlockParameter } from '../../blocks/types'; +import { useMenuAnchor } from '../../hooks/useMenuAnchor'; // `nameParam`: the descriptor's entries.name_param — a defined name, never computed. export function isBindable(parameter: BlockParameter, nameParam?: string): boolean { @@ -21,13 +22,13 @@ export function BindToDataLink({ onBind: (valueSourceId: string) => void; sources: { badge?: string; id: string; label: string }[]; }) { - const [anchor, setAnchor] = useState(null); + const menu = useMenuAnchor(); return ( or - setAnchor(null)}> - Compute {fieldLabel} from: - {sources.map(source => ( - { - onBind(source.id); - setAnchor(null); - }} - > - - - ))} - + {menu.mounted && ( + + Compute {fieldLabel} from: + {sources.map(source => ( + { + onBind(source.id); + menu.close(); + }} + > + + + ))} + + )} ); } diff --git a/src/renderer/src/components/properties/decorators.tsx b/src/renderer/src/components/properties/decorators.tsx index 5b82974..a1c557c 100644 --- a/src/renderer/src/components/properties/decorators.tsx +++ b/src/renderer/src/components/properties/decorators.tsx @@ -7,6 +7,7 @@ import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import { Box, Button, IconButton, ListItemText, Menu, MenuItem, Stack, TextField, Typography } from '@mui/material'; import { decorators } from '../../blocks/decorators'; +import { useMenuAnchor } from '../../hooks/useMenuAnchor'; import type { DecoratorInstance } from '../../store/canvasSlice/types'; import { ParameterField } from './paramFields'; @@ -71,7 +72,7 @@ const DECORATOR_GROUPS: { label: string; note: string; type: 'slist' | 'string' ]; export function AddDecoratorButton({ chainType, onAdd }: { chainType: 'slist' | 'string'; onAdd: (decoratorId: string) => void }) { - const [anchorEl, setAnchorEl] = useState(null); + const menu = useMenuAnchor(); const [search, setSearch] = useState(''); const query = search.trim().toLowerCase(); const matches = (decorator: (typeof decorators)[number]) => @@ -83,51 +84,53 @@ export function AddDecoratorButton({ chainType, onAdd }: { chainType: 'slist' | size="small" startIcon={} onClick={event => { - setAnchorEl(event.currentTarget); + menu.open(event.currentTarget); setSearch(''); }} sx={{ alignSelf: 'flex-start' }} > Add transform - setAnchorEl(null)}> - - setSearch(event.target.value)} - onKeyDown={event => event.stopPropagation()} - fullWidth - /> - - {DECORATOR_GROUPS.flatMap(group => { - const items = decorators.filter(decorator => decorator.input_type === group.type && matches(decorator)); - if (items.length === 0) return []; - const enabled = group.type === chainType; - return [ - - {group.label} - {!enabled && {group.note}} - , - ...items.map(decorator => ( - { - onAdd(decorator.id); - setAnchorEl(null); - }} - > - - {decorator.badge && {decorator.badge}} - - )) - ]; - })} - + {menu.mounted && ( + + + setSearch(event.target.value)} + onKeyDown={event => event.stopPropagation()} + fullWidth + /> + + {DECORATOR_GROUPS.flatMap(group => { + const items = decorators.filter(decorator => decorator.input_type === group.type && matches(decorator)); + if (items.length === 0) return []; + const enabled = group.type === chainType; + return [ + + {group.label} + {!enabled && {group.note}} + , + ...items.map(decorator => ( + { + onAdd(decorator.id); + menu.close(); + }} + > + + {decorator.badge && {decorator.badge}} + + )) + ]; + })} + + )} ); } diff --git a/src/renderer/src/components/properties/paramFields.tsx b/src/renderer/src/components/properties/paramFields.tsx index f5240f3..0943901 100644 --- a/src/renderer/src/components/properties/paramFields.tsx +++ b/src/renderer/src/components/properties/paramFields.tsx @@ -3,6 +3,7 @@ import { type ReactNode, useState } from 'react'; import { Autocomplete, Box, Checkbox, FormControlLabel, InputAdornment, MenuItem, TextField, Typography } from '@mui/material'; import { type BlockParameter, optionHelp, optionLabel, optionValue } from '../../blocks/types'; +import { contentSized, plainTextarea } from '../contentSized'; import type { TemplateToken } from '../editor/templateTokens'; import { CasesField } from './CasesField'; import type { NewClassDefinition } from './NewClassModal'; @@ -162,12 +163,11 @@ function renderTextField({ parameter, value, onOpenEditor, label, error }: Field helperText={error ?? (parameter.help && !parameter.mustache ? `${parameter.help} Click to edit.` : 'Click to edit.')} fullWidth multiline - minRows={3} - maxRows={3} size="small" onClick={onOpenEditor} slotProps={{ - htmlInput: { style: { fontFamily: 'monospace', cursor: 'pointer' }, readOnly: true } + input: plainTextarea, + htmlInput: { style: { ...contentSized(3, 3), fontFamily: 'monospace', cursor: 'pointer' }, readOnly: true } }} sx={inputSx} /> diff --git a/src/renderer/src/hooks/useLatest.ts b/src/renderer/src/hooks/useLatest.ts new file mode 100644 index 0000000..8b753df --- /dev/null +++ b/src/renderer/src/hooks/useLatest.ts @@ -0,0 +1,16 @@ +import { useCallback, useLayoutEffect, useRef } from 'react'; + +/** A ref always holding the last rendered `value`: for callbacks created once that must call the current handlers. */ +export function useLatest(value: T) { + const latest = useRef(value); + useLayoutEffect(() => { + latest.current = value; + }); + return latest; +} + +/** A function that keeps its identity across renders but always runs the last rendered `callback`. */ +export function useStableCallback(callback: (...args: A) => R): (...args: A) => R { + const latest = useLatest(callback); + return useCallback((...args: A) => latest.current(...args), [latest]); +} diff --git a/src/renderer/src/hooks/useMenuAnchor.ts b/src/renderer/src/hooks/useMenuAnchor.ts new file mode 100644 index 0000000..a60bc81 --- /dev/null +++ b/src/renderer/src/hooks/useMenuAnchor.ts @@ -0,0 +1,20 @@ +import { useState } from 'react'; + +/** + * A menu's anchor, and whether to mount the menu at all: from its first opening on, so the + * closing transition still plays. A closed MUI Menu still renders a Popover and a Modal, which + * on every canvas block or arrow adds hundreds of components to each re-render. + */ +export function useMenuAnchor() { + const [anchor, setAnchor] = useState(null); + const [mounted, setMounted] = useState(false); + return { + anchor, + mounted, + open: (element: HTMLElement) => { + setMounted(true); + setAnchor(element); + }, + close: () => setAnchor(null) + }; +} diff --git a/src/renderer/src/pages/ProjectView.tsx b/src/renderer/src/pages/ProjectView.tsx index 4ff810b..3999718 100644 --- a/src/renderer/src/pages/ProjectView.tsx +++ b/src/renderer/src/pages/ProjectView.tsx @@ -38,6 +38,7 @@ import { ConfirmDialog } from '../components/dialogs/ConfirmDialog'; import { ConditionSection } from '../components/properties/ConditionSection'; import { RunsWhenSection } from '../components/properties/RunsWhenSection'; import { buildClassNameOptions } from '../components/properties/classOptions'; +import { useStableCallback } from '../hooks/useLatest'; import { LEFT_SIDEBAR_MAX, LEFT_SIDEBAR_MIN, @@ -103,10 +104,12 @@ import { fileConditionModeChanged, fileConditionRemoved, fileDescriptionChanged, + fileMoved, fileRemoved, fileRenamed, fileSelected, folderAdded, + folderMoved, folderRemoved, folderRenamed } from '../store/filesSlice'; @@ -281,6 +284,9 @@ interface ProjectViewProps { onSave: () => Promise; } +// A module constant: a new options object each render would make new sensors, and re-render every draggable. +const POINTER_SENSOR_OPTIONS = { activationConstraint: { distance: 8 } }; + /** The open project; saving it into cfbs.json is owned by App (project/useProjectSession.ts). */ export default function ProjectView({ dirty, locked, onConnectAgent, onOpenSettings, onReload, onSave }: ProjectViewProps) { const dispatch = useAppDispatch(); @@ -399,7 +405,7 @@ export default function ProjectView({ dirty, locked, onConnectAgent, onOpenSetti })); }; - const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } })); + const sensors = useSensors(useSensor(PointerSensor, POINTER_SENSOR_OPTIONS)); const sizeOf = (instance: BlockInstance) => layoutSizes[instance.instanceId] ?? { width: NODE_WIDTH, height: estimateNodeHeight(instance, blockDescriptorsById.get(instance.blockId)) }; @@ -447,6 +453,10 @@ export default function ProjectView({ dirty, locked, onConnectAgent, onOpenSetti dispatch(folderRenamed({ folderId, name })); }; + const handleMove = (kind: 'file' | 'folder', id: string, parentId: string | null) => { + dispatch(kind === 'file' ? fileMoved({ fileId: id, parentId }) : folderMoved({ folderId: id, parentId })); + }; + const handleDeleteFile = (fileId: string) => { asOneStep(() => { dispatch(fileRemoved({ fileId })); @@ -695,7 +705,12 @@ export default function ProjectView({ dirty, locked, onConnectAgent, onOpenSetti useEffect(() => () => clearTimeout(statusMessageTimeoutRef.current), []); - const duplicateKeys = useMemo(() => duplicateDefinitionKeys(allInstances, blockDescriptorsById, currentFileId), [allInstances, currentFileId]); + // A new Set only when its keys change: every block card depends on it. + const duplicateList = useMemo( + () => [...duplicateDefinitionKeys(allInstances, blockDescriptorsById, currentFileId)].sort().join('\n'), + [allInstances, currentFileId] + ); + const duplicateKeys = useMemo(() => new Set(duplicateList ? duplicateList.split('\n') : []), [duplicateList]); const handleEscape = () => { if (selectedGroup) setSelectedGroupId(null); @@ -773,6 +788,34 @@ export default function ProjectView({ dirty, locked, onConnectAgent, onOpenSetti } }); + // New handlers re-render every draggable (the palette's rows) through dnd-kit's context. + const dndHandlers = { + onDragStart: useStableCallback(handleDragStart), + onDragEnd: useStableCallback(handleDragEnd), + onDragCancel: useStableCallback(() => setDragPreview(null)) + }; + // The sidebars are memoized: they get stable callbacks, and counts that change only with them. + const paletteAddBlock = useStableCallback((descriptor: BlockDescriptor) => addBlockToCanvas(descriptor)); + const blockCountsJson = useMemo(() => { + const counts: Record = {}; + for (const instance of allInstances) counts[instance.fileId] = (counts[instance.fileId] ?? 0) + 1; + return JSON.stringify(counts); + }, [allInstances]); + const blockCounts = useMemo(() => JSON.parse(blockCountsJson) as Record, [blockCountsJson]); + const explorer = { + onSelectFile: useStableCallback((fileId: string) => { + dispatch(fileSelected({ fileId })); + setSelectedInstanceId(null); + }), + onAddFile: useStableCallback(handleAddFile), + onAddFolder: useStableCallback(handleAddFolder), + onRenameFile: useStableCallback(handleRenameFile), + onRenameFolder: useStableCallback(handleRenameFolder), + onDeleteFile: useStableCallback(handleDeleteFile), + onDeleteFolder: useStableCallback(handleDeleteFolder), + onMove: useStableCallback(handleMove) + }; + if (!project) return null; const selectedInstance = instances.find(instance => instance.instanceId === selectedInstanceId); @@ -948,7 +991,7 @@ export default function ProjectView({ dirty, locked, onConnectAgent, onOpenSetti ); return ( - setDragPreview(null)}> + - + @@ -990,17 +1033,15 @@ export default function ProjectView({ dirty, locked, onConnectAgent, onOpenSetti files={files} folders={folders} currentFileId={currentFileId} - instances={allInstances} - onSelectFile={fileId => { - dispatch(fileSelected({ fileId })); - setSelectedInstanceId(null); - }} - onAddFile={handleAddFile} - onAddFolder={handleAddFolder} - onRenameFile={handleRenameFile} - onRenameFolder={handleRenameFolder} - onDeleteFile={handleDeleteFile} - onDeleteFolder={handleDeleteFolder} + blockCounts={blockCounts} + onSelectFile={explorer.onSelectFile} + onAddFile={explorer.onAddFile} + onAddFolder={explorer.onAddFolder} + onRenameFile={explorer.onRenameFile} + onRenameFolder={explorer.onRenameFolder} + onDeleteFile={explorer.onDeleteFile} + onDeleteFolder={explorer.onDeleteFolder} + onMove={explorer.onMove} /> diff --git a/src/renderer/src/store/filesSlice/index.ts b/src/renderer/src/store/filesSlice/index.ts index c2525f7..7ddc6e8 100644 --- a/src/renderer/src/store/filesSlice/index.ts +++ b/src/renderer/src/store/filesSlice/index.ts @@ -110,6 +110,25 @@ const filesSlice = createSlice({ if (!sanitized) return; folder.name = uniqueSiblingName(siblingFolderNames(state, folder.parentId, folder.id), sanitized); }, + // A name already taken in the target folder gets a " 2" suffix, as on create. + fileMoved(state, action: PayloadAction<{ fileId: string; parentId: string | null }>) { + const file = state.files.find(item => item.id === action.payload.fileId); + const { parentId } = action.payload; + if (!file || file.parentId === parentId || (parentId && !state.folders.some(folder => folder.id === parentId))) return; + file.name = uniqueSiblingName(siblingFileNames(state, parentId, file.id), file.name); + file.parentId = parentId; + }, + // Ignored when the target is the folder itself or nested inside it. + folderMoved(state, action: PayloadAction<{ folderId: string; parentId: string | null }>) { + const folder = state.folders.find(item => item.id === action.payload.folderId); + const { parentId } = action.payload; + if (!folder || folder.parentId === parentId) return; + if (parentId && !state.folders.some(item => item.id === parentId)) return; + const { folderIds } = collectFolderDescendants(state.files, state.folders, folder.id); + if (parentId === folder.id || (parentId && folderIds.includes(parentId))) return; + folder.name = uniqueSiblingName(siblingFolderNames(state, parentId, folder.id), folder.name); + folder.parentId = parentId; + }, // The caller is responsible for also dispatching // canvasSlice's blocksRemovedForFile — this slice doesn't know about blocks. fileRemoved(state, action: PayloadAction<{ fileId: string }>) { @@ -150,6 +169,8 @@ export const { fileSelected, fileRenamed, folderRenamed, + fileMoved, + folderMoved, fileRemoved, folderRemoved } = filesSlice.actions;