From eaef11e1e3776cbee7930874b1ea703280f2e01a Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 1 Jun 2026 16:43:09 +0300 Subject: [PATCH 01/41] refactor: update dependencies and improve graph handling - Changed the dependency for @falkordb/canvas to a local file reference. - Introduced graphDataToData function to standardize graph data handling across components. - Removed cooldownTicks state management and replaced it with animation state in multiple components. - Enhanced the Chat component to utilize createMessage for message creation, ensuring unique IDs. - Updated the Toolbar component to include animation controls and layout options. - Modified the Vite configuration to point to the production API endpoint. --- app/package-lock.json | 205 ++++-------------------------- app/package.json | 2 +- app/src/App.tsx | 25 ++-- app/src/components/ForceGraph.tsx | 34 ++--- app/src/components/chat.tsx | 98 +++++++------- app/src/components/code-graph.tsx | 31 ++--- app/src/components/graphView.tsx | 118 +++++++++++------ app/src/components/toolbar.tsx | 183 ++++++++++++++++++++++++-- app/src/lib/utils.ts | 6 + 9 files changed, 380 insertions(+), 322 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index d22f1851..76d82ccc 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -8,7 +8,7 @@ "name": "code-graph", "version": "0.3.0", "dependencies": { - "@falkordb/canvas": "^0.0.44", + "@falkordb/canvas": "file:../../falkordb-canvas", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -52,6 +52,28 @@ "vite": "^6.3.5" } }, + "../../falkordb-canvas": { + "name": "@falkordb/canvas", + "version": "0.0.0-dev", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "force-graph": "^1.44.4", + "react": "^19.2.3" + }, + "devDependencies": { + "@types/d3": "^7.4.3", + "@types/react": "^19.2.7", + "@typescript-eslint/eslint-plugin": "^8.18.2", + "@typescript-eslint/parser": "^8.18.2", + "eslint": "^10.0.2", + "jsdom": "^29.0.2", + "promise-retry": "^2.0.1", + "tsup": "^8.5.1", + "typescript": "^6.0.3", + "vitest": "^4.1.4" + } + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -516,24 +538,8 @@ } }, "node_modules/@falkordb/canvas": { - "version": "0.0.44", - "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.0.44.tgz", - "integrity": "sha512-ljVc6h+4kkTJw7cBSWth5soRjnV1LtURWa3x9DFXqbLXmVtItBy5EdnSvmhp6GCE43ZBmdVY8o12QDHxU2/vqQ==", - "license": "MIT", - "dependencies": { - "d3": "^7.9.0", - "force-graph": "^1.44.4", - "react": "^19.2.3" - } - }, - "node_modules/@falkordb/canvas/node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "resolved": "../../falkordb-canvas", + "link": true }, "node_modules/@floating-ui/core": { "version": "1.7.5", @@ -2302,12 +2308,6 @@ "@swc/counter": "^0.1.3" } }, - "node_modules/@tweenjs/tween.js": { - "version": "25.0.0", - "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", - "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", - "license": "MIT" - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2336,7 +2336,6 @@ "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -2352,7 +2351,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -2364,7 +2362,6 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -2406,15 +2403,6 @@ "vite": "^4 || ^5 || ^6 || ^7" } }, - "node_modules/accessor-fn": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.3.tgz", - "integrity": "sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -2511,16 +2499,6 @@ "node": ">=6.0.0" } }, - "node_modules/bezier-js": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-6.1.4.tgz", - "integrity": "sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==", - "license": "MIT", - "funding": { - "type": "individual", - "url": "https://github.com/Pomax/bezierjs/blob/master/FUNDING.md" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2565,7 +2543,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2610,18 +2587,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/canvas-color-tracker": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.3.2.tgz", - "integrity": "sha512-ryQkDX26yJ3CXzb3hxUVNlg1NKE4REc5crLBq661Nxzr8TNd236SaEf2ffYLXyI5tSABSeguHLqcVq4vf9L3Zg==", - "license": "MIT", - "dependencies": { - "tinycolor2": "^1.6.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/canvas2svg": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/canvas2svg/-/canvas2svg-1.0.16.tgz", @@ -2864,12 +2829,6 @@ "node": ">=12" } }, - "node_modules/d3-binarytree": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", - "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", - "license": "MIT" - }, "node_modules/d3-brush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", @@ -3013,22 +2972,6 @@ "node": ">=12" } }, - "node_modules/d3-force-3d": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", - "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", - "license": "MIT", - "dependencies": { - "d3-binarytree": "1", - "d3-dispatch": "1 - 3", - "d3-octree": "1", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-format": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", @@ -3071,12 +3014,6 @@ "node": ">=12" } }, - "node_modules/d3-octree": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", - "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", - "license": "MIT" - }, "node_modules/d3-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", @@ -3147,7 +3084,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -3270,8 +3206,7 @@ "version": "8.6.0", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/embla-carousel-react": { "version": "8.6.0", @@ -3409,46 +3344,6 @@ "node": ">=8" } }, - "node_modules/float-tooltip": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/float-tooltip/-/float-tooltip-1.7.5.tgz", - "integrity": "sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==", - "license": "MIT", - "dependencies": { - "d3-selection": "2 - 3", - "kapsule": "^1.16", - "preact": "10" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/force-graph": { - "version": "1.51.1", - "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.51.1.tgz", - "integrity": "sha512-uEEX8iRzgq1IKRISOw6RrB2RLMhcI25xznQYrCTVvxZHZZ+A2jH6qIolYuwavVxAMi64pFp2yZm4KFVdD993cg==", - "license": "MIT", - "dependencies": { - "@tweenjs/tween.js": "18 - 25", - "accessor-fn": "1", - "bezier-js": "3 - 6", - "canvas-color-tracker": "^1.3", - "d3-array": "1 - 3", - "d3-drag": "2 - 3", - "d3-force-3d": "2 - 3", - "d3-scale": "1 - 4", - "d3-scale-chromatic": "1 - 3", - "d3-selection": "2 - 3", - "d3-zoom": "2 - 3", - "float-tooltip": "^1.7", - "index-array-by": "1", - "kapsule": "^1.16", - "lodash-es": "4" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -3594,15 +3489,6 @@ "node": ">=0.10.0" } }, - "node_modules/index-array-by": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/index-array-by/-/index-array-by-1.4.2.tgz", - "integrity": "sha512-SP23P27OUKzXWEC/TOyWlwLviofQkCSCKONnc62eItjp69yCZZPqDQtr3Pw5gJDnPeUMqExmKydNZaJO0FU9pw==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -3724,7 +3610,6 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -3735,18 +3620,6 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, - "node_modules/kapsule": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.16.3.tgz", - "integrity": "sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==", - "license": "MIT", - "dependencies": { - "lodash-es": "4" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -3970,7 +3843,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -4108,16 +3980,6 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, - "node_modules/preact": { - "version": "10.28.4", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.4.tgz", - "integrity": "sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -4177,7 +4039,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -4202,7 +4063,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -4221,7 +4081,8 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-json-tree": { "version": "0.19.0", @@ -4591,7 +4452,6 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "license": "MIT", - "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -4663,12 +4523,6 @@ "node": ">=0.8" } }, - "node_modules/tinycolor2": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", - "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -4707,7 +4561,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -4868,7 +4721,6 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -4962,7 +4814,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, diff --git a/app/package.json b/app/package.json index a32bbefb..0d7c2e83 100644 --- a/app/package.json +++ b/app/package.json @@ -11,7 +11,7 @@ "preview": "vite preview" }, "dependencies": { - "@falkordb/canvas": "^0.0.44", + "@falkordb/canvas": "file:../../falkordb-canvas", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", diff --git a/app/src/App.tsx b/app/src/App.tsx index 156b1313..0ad845e8 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -15,6 +15,7 @@ import { Labels } from './components/labels'; import { Toolbar } from './components/toolbar'; import { cn, GraphRef, Message, Path, PathData, PathNode } from '@/lib/utils'; import type { GraphNode } from '@falkordb/canvas'; +import { graphDataToData } from '@falkordb/canvas'; import { Toaster } from '@/components/ui/toaster'; import GTM from './GTM'; import { Button } from '@/components/ui/button'; @@ -79,7 +80,8 @@ export default function App() { const [menuOpen, setMenuOpen] = useState(false) const [chatOpen, setChatOpen] = useState(false) const [searchNode, setSearchNode] = useState({}); - const [cooldownTicks, setCooldownTicks] = useState(0) + const [cooldownTicks, setCooldownTicks] = useState(undefined) + const [animation, setAnimation] = useState(false) const [optionsOpen, setOptionsOpen] = useState(false) const [messages, setMessages] = useState([]); const [query, setQuery] = useState(''); @@ -174,8 +176,6 @@ export default function App() { const g = Graph.create(json.entities, graphName) setGraph(g) - if (cooldownTicks === 0) setCooldownTicks(-1) - setIsPathResponse(false) chatPanel.current?.expand() // @ts-ignore @@ -225,8 +225,6 @@ export default function App() { if (!chartNode) { chartNode = graph.extend({ nodes: [node], edges: [] }).nodes[0] - if (cooldownTicks === 0) setCooldownTicks(-1) - setZoomedNodes([chartNode]) graph.visibleLinks(true, [chartNode!.id]) @@ -251,7 +249,7 @@ export default function App() { currentData.nodes.push(graphNode) } - canvas.setGraphData(currentData) + canvas.setGraphData(graphDataToData(currentData)) setTimeout(() => { @@ -280,7 +278,7 @@ export default function App() { } }) - canvas.setGraphData(currentData) + canvas.setGraphData(graphDataToData(currentData)) } setTimeout(() => { @@ -324,9 +322,8 @@ export default function App() { } }); - canvas.setGraphData(currentData); + canvas.setGraphData(graphDataToData(currentData)); - setCooldownTicks(cooldownTicks === undefined ? undefined : -1); setHasHiddenElements(graph.getElements().some(element => !element.visible)); } @@ -540,6 +537,8 @@ export default function App() { setSearchNode={setSearchNode} cooldownTicks={cooldownTicks} setCooldownTicks={setCooldownTicks} + animation={animation} + setAnimation={setAnimation} onCategoryClick={(name, show) => onCategoryClick(name, show, desktopChartRef)} handleDownloadImage={handleDownloadImage} zoomedNodes={zoomedNodes} @@ -574,7 +573,6 @@ export default function App() { setIsPathResponse={setIsPathResponse} paths={paths} setPaths={setPaths} - setCooldownTicks={setCooldownTicks} /> @@ -667,6 +665,8 @@ export default function App() { searchNode={searchNode} cooldownTicks={cooldownTicks} setCooldownTicks={setCooldownTicks} + animation={animation} + setAnimation={setAnimation} onCategoryClick={(name, show) => onCategoryClick(name, show, mobileChartRef)} handleDownloadImage={handleDownloadImage} zoomedNodes={zoomedNodes} @@ -705,7 +705,6 @@ export default function App() { setChatOpen={setChatOpen} paths={paths} setPaths={setPaths} - setCooldownTicks={setCooldownTicks} /> @@ -723,8 +722,8 @@ export default function App() { number[] onZoom: () => void onEngineStop: () => void - cooldownTicks: number | undefined + animation: boolean backgroundColor?: string foregroundColor?: string } + const convertToCanvasData = (graphData: GraphData): Data => ({ nodes: graphData.nodes.filter(n => n.visible).map(({ id, category, color, visible, isPath, isPathSelected, data }) => ({ id, @@ -66,7 +67,7 @@ export default function ForceGraph({ nodeCanvasObject, nodePointerAreaPaint, linkLineDash, - cooldownTicks, + animation, backgroundColor = "#FFFFFF", foregroundColor = "#000000" }: Props) { @@ -94,12 +95,12 @@ export default function ForceGraph({ canvasRef.current.setForegroundColor(foregroundColor) }, [canvasRef, backgroundColor, foregroundColor, canvasLoaded]) - // Update cooldown ticks + // Update animation state on canvas useEffect(() => { if (!canvasRef.current || !canvasLoaded) return - canvasRef.current.setCooldownTicks(cooldownTicks === -1 ? undefined : cooldownTicks) - }, [canvasRef, cooldownTicks, canvasLoaded]) + canvasRef.current.setAnimation(animation) + }, [canvasRef, animation, canvasLoaded]) // Map node click handler const handleNodeClick = useCallback((node: GraphNode, event: MouseEvent) => { @@ -158,23 +159,24 @@ export default function ForceGraph({ useEffect(() => { if (!canvasRef.current || !canvasLoaded) return canvasRef.current.setConfig({ - autoStopOnSettle: false, // nodes will display node.data.captionsKeys in the canvas captionsKeys: ["name", "title"], - onNodeClick: handleNodeClick, - onNodeRightClick: handleNodeRightClick, - onNodeHover: handleNodeHover, isNodeSelected: isNodeSelected, - onLinkClick: handleLinkClick, - onLinkRightClick: handleLinkRightClick, - onLinkHover: handleLinkHover, isLinkSelected: isLinkSelected, - onBackgroundClick, - onBackgroundRightClick, - onEngineStop: handleEngineStop, node: { nodeCanvasObject, nodePointerAreaPaint }, linkLineDash, - onZoom + eventHandlers: { + onNodeClick: handleNodeClick, + onNodeRightClick: handleNodeRightClick, + onNodeHover: handleNodeHover, + onLinkClick: handleLinkClick, + onLinkRightClick: handleLinkRightClick, + onLinkHover: handleLinkHover, + onBackgroundClick, + onBackgroundRightClick, + onEngineStop: handleEngineStop, + onZoom + } }) }, [ handleNodeClick, diff --git a/app/src/components/chat.tsx b/app/src/components/chat.tsx index 0ecc49c2..f5057c70 100644 --- a/app/src/components/chat.tsx +++ b/app/src/components/chat.tsx @@ -1,7 +1,7 @@ import { toast } from "@/components/ui/use-toast"; import { Dispatch, FormEvent, SetStateAction, useEffect, useRef, useState } from "react"; import { AlignLeft, ArrowRight, ChevronDown, Lightbulb, Loader2, Undo2 } from "lucide-react"; -import { Message, MessageTypes, Path, PathData, PATH_COLOR } from "@/lib/utils"; +import { Message, MessageTypes, Path, PathData, PATH_COLOR, createMessage } from "@/lib/utils"; import Input from "./Input"; import { Graph, GraphData, Node } from "./model"; import { cn, GraphRef } from "@/lib/utils"; @@ -10,9 +10,9 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "@/compon import { Button } from "@/components/ui/button"; const AUTH_HEADERS: HeadersInit = import.meta.env.VITE_SECRET_TOKEN - ? { 'Authorization': `Bearer ${import.meta.env.VITE_SECRET_TOKEN}` } - : {}; -import { dataToGraphData, GraphLink, GraphNode } from "@falkordb/canvas"; + ? { 'Authorization': `Bearer ${import.meta.env.VITE_SECRET_TOKEN}` } + : {}; +import { dataToGraphData, graphDataToData, GraphLink, GraphNode } from "@falkordb/canvas"; interface Props { repo: string @@ -29,7 +29,6 @@ interface Props { setQuery: Dispatch> selectedPath: PathData | undefined setSelectedPath: Dispatch> - setCooldownTicks: Dispatch> setChatOpen?: Dispatch> paths: PathData[] setPaths: Dispatch> @@ -48,29 +47,30 @@ type RemoveLastPathResult = { insertIndex: number } -const RemoveLastPath = (messages: Message[]): RemoveLastPathResult => { - // Find the last Path message so we know where the user was in the conversation +const RemoveLastPath = (messages: Message[], includeQuery = false): RemoveLastPathResult => { + // Find the last Path marker (the pending "select start/end" state) const index = messages.findLastIndex((m) => m.type === MessageTypes.Path) if (index === -1) { return { messages, insertIndex: messages.length } } - const groupStart = Math.max(0, index - 2) - const hasMessagesAfter = index < messages.length - 1 - const cleaned = [...messages.slice(0, groupStart), ...messages.slice(index + 1)] - - // Recurse to strip any remaining Path groups - const { messages: finalMessages } = RemoveLastPath(cleaned) + // The Path marker is always preceded by Response("Please select..."). + // Optionally also remove the Query("Create a path") before it. + let groupStart = index; + if (index > 0 && messages[index - 1].type === MessageTypes.Response) { + groupStart = index - 1; + if (includeQuery && groupStart > 0 && messages[groupStart - 1].type === MessageTypes.Query) { + groupStart = groupStart - 1; + } + } - // If there were messages after the Path group, inject the answer there; - // otherwise just append at the end - const insertIndex = hasMessagesAfter ? groupStart : finalMessages.length + const cleaned = [...messages.slice(0, groupStart), ...messages.slice(index + 1)] - return { messages: finalMessages, insertIndex } + return { messages: cleaned, insertIndex: cleaned.length } } -export function Chat({ messages, setMessages, query, setQuery, selectedPath, setSelectedPath, setChatOpen, repo, path, setPath, graph, selectedPathId, isPathResponse, setIsPathResponse, setCooldownTicks, canvasRef, paths, setPaths }: Props) { +export function Chat({ messages, setMessages, query, setQuery, selectedPath, setSelectedPath, setChatOpen, repo, path, setPath, graph, selectedPathId, isPathResponse, setIsPathResponse, canvasRef, paths, setPaths }: Props) { const [sugOpen, setSugOpen] = useState(false); @@ -225,11 +225,14 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set } }); - canvas.setGraphData(currentData) + canvas.setGraphData(graphDataToData(currentData)) setTimeout(() => { + const filteredNodes = currentData.nodes.filter((n: any) => pNodeIds.has(n.id)); + console.log('[zoomToFit] filtered nodes:', filteredNodes.map((n: any) => ({ id: n.id, x: n.x, y: n.y }))); + console.log('[zoomToFit] pNodeIds:', [...pNodeIds]); canvas.zoomToFit(2, (n: GraphNode) => pNodeIds.has(n.id)); - }, 0) + }, 300) setChatOpen && setChatOpen(false) } @@ -263,7 +266,7 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set setQuery("") - setMessages((messages) => [...messages, { text: q, type: MessageTypes.Query }, { type: MessageTypes.Pending }]); + setMessages((messages) => [...messages, createMessage({ text: q, type: MessageTypes.Query }), createMessage({ type: MessageTypes.Pending })]); const result = await fetch(`/api/chat`, { method: 'POST', @@ -277,7 +280,7 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set if (!result.ok) { setMessages((prev) => { prev = [...prev.slice(0, -1)]; - return [...prev, { type: MessageTypes.Response, text: "Sorry but I couldn't answer your question, please try rephrasing." }]; + return [...prev, createMessage({ type: MessageTypes.Response, text: "Sorry but I couldn't answer your question, please try rephrasing." })]; }); return } @@ -286,7 +289,7 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set setMessages((prev) => { prev = prev.slice(0, -1); - return [...prev, { text: json.response, type: MessageTypes.Response }]; + return [...prev, createMessage({ text: json.response, type: MessageTypes.Response })]; }); } @@ -300,17 +303,17 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set if (!path?.start?.id || !path.end?.id) return - const pathMessage = [{ + const pathMessage = [createMessage({ type: MessageTypes.Response, text: "Please select a starting point and the end point. Select or press relevant item on the graph" - }, { type: MessageTypes.Path }] + }), createMessage({ type: MessageTypes.Path })] setPath(undefined) let insertIndex = 0 setMessages((prev) => { const { messages, insertIndex: idx } = RemoveLastPath(prev) insertIndex = idx - const pending: Message = { type: MessageTypes.Pending } + const pending: Message = createMessage({ type: MessageTypes.Pending }) return [...messages.slice(0, insertIndex), pending, ...messages.slice(insertIndex)] }) @@ -367,7 +370,7 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set setPaths(formattedPaths) setMessages((prev) => [ ...prev.slice(0, insertIndex), - { type: MessageTypes.PathResponse, paths: formattedPaths, graphName: graph.Id }, + createMessage({ type: MessageTypes.PathResponse, paths: formattedPaths, graphName: graph.Id }), ...prev.slice(insertIndex + 1), ]); setIsPathResponse(true) @@ -430,14 +433,10 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set ) // Merge with existing data - canvasRef.current?.setGraphData({ + canvasRef.current?.setGraphData(graphDataToData({ nodes: [...currentData.nodes, ...newGraphData.nodes], links: [...currentData.links, ...newGraphData.links] - }) - - if (elements.nodes.length !== 0 || elements.links.length !== 0) { - setCooldownTicks(-1) - } + })) setTimeout(() => { const nodesMap = new Map(formattedPaths.flatMap(p => p.nodes.map((n: Node) => [n.id, n]))) @@ -457,9 +456,9 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set setSugOpen(false) setMessages(prev => { - const { messages, insertIndex } = RemoveLastPath(prev) - const queryMsg: Message = { type: MessageTypes.Query, text: "Create a path" } - return [...messages.slice(0, insertIndex), queryMsg, ...messages.slice(insertIndex)] + const { messages } = RemoveLastPath(prev, true) + const queryMsg: Message = createMessage({ type: MessageTypes.Query, text: "Create a path" }) + return [...messages, queryMsg] }) if (isPathResponse) { @@ -480,13 +479,13 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set } }) - canvas.setGraphData(currentData) + canvas.setGraphData(graphDataToData(currentData)) } - setMessages(prev => [...prev, { + setMessages(prev => [...prev, createMessage({ type: MessageTypes.Response, text: "Please select a starting point and the end point. Select or press relevant item on the graph" - }, { type: MessageTypes.Path }]) + }), createMessage({ type: MessageTypes.Path })]) setPath({}) }} > @@ -513,7 +512,7 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set const getMessage = (message: Message, index?: number) => { switch (message.type) { case MessageTypes.Query: return ( -
+

You

@@ -522,13 +521,12 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set
) case MessageTypes.Response: return ( -
+

Answer

) case MessageTypes.Text: return ( -

{message.text}

+

{message.text}

) case MessageTypes.Path: { return ( -
+
+
+
+ +

Answer

+
{ message.paths && message.paths.map((p, i: number) => ( @@ -577,7 +579,7 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set className={cn( "flex text-wrap border p-2 gap-2 rounded-md", p.nodes.length === selectedPath?.nodes.length && - selectedPath?.nodes.every(node => p?.nodes.some((n) => n.id === node.id)) && + selectedPath?.nodes.every((node, i) => p?.nodes[i]?.id === node.id) && "border-[#ffde21] bg-[#ffde2133]", message.graphName !== graph.Id && "opacity-50 bg-secondary" )} @@ -592,7 +594,7 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set return; } - if (selectedPath?.nodes.every(node => p?.nodes.some((n) => n.id === node.id)) && selectedPath.nodes.length === p.nodes.length) return + if (selectedPath?.nodes.every((node, i) => p?.nodes[i]?.id === node.id) && selectedPath.nodes.length === p.nodes.length) return if (!isPathResponse) { setIsPathResponse(undefined) @@ -615,7 +617,7 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set
) default: return ( -
+
Thinking... diff --git a/app/src/components/code-graph.tsx b/app/src/components/code-graph.tsx index c0b2a083..4dc3b38a 100644 --- a/app/src/components/code-graph.tsx +++ b/app/src/components/code-graph.tsx @@ -14,7 +14,7 @@ import { Checkbox } from '@/components/ui/checkbox'; import type { Position } from "./graphView"; import { prepareArg } from '../utils'; import { GraphRef } from "@/lib/utils"; -import { dataToGraphData } from "@falkordb/canvas"; +import { dataToGraphData, graphDataToData } from "@falkordb/canvas"; import type { Node as CanvasNode, Link as CanvasLink, GraphData as CanvasData } from "@falkordb/canvas"; import GraphView from "./graphView"; @@ -44,6 +44,8 @@ interface Props { setSearchNode: Dispatch> cooldownTicks: number | undefined setCooldownTicks: Dispatch> + animation: boolean + setAnimation: (animation: boolean) => void onCategoryClick: (name: string, show: boolean) => void handleDownloadImage: () => void zoomedNodes: Node[] @@ -74,6 +76,8 @@ export function CodeGraph({ setSearchNode, cooldownTicks, setCooldownTicks, + animation, + setAnimation, onCategoryClick, handleDownloadImage, zoomedNodes, @@ -280,12 +284,10 @@ export function CodeGraph({ ) // Merge with existing data - canvasRef.current?.setGraphData({ + canvasRef.current?.setGraphData(graphDataToData({ nodes: [...currentData.nodes, ...newGraphData.nodes], links: [...currentData.links, ...newGraphData.links] - }) - - setCooldownTicks(-1) + })) } else { const deleteNodes = nodes.filter(n => n.expand) if (deleteNodes.length > 0) { @@ -299,8 +301,7 @@ export function CodeGraph({ currentData.nodes = currentData.nodes.filter(node => !deleteIdsMap.has(Number(node.id))) currentData.links = currentData.links.filter(link => !deleteIdsMap.has(Number(link.source.id)) && !deleteIdsMap.has(Number(link.target.id))) - canvasRef.current?.setGraphData(currentData) - setCooldownTicks(-1) + canvasRef.current?.setGraphData(graphDataToData(currentData)) } } } @@ -337,13 +338,12 @@ export function CodeGraph({ }) } - canvas.setGraphData(currentData) + canvas.setGraphData(graphDataToData(currentData)) graph.visibleLinks(false, ids) setHasHiddenElements(true) setSelectedObj(undefined) setSelectedObjects([]) - setCooldownTicks(-1) } return ( @@ -403,9 +403,8 @@ export function CodeGraph({ } }) - canvas.setGraphData(currentData) + canvas.setGraphData(graphDataToData(currentData)) setIsPathResponse(false) - setCooldownTicks(-1) }} > @@ -434,9 +433,8 @@ export function CodeGraph({ element.visible = true }); - canvas.setGraphData(currentData); + canvas.setGraphData(graphDataToData(currentData)); setHasHiddenElements(false); - setCooldownTicks(-1); }} > @@ -475,8 +473,7 @@ export function CodeGraph({ isPathResponse={isPathResponse} selectedPathId={selectedPathId} setSelectedPathId={setSelectedPathId} - cooldownTicks={cooldownTicks} - setCooldownTicks={setCooldownTicks} + animation={animation} setZoomedNodes={setZoomedNodes} zoomedNodes={zoomedNodes} /> @@ -510,8 +507,8 @@ export function CodeGraph({ className="gap-4" canvasRef={canvasRef} handleDownloadImage={handleDownloadImage} - setCooldownTicks={setCooldownTicks} - cooldownTicks={cooldownTicks} + animation={animation} + setAnimation={setAnimation} />
diff --git a/app/src/components/graphView.tsx b/app/src/components/graphView.tsx index 3a08e27f..38fcad64 100644 --- a/app/src/components/graphView.tsx +++ b/app/src/components/graphView.tsx @@ -5,7 +5,7 @@ import { Path, PATH_COLOR } from '@/lib/utils'; import { Fullscreen } from 'lucide-react'; import { GraphRef } from '@/lib/utils'; import ForceGraph from './ForceGraph'; -import { GraphLink, GraphNode } from '@falkordb/canvas'; +import { GraphLink, GraphNode, NODE_SIZE, getContrastTextColor, wrapTextForCircularNode } from '@falkordb/canvas'; import { useTheme } from './theme-provider'; export interface Position { @@ -30,14 +30,18 @@ interface Props { isPathResponse: boolean | undefined selectedPathId: number | undefined setSelectedPathId: (selectedPathId: number) => void - cooldownTicks: number | undefined - setCooldownTicks: Dispatch> + animation: boolean setZoomedNodes: Dispatch> zoomedNodes: Node[] } -const NODE_SIZE = 6; const PADDING = 2; +const FONT_FAMILY = 'SofiaSans, Arial, sans-serif'; +const FONT_WEIGHT_NORMAL = 400; +const FONT_WEIGHT_SELECTED = 700; +const TEXT_FILL_RATIO = 0.85; +const STROKE_WIDTH_SELECTED = 1.5; +const STROKE_WIDTH_UNSELECTED = 0.5; const LIGHT_CANVAS_BACKGROUND = '#FFFFFF'; const DARK_CANVAS_BACKGROUND = '#1A1A1A'; const LIGHT_CANVAS_FOREGROUND = '#000000'; @@ -63,8 +67,7 @@ export default function GraphView({ isPathResponse, selectedPathId, setSelectedPathId, - cooldownTicks, - setCooldownTicks, + animation, zoomedNodes, setZoomedNodes }: Props) { @@ -151,10 +154,12 @@ export default function GraphView({ const isDoubleClick = now.getTime() - date.getTime() < 1000 && name === node.data.name lastClick.current = { date: now, name: node.data.name } - + if (isDoubleClick) { + lastClick.current = { date: now, name: "" } handleExpand([node], !node.expand) } else if (isShowPath) { + lastClick.current = { date: now, name: "" } setPath(prev => { if (!prev?.start?.name || (prev.end?.name && prev.end?.name !== "")) { return ({ start: { id: Number(node.id), name: node.data.name } }) @@ -171,11 +176,7 @@ export default function GraphView({ canvasRef.current?.zoomToFit(zoomedNodes.length === 1 ? 4 : 1, (n: GraphNode) => zoomedNodes.some(node => node.id === n.id)) setZoomedNodes([]) } - - if (cooldownTicks !== -1) return - - setCooldownTicks(0) - }, [zoomedNodes, cooldownTicks, canvasRef]) + }, [zoomedNodes, canvasRef]) const nodeCanvasObject = useCallback((node: GraphNode, ctx: CanvasRenderingContext2D) => { if (node.x === undefined || node.y === undefined) { @@ -185,66 +186,111 @@ export default function GraphView({ const isHovered = !!hoverElement && !('source' in hoverElement) && hoverElement.id === node.id const isSelected = selectedObjects.some(obj => obj.id === node.id) || selectedObj?.id === node.id + const nodeSelected = isSelected || isHovered + // --- Determine colors based on path state --- if (isPathResponse) { if (node.data.isPathSelected) { ctx.fillStyle = node.color; ctx.strokeStyle = PATH_COLOR; - ctx.lineWidth = 1.5 + ctx.lineWidth = STROKE_WIDTH_SELECTED; } else if (node.data.isPath) { ctx.fillStyle = node.color; ctx.strokeStyle = PATH_COLOR; - ctx.lineWidth = 1 + ctx.lineWidth = STROKE_WIDTH_UNSELECTED; } else { ctx.fillStyle = dimmedNodeFillColor; ctx.strokeStyle = dimmedNodeStrokeColor; - ctx.lineWidth = 1 + ctx.lineWidth = STROKE_WIDTH_UNSELECTED; } } else if (isPathResponse === undefined) { if (node.data.isPathSelected) { ctx.fillStyle = node.color; ctx.strokeStyle = PATH_COLOR; - ctx.lineWidth = 1.5 + ctx.lineWidth = STROKE_WIDTH_SELECTED; } else if (node.data.isPath) { ctx.fillStyle = node.color; ctx.strokeStyle = PATH_COLOR; - ctx.lineWidth = 1 + ctx.lineWidth = STROKE_WIDTH_UNSELECTED; } else { ctx.fillStyle = node.color; ctx.strokeStyle = canvasForegroundColor; - ctx.lineWidth = isSelected || isHovered ? 1.5 : 1 + ctx.lineWidth = nodeSelected ? STROKE_WIDTH_SELECTED : STROKE_WIDTH_UNSELECTED; } } else { ctx.fillStyle = node.color; ctx.strokeStyle = canvasForegroundColor; - ctx.lineWidth = isSelected || isHovered ? 1.5 : 1 + ctx.lineWidth = nodeSelected ? STROKE_WIDTH_SELECTED : STROKE_WIDTH_UNSELECTED; } + const radius = NODE_SIZE + ctx.lineWidth / 2; + + // Draw stroke circle ctx.beginPath(); - ctx.arc(node.x, node.y, NODE_SIZE + ctx.lineWidth / 2, 0, 2 * Math.PI, false); + ctx.arc(node.x, node.y, radius, 0, 2 * Math.PI, false); ctx.stroke(); + + // Draw fill circle + ctx.beginPath(); + ctx.arc(node.x, node.y, NODE_SIZE, 0, 2 * Math.PI, false); ctx.fill(); - ctx.fillStyle = canvasForegroundColor; + // Skip labels when zoomed out (large graph optimisation) + const zoom = ctx.getTransform().a; + if (zoom < 1) return; + + // --- Draw text (matching canvas logic) --- + const fillColor = ctx.fillStyle as string; + ctx.fillStyle = getContrastTextColor(fillColor); ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; - ctx.font = '2px Arial'; - let name = node.data.name || ""; - const textWidth = ctx.measureText(name).width; - const ellipsis = '...'; - const ellipsisWidth = ctx.measureText(ellipsis).width; - const nodeSize = (NODE_SIZE + ctx.lineWidth / 2) * 2 - PADDING; - - // truncate text if it's too long - if (textWidth > nodeSize) { - while (name.length > 0 && ctx.measureText(name).width + ellipsisWidth > nodeSize) { - name = name.slice(0, -1); + + const textRadius = NODE_SIZE - PADDING / 2; + const name = node.data.name || node.data.title || String(node.id); + const nodeFontWeight = nodeSelected ? FONT_WEIGHT_SELECTED : FONT_WEIGHT_NORMAL; + const baseFontSize = 4; + + // Measure at the base size for line-wrapping decisions + ctx.font = `${nodeFontWeight} ${baseFontSize}px ${FONT_FAMILY}`; + const [line1, line2] = wrapTextForCircularNode(ctx, name, textRadius); + + let chosenSize = baseFontSize; + + if (TEXT_FILL_RATIO > 0 && !line2) { + // Auto-size mode: scale text to fill textFillRatio × nodeRadius + const REF = 20; + ctx.font = `${nodeFontWeight} ${REF}px ${FONT_FAMILY}`; + const refMetrics = ctx.measureText(line1); + const visualWidth = (refMetrics.actualBoundingBoxLeft ?? 0) + + (refMetrics.actualBoundingBoxRight ?? 0); + const refWidth = Math.max(visualWidth, refMetrics.width); + const refHeight = (refMetrics.actualBoundingBoxAscent ?? 0) + + (refMetrics.actualBoundingBoxDescent ?? 0); + + const r = TEXT_FILL_RATIO * textRadius; + if (refWidth > 0 && refHeight > 0) { + const diagonal = Math.sqrt(refWidth * refWidth + refHeight * refHeight); + chosenSize = REF * (2 * r / diagonal); + } else if (refWidth > 0) { + chosenSize = REF * (2 * r / refWidth); } - name += ellipsis; } - // add label - ctx.fillText(name, node.x, node.y); + ctx.font = `${nodeFontWeight} ${chosenSize}px ${FONT_FAMILY}`; + + const textMetrics = ctx.measureText(line1); + const textHeight = textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent; + const halfTextHeight = (textHeight / 2) * 1.5; + + if (line1) { + const yCorrection = line2 + ? 0 + : (textMetrics.actualBoundingBoxAscent - textMetrics.actualBoundingBoxDescent) / 2; + ctx.fillText(line1, node.x, line2 ? node.y - halfTextHeight : node.y + yCorrection); + } + if (line2) { + ctx.fillText(line2, node.x, node.y + halfTextHeight); + } }, [ selectedObj, selectedObjects, @@ -302,7 +348,7 @@ export default function GraphView({ nodeCanvasObject={nodeCanvasObject} nodePointerAreaPaint={nodePointerAreaPaint} linkLineDash={linkLineDash} - cooldownTicks={cooldownTicks} + animation={animation} backgroundColor={canvasBackgroundColor} foregroundColor={canvasForegroundColor} /> diff --git a/app/src/components/toolbar.tsx b/app/src/components/toolbar.tsx index b4d11258..5553c1c7 100644 --- a/app/src/components/toolbar.tsx +++ b/app/src/components/toolbar.tsx @@ -1,17 +1,42 @@ -import { Download, Fullscreen, ZoomIn, ZoomOut } from "lucide-react"; +import { useState } from "react"; +import { ChevronDown, Circle, Download, Fullscreen, Pause, Pin, PinOff, Play, ZoomIn, ZoomOut } from "lucide-react"; +import type { LayoutMode, HierarchyDirection, RadialDirection } from "@falkordb/canvas"; import { cn } from "@/lib/utils" import { GraphRef } from "@/lib/utils"; import { Switch } from "@/components/ui/switch"; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; + +const LAYOUTS: { value: LayoutMode; label: string }[] = [ + { value: 'force', label: 'Force' }, + { value: 'tree', label: 'Tree' }, + { value: 'radial', label: 'Radial' }, +]; + +const HIERARCHY_DIRECTIONS: { value: HierarchyDirection; label: string }[] = [ + { value: 'td', label: 'Top → Down' }, + { value: 'bu', label: 'Bottom → Up' }, + { value: 'lr', label: 'Left → Right' }, + { value: 'rl', label: 'Right → Left' }, +]; + +const RADIAL_DIRECTIONS: { value: RadialDirection; label: string }[] = [ + { value: 'out', label: 'Outward' }, + { value: 'in', label: 'Inward' }, +]; interface Props { canvasRef: GraphRef className?: string handleDownloadImage?: () => void - setCooldownTicks: (ticks?: 0) => void - cooldownTicks: number | undefined + animation: boolean + setAnimation: (animation: boolean) => void } -export function Toolbar({ canvasRef, className, handleDownloadImage, setCooldownTicks, cooldownTicks }: Props) { +export function Toolbar({ canvasRef, className, handleDownloadImage, animation, setAnimation }: Props) { + + const [layout, setLayout] = useState('force'); + const [direction, setDirection] = useState(''); + const [pinned, setPinned] = useState(false); const handleZoomClick = (changefactor: number) => { const canvas = canvasRef.current @@ -29,42 +54,172 @@ export function Toolbar({ canvasRef, className, handleDownloadImage, setCooldown } } + const handleAnimationToggle = () => { + setAnimation(!animation) + } + + const handlePinToggle = () => { + const next = !pinned; + setPinned(next); + canvasRef.current?.setPinOnDragEnd(next); + } + + const handleLayoutChange = (value: string) => { + const mode = value as LayoutMode; + setLayout(mode); + + if (mode === 'tree') { + const dir = direction || 'td'; + setDirection(dir); + canvasRef.current?.setLayoutOptions({ tree: { direction: dir as HierarchyDirection } }); + } else if (mode === 'radial') { + const dir = direction || 'out'; + setDirection(dir); + canvasRef.current?.setLayoutOptions({ radial: { direction: dir as RadialDirection } }); + } else { + setDirection(''); + } + + canvasRef.current?.setLayout(mode); + + // Non-force layouts auto-pin + const nextPinned = mode !== 'force'; + setPinned(nextPinned); + canvasRef.current?.setPinOnDragEnd(nextPinned); + } + + const handleDirectionChange = (value: string, targetLayout?: string) => { + const effectiveLayout = targetLayout || layout; + setDirection(value); + + if (effectiveLayout === 'tree') { + canvasRef.current?.setLayoutOptions({ tree: { direction: value as HierarchyDirection } }); + } else if (effectiveLayout === 'radial') { + canvasRef.current?.setLayoutOptions({ radial: { direction: value as RadialDirection } }); + } + } + + const animationDisabled = pinned || layout !== 'force'; + return ( -
+
+ {animation ? : } { - setCooldownTicks(cooldownTicks !== 0 ? 0 : undefined) - }} + className="pointer-events-auto data-[state=unchecked]:bg-border" + checked={animation} + disabled={animationDisabled} + onCheckedChange={handleAnimationToggle} /> + +
+ + + + + + + Force + + + + {layout === 'tree' && ( + + + + )} + Tree + + + {HIERARCHY_DIRECTIONS.map(d => ( + { + if (layout !== 'tree') handleLayoutChange('tree'); + handleDirectionChange(d.value, 'tree'); + }} + > + {layout === 'tree' && direction === d.value && ( + + + + )} + {d.label} + + ))} + + + + + {layout === 'radial' && ( + + + + )} + Radial + + + {RADIAL_DIRECTIONS.map(d => ( + { + if (layout !== 'radial') handleLayoutChange('radial'); + handleDirectionChange(d.value, 'radial'); + }} + > + {layout === 'radial' && direction === d.value && ( + + + + )} + {d.label} + + ))} + + + + +
) diff --git a/app/src/lib/utils.ts b/app/src/lib/utils.ts index 73cf2c9c..a67780c7 100644 --- a/app/src/lib/utils.ts +++ b/app/src/lib/utils.ts @@ -27,7 +27,13 @@ export enum MessageTypes { Text, } +let messageIdCounter = 0; +export function createMessage(msg: Omit): Message { + return { ...msg, id: ++messageIdCounter }; +} + export interface Message { + id: number; type: MessageTypes; text?: string; paths?: { nodes: any[], links: any[] }[]; From 98fc391457a9bc80708988c97b28dfe46a0fd8a8 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Wed, 8 Jul 2026 13:08:32 +0300 Subject: [PATCH 02/41] feat: enhance combobox type safety and element menu positioning - Updated Combobox component to enforce string array type for options. - Added container height state to ElementMenu for improved positioning. - Refactored ElementMenu positioning logic to ensure it remains within parent boundaries. - Improved GraphView component by removing unused props and optimizing node/link selection logic. - Introduced manual dimming feature for nodes and links in GraphView based on selection. - Enhanced Toolbar with zoom controls and manual dimming toggle. - Updated Graph model to include collapsed state for links and modified link removal logic. - Added e2e tests to validate node visibility and position preservation after hiding/unhiding. --- app/package-lock.json | 186 ++++++++++++--- app/package.json | 2 +- app/src/App.tsx | 106 +++------ app/src/components/ForceGraph.tsx | 66 +++--- app/src/components/chat.tsx | 132 ++--------- app/src/components/code-graph.tsx | 201 ++++++----------- app/src/components/combobox.tsx | 2 +- app/src/components/elementMenu.tsx | 31 ++- app/src/components/graphView.tsx | 349 +++++++++++++++-------------- app/src/components/model.ts | 17 +- app/src/components/toolbar.tsx | 120 +++++++--- e2e/logic/POM/codeGraph.ts | 2 +- e2e/tests/canvas.spec.ts | 88 +++++++- 13 files changed, 726 insertions(+), 576 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 76d82ccc..4c55da26 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -8,7 +8,7 @@ "name": "code-graph", "version": "0.3.0", "dependencies": { - "@falkordb/canvas": "file:../../falkordb-canvas", + "@falkordb/canvas": "v0.2.2", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -52,28 +52,6 @@ "vite": "^6.3.5" } }, - "../../falkordb-canvas": { - "name": "@falkordb/canvas", - "version": "0.0.0-dev", - "license": "MIT", - "dependencies": { - "d3": "^7.9.0", - "force-graph": "^1.44.4", - "react": "^19.2.3" - }, - "devDependencies": { - "@types/d3": "^7.4.3", - "@types/react": "^19.2.7", - "@typescript-eslint/eslint-plugin": "^8.18.2", - "@typescript-eslint/parser": "^8.18.2", - "eslint": "^10.0.2", - "jsdom": "^29.0.2", - "promise-retry": "^2.0.1", - "tsup": "^8.5.1", - "typescript": "^6.0.3", - "vitest": "^4.1.4" - } - }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -538,8 +516,24 @@ } }, "node_modules/@falkordb/canvas": { - "resolved": "../../falkordb-canvas", - "link": true + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.2.2.tgz", + "integrity": "sha512-oGo4/tXrLqUzzf39wjpcZ2lfoSo0InTsHqORGBpLVyQtgyNRSLgNdgKUrAbO3vjVnjbfNFzKIwHrUJFf2R6mHA==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "force-graph": "^1.44.4", + "react": "^19.2.3" + } + }, + "node_modules/@falkordb/canvas/node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, "node_modules/@floating-ui/core": { "version": "1.7.5", @@ -2308,6 +2302,12 @@ "@swc/counter": "^0.1.3" } }, + "node_modules/@tweenjs/tween.js": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", + "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2403,6 +2403,15 @@ "vite": "^4 || ^5 || ^6 || ^7" } }, + "node_modules/accessor-fn": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.3.tgz", + "integrity": "sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -2499,6 +2508,16 @@ "node": ">=6.0.0" } }, + "node_modules/bezier-js": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-6.1.4.tgz", + "integrity": "sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==", + "license": "MIT", + "funding": { + "type": "individual", + "url": "https://github.com/Pomax/bezierjs/blob/master/FUNDING.md" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2587,6 +2606,18 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas-color-tracker": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.3.2.tgz", + "integrity": "sha512-ryQkDX26yJ3CXzb3hxUVNlg1NKE4REc5crLBq661Nxzr8TNd236SaEf2ffYLXyI5tSABSeguHLqcVq4vf9L3Zg==", + "license": "MIT", + "dependencies": { + "tinycolor2": "^1.6.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/canvas2svg": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/canvas2svg/-/canvas2svg-1.0.16.tgz", @@ -2829,6 +2860,12 @@ "node": ">=12" } }, + "node_modules/d3-binarytree": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", + "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", + "license": "MIT" + }, "node_modules/d3-brush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", @@ -2972,6 +3009,22 @@ "node": ">=12" } }, + "node_modules/d3-force-3d": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", + "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", + "license": "MIT", + "dependencies": { + "d3-binarytree": "1", + "d3-dispatch": "1 - 3", + "d3-octree": "1", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-format": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", @@ -3014,6 +3067,12 @@ "node": ">=12" } }, + "node_modules/d3-octree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", + "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", + "license": "MIT" + }, "node_modules/d3-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", @@ -3344,6 +3403,46 @@ "node": ">=8" } }, + "node_modules/float-tooltip": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/float-tooltip/-/float-tooltip-1.7.5.tgz", + "integrity": "sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==", + "license": "MIT", + "dependencies": { + "d3-selection": "2 - 3", + "kapsule": "^1.16", + "preact": "10" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/force-graph": { + "version": "1.51.4", + "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.51.4.tgz", + "integrity": "sha512-TdJ2KbkoiDQ7NIRx8IPGD0mAXXpLhamS7c+b7W98b0MHG7lphnda1VOQX/98UDTsttIAdH4TcP0l0MauSnLK8w==", + "license": "MIT", + "dependencies": { + "@tweenjs/tween.js": "18 - 25", + "accessor-fn": "1", + "bezier-js": "3 - 6", + "canvas-color-tracker": "^1.3", + "d3-array": "1 - 3", + "d3-drag": "2 - 3", + "d3-force-3d": "2 - 3", + "d3-scale": "1 - 4", + "d3-scale-chromatic": "1 - 3", + "d3-selection": "2 - 3", + "d3-zoom": "2 - 3", + "float-tooltip": "^1.7", + "index-array-by": "1", + "kapsule": "^1.16", + "lodash-es": "4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -3489,6 +3588,15 @@ "node": ">=0.10.0" } }, + "node_modules/index-array-by": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/index-array-by/-/index-array-by-1.4.2.tgz", + "integrity": "sha512-SP23P27OUKzXWEC/TOyWlwLviofQkCSCKONnc62eItjp69yCZZPqDQtr3Pw5gJDnPeUMqExmKydNZaJO0FU9pw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -3620,6 +3728,18 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/kapsule": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.16.3.tgz", + "integrity": "sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==", + "license": "MIT", + "dependencies": { + "lodash-es": "4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -3980,6 +4100,16 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/preact": { + "version": "10.29.5", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.5.tgz", + "integrity": "sha512-zIai7HLEIz9c4GNfqpiuu5K9I1szXNtx0Ykr8f1Vbm1NasSVIvXGRxc8R8alLC+G+zonAt0TiUUBMn4+CiTgaA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -4523,6 +4653,12 @@ "node": ">=0.8" } }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", diff --git a/app/package.json b/app/package.json index 0d7c2e83..6a5e1fe4 100644 --- a/app/package.json +++ b/app/package.json @@ -11,7 +11,7 @@ "preview": "vite preview" }, "dependencies": { - "@falkordb/canvas": "file:../../falkordb-canvas", + "@falkordb/canvas": "v0.2.2", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", diff --git a/app/src/App.tsx b/app/src/App.tsx index 0ad845e8..fe70471d 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -12,10 +12,10 @@ import { Carousel, CarouselApi, CarouselContent, CarouselItem, CarouselNext, Car import { Drawer, DrawerContent, DrawerDescription, DrawerTitle, DrawerTrigger } from '@/components/ui/drawer'; import Input from './components/Input'; import { Labels } from './components/labels'; -import { Toolbar } from './components/toolbar'; +import { Toolbar, ZoomControls } from './components/toolbar'; import { cn, GraphRef, Message, Path, PathData, PathNode } from '@/lib/utils'; import type { GraphNode } from '@falkordb/canvas'; -import { graphDataToData } from '@falkordb/canvas'; +import { convertToCanvasData } from './components/ForceGraph'; import { Toaster } from '@/components/ui/toaster'; import GTM from './GTM'; import { Button } from '@/components/ui/button'; @@ -82,6 +82,7 @@ export default function App() { const [searchNode, setSearchNode] = useState({}); const [cooldownTicks, setCooldownTicks] = useState(undefined) const [animation, setAnimation] = useState(false) + const [manualDimmed, setManualDimmed] = useState(true) const [optionsOpen, setOptionsOpen] = useState(false) const [messages, setMessages] = useState([]); const [query, setQuery] = useState(''); @@ -228,29 +229,7 @@ export default function App() { setZoomedNodes([chartNode]) graph.visibleLinks(true, [chartNode!.id]) - const currentData = canvas.getGraphData() - - const { dataToGraphData } = await import('@falkordb/canvas') - const graphNode = dataToGraphData({ - nodes: [{ - color: chartNode.color, - id: chartNode.id, - labels: [chartNode.category], - visible: chartNode.visible, - data: { - ...chartNode.data, - isPath: chartNode.isPath, - isPathSelected: chartNode.isPathSelected, - } - }], links: [] - }).nodes[0] - - if (graphNode) { - currentData.nodes.push(graphNode) - } - - canvas.setGraphData(graphDataToData(currentData)) - + canvas.setGraphData(convertToCanvasData(graph.Elements)) setTimeout(() => { canvas.zoomToFit(4, (n: GraphNode) => n.id === chartNode!.id) @@ -263,22 +242,7 @@ export default function App() { chartNode.visible = true graph.visibleLinks(true, [chartNode!.id]) - const currentData = canvas.getGraphData() - - const graphNode = currentData.nodes.find(n => n.id === chartNode!.id) - if (graphNode) { - graphNode.visible = true - } - - currentData.links.forEach(canvasLink => { - const appLink = graph.LinksMap.get(canvasLink.id) - - if (appLink) { - canvasLink.visible = appLink.visible - } - }) - - canvas.setGraphData(graphDataToData(currentData)) + canvas.setGraphData(convertToCanvasData(graph.Elements)) } setTimeout(() => { @@ -304,25 +268,17 @@ export default function App() { graph.visibleLinks(show) - const currentData = canvas.getGraphData(); - - currentData.nodes.forEach(canvasNode => { - const appNode = graph.NodesMap.get(canvasNode.id); - - if (appNode) { - canvasNode.visible = appNode.visible; - } - }); - - currentData.links.forEach(canvasLink => { - const appLink = graph.LinksMap.get(canvasLink.id); - - if (appLink) { - canvasLink.visible = appLink.visible; - } - }); - - canvas.setGraphData(graphDataToData(currentData)); + // setGraphData doesn't update visible for existing nodes — mutate canvas nodes directly + const canvasData = canvas.getGraphData() + canvasData.nodes.forEach((canvasNode: { id: number, visible: boolean }) => { + const appNode = graph.NodesMap.get(canvasNode.id) + if (appNode) canvasNode.visible = appNode.visible + }) + canvasData.links.forEach((canvasLink: { id: number, visible: boolean }) => { + const appLink = graph.LinksMap.get(canvasLink.id) + if (appLink) canvasLink.visible = appLink.visible + }) + canvas.refresh() setHasHiddenElements(graph.getElements().some(element => !element.visible)); } @@ -539,6 +495,8 @@ export default function App() { setCooldownTicks={setCooldownTicks} animation={animation} setAnimation={setAnimation} + manualDimmed={manualDimmed} + setManualDimmed={setManualDimmed} onCategoryClick={(name, show) => onCategoryClick(name, show, desktopChartRef)} handleDownloadImage={handleDownloadImage} zoomedNodes={zoomedNodes} @@ -667,6 +625,8 @@ export default function App() { setCooldownTicks={setCooldownTicks} animation={animation} setAnimation={setAnimation} + manualDimmed={manualDimmed} + setManualDimmed={setManualDimmed} onCategoryClick={(name, show) => onCategoryClick(name, show, mobileChartRef)} handleDownloadImage={handleDownloadImage} zoomedNodes={zoomedNodes} @@ -719,11 +679,10 @@ export default function App() { - onCategoryClick(name, show, mobileChartRef)} /> -
- -

Take Screenshot

-
+ {/* Options controls inside the drawer, below search */} +
diff --git a/app/src/components/ForceGraph.tsx b/app/src/components/ForceGraph.tsx index 110b3902..dd915a00 100644 --- a/app/src/components/ForceGraph.tsx +++ b/app/src/components/ForceGraph.tsx @@ -16,31 +16,34 @@ interface Props { onLinkHover: (link: Link | null) => void onLinkRightClick: (link: Link, event: MouseEvent) => void isLinkSelected: (link: GraphLink) => boolean + isNodeDimmed: (node: GraphNode) => boolean + isLinkDimmed: (link: GraphLink) => boolean + dimmed: boolean + linkLineDash: (link: any) => number[] onBackgroundClick: (event: MouseEvent) => void onBackgroundRightClick: (event: MouseEvent) => void - nodeCanvasObject: (node: GraphNode, ctx: CanvasRenderingContext2D) => void - nodePointerAreaPaint: (node: GraphNode, color: string, ctx: CanvasRenderingContext2D) => void - linkLineDash: (link: any) => number[] onZoom: () => void onEngineStop: () => void - animation: boolean + onNodeDragEnd?: () => void backgroundColor?: string foregroundColor?: string } -const convertToCanvasData = (graphData: GraphData): Data => ({ - nodes: graphData.nodes.filter(n => n.visible).map(({ id, category, color, visible, isPath, isPathSelected, data }) => ({ +export const convertToCanvasData = (graphData: GraphData): Data => ({ + nodes: graphData.nodes.map(({ id, category, color, visible, expand, isPath, isPathSelected, data }) => ({ id, labels: [category], color, + borderColor: (isPath || isPathSelected) ? PATH_COLOR : undefined, visible, + expand, data: { ...data, isPath, isPathSelected } })), - links: graphData.links.filter(l => l.visible).map(({ id, label, color, visible, source, target, isPath, isPathSelected, data }) => ({ + links: graphData.links.map(({ id, label, color, visible, source, target, isPath, isPathSelected, data }) => ({ id, relationship: label, - color: isPath ? PATH_COLOR : color, + color: (isPath || isPathSelected) ? PATH_COLOR : color, visible, source, target, @@ -60,14 +63,15 @@ export default function ForceGraph({ onLinkHover, onLinkRightClick, isLinkSelected, + isNodeDimmed, + isLinkDimmed, + dimmed, + linkLineDash, onBackgroundClick, onBackgroundRightClick, onZoom, onEngineStop, - nodeCanvasObject, - nodePointerAreaPaint, - linkLineDash, - animation, + onNodeDragEnd, backgroundColor = "#FFFFFF", foregroundColor = "#000000" }: Props) { @@ -83,10 +87,10 @@ export default function ForceGraph({ useEffect(() => { const canvas = canvasRef.current - if (!canvas) return + if (!canvas || !canvasLoaded) return (window as any)[id === "desktop" ? "graphDesktop" : "graphMobile"] = () => canvas.getGraphData(); - }, [canvasRef, id]) + }, [canvasRef, id, canvasLoaded]) // Update canvas colors useEffect(() => { @@ -95,13 +99,6 @@ export default function ForceGraph({ canvasRef.current.setForegroundColor(foregroundColor) }, [canvasRef, backgroundColor, foregroundColor, canvasLoaded]) - // Update animation state on canvas - useEffect(() => { - if (!canvasRef.current || !canvasLoaded) return - - canvasRef.current.setAnimation(animation) - }, [canvasRef, animation, canvasLoaded]) - // Map node click handler const handleNodeClick = useCallback((node: GraphNode, event: MouseEvent) => { const originalNode = data.nodes.find(n => n.id === node.id) @@ -114,9 +111,9 @@ export default function ForceGraph({ onNodeHover(null) return } - + const originalNode = data.nodes.find(n => n.id === node.id) - + if (originalNode) onNodeHover(originalNode) }, [onNodeHover, data.nodes]) @@ -138,9 +135,9 @@ export default function ForceGraph({ onLinkHover(null) return } - + const originalLink = data.links.find(l => l.id === link.id) - + if (originalLink) onLinkHover(originalLink) }, [onLinkHover, data.links]) @@ -159,11 +156,11 @@ export default function ForceGraph({ useEffect(() => { if (!canvasRef.current || !canvasLoaded) return canvasRef.current.setConfig({ - // nodes will display node.data.captionsKeys in the canvas - captionsKeys: ["name", "title"], + captionsKeys: [["name", true], ["title", true]], isNodeSelected: isNodeSelected, isLinkSelected: isLinkSelected, - node: { nodeCanvasObject, nodePointerAreaPaint }, + isNodeDimmed: isNodeDimmed, + isLinkDimmed: isLinkDimmed, linkLineDash, eventHandlers: { onNodeClick: handleNodeClick, @@ -175,6 +172,7 @@ export default function ForceGraph({ onBackgroundClick, onBackgroundRightClick, onEngineStop: handleEngineStop, + onNodeDragEnd, onZoom } }) @@ -187,17 +185,25 @@ export default function ForceGraph({ handleLinkHover, isNodeSelected, isLinkSelected, + isNodeDimmed, + isLinkDimmed, + dimmed, onBackgroundClick, onBackgroundRightClick, handleEngineStop, - nodeCanvasObject, - nodePointerAreaPaint, + onNodeDragEnd, linkLineDash, onZoom, canvasRef, canvasLoaded ]) + // Sync dimmed state to canvas (like browser: separate effect, not via setConfig) + useEffect(() => { + if (!canvasRef.current || !canvasLoaded) return + canvasRef.current.setDimmed(dimmed) + }, [dimmed, canvasRef, canvasLoaded]) + // Update canvas data useEffect(() => { const canvas = canvasRef.current diff --git a/app/src/components/chat.tsx b/app/src/components/chat.tsx index f5057c70..f68cdd01 100644 --- a/app/src/components/chat.tsx +++ b/app/src/components/chat.tsx @@ -12,7 +12,8 @@ import { Button } from "@/components/ui/button"; const AUTH_HEADERS: HeadersInit = import.meta.env.VITE_SECRET_TOKEN ? { 'Authorization': `Bearer ${import.meta.env.VITE_SECRET_TOKEN}` } : {}; -import { dataToGraphData, graphDataToData, GraphLink, GraphNode } from "@falkordb/canvas"; +import { GraphNode } from "@falkordb/canvas"; +import { convertToCanvasData } from "./ForceGraph"; interface Props { repo: string @@ -114,7 +115,7 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set if (!canvas) return - // Sets for the new path + setIsPathResponse(true) const pNodeIds = new Set(p.nodes.map((n: Node) => n.id)) const pLinkIds = new Set(p.links.map((l: any) => l.id)) const pIds = new Set([...pNodeIds, ...pLinkIds]) @@ -180,57 +181,9 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set }) } - const currentData = canvas.getGraphData(); - - // --- Unset canvas data for previous path elements no longer in the new path --- - if (selectedPath) { - currentData.nodes.forEach((n: any) => { - if (prevNodeIds.has(n.id) && !pNodeIds.has(n.id)) { - if (isPathResponse) { - // keep isPath + PATH_COLOR border, just deselect - n.data.isPathSelected = false - } else { - n.data.isPath = false - n.data.isPathSelected = false - } - } - }) - currentData.links.forEach((l: any) => { - if (prevLinkIds.has(l.id) && !pLinkIds.has(l.id)) { - if (isPathResponse) { - // keep color + dashed path line, just deselect - l.data.isPathSelected = false - } else { - l.data.isPathSelected = false - l.color = "#999999" - } - } - }) - } - - // --- Set canvas data for new path elements --- - currentData.nodes.forEach((n: any) => { - if (pNodeIds.has(n.id)) { - if (n.id === firstNodeId || n.id === lastNodeId) { - n.data.isPathSelected = true; - } else { - n.data.isPath = true; - } - } - }); - currentData.links.forEach((l: any) => { - if (pLinkIds.has(l.id)) { - l.data.isPathSelected = true; - l.color = PATH_COLOR; - } - }); - - canvas.setGraphData(graphDataToData(currentData)) + canvas.setGraphData(convertToCanvasData(graph.Elements)) setTimeout(() => { - const filteredNodes = currentData.nodes.filter((n: any) => pNodeIds.has(n.id)); - console.log('[zoomToFit] filtered nodes:', filteredNodes.map((n: any) => ({ id: n.id, x: n.x, y: n.y }))); - console.log('[zoomToFit] pNodeIds:', [...pNodeIds]); canvas.zoomToFit(2, (n: GraphNode) => pNodeIds.has(n.id)); }, 300) setChatOpen && setChatOpen(false) @@ -375,69 +328,30 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set ]); setIsPathResponse(true) - const currentData = canvas.getGraphData(); - - const nodesMap = new Map(currentData.nodes.map(n => [n.id, n])) - const linksMap = new Map(currentData.links.map(l => [l.id, l])) - + // Mark path elements on the model formattedPaths.flatMap(p => p.nodes).forEach(n => { - const node = nodesMap.get(n.id); + const node = graph.Elements.nodes.find(gn => gn.id === n.id); if (node) { - node.data.isPath = true; + node.isPath = true; } }); formattedPaths.flatMap(p => p.links).forEach(l => { - const link = linksMap.get(l.id); - + const link = graph.Elements.links.find(gl => gl.id === l.id); if (link) { - link.data.isPath = true; + link.isPath = true; link.color = PATH_COLOR; } }); - // Filter for only new elements - const newDataElements = { - nodes: elements.nodes.filter(n => !nodesMap.has(n.id)) - .map(({ category, color, data, id, isPath, isPathSelected, visible }) => ({ - color, - id, - labels: [category], - data: { - ...data, - isPath, - isPathSelected - }, - visible, - })), - links: elements.links.filter(l => !linksMap.has(l.id)) - .map(({ color, id, source, target, data, isPath, isPathSelected, visible, label }) => ({ - color: isPath ? PATH_COLOR : color, - id, - source, - target, - data: { - ...data, - isPath, - isPathSelected - }, - visible, - relationship: label, - })) + // Update the canvas from the model + canvasRef.current?.setGraphData(convertToCanvasData(graph.Elements)) + + // Zoom to fit all path nodes after coloring + const pathNodeIds = new Set(formattedPaths.flatMap(p => p.nodes).map(n => n.id)) + if (pathNodeIds.size > 0 && canvasRef.current) { + canvasRef.current.zoomToFit(1, (n: any) => pathNodeIds.has(n.id)) } - // Convert only new data to GraphData format - const newGraphData = dataToGraphData( - newDataElements, - undefined, - new Map(currentData.nodes.map(n => [n.id, n])) - ) - - // Merge with existing data - canvasRef.current?.setGraphData(graphDataToData({ - nodes: [...currentData.nodes, ...newGraphData.nodes], - links: [...currentData.links, ...newGraphData.links] - })) - setTimeout(() => { const nodesMap = new Map(formattedPaths.flatMap(p => p.nodes.map((n: Node) => [n.id, n]))) canvas.zoomToFit(2, (n: GraphNode) => formattedPaths.some(p => nodesMap.has(n.id))); @@ -468,18 +382,12 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set e.isPathSelected = false }) - const currentData = canvas.getGraphData(); - - [...currentData.nodes, ...currentData.links].forEach(element => { - element.data.isPath = false - element.data.isPathSelected = false - - if ("source" in element) { - element.color = "#999999" - } + // Reset link colors on the model + graph.Elements.links.forEach(link => { + link.color = "#999999" }) - canvas.setGraphData(graphDataToData(currentData)) + canvas.setGraphData(convertToCanvasData(graph.Elements)) } setMessages(prev => [...prev, createMessage({ diff --git a/app/src/components/code-graph.tsx b/app/src/components/code-graph.tsx index 4dc3b38a..a872ba24 100644 --- a/app/src/components/code-graph.tsx +++ b/app/src/components/code-graph.tsx @@ -1,5 +1,5 @@ import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react"; -import { Graph, GraphData, Node, Link } from "./model"; +import { Graph, GraphData, Node, Link, getCategoryColorValue } from "./model"; import { Toolbar } from "./toolbar"; import { Labels } from "./labels"; import { Download, GitFork, Search, X } from "lucide-react"; @@ -7,20 +7,18 @@ import { Button } from "@/components/ui/button"; import ElementMenu from "./elementMenu"; import Combobox from "./combobox"; import { toast } from '@/components/ui/use-toast'; -import { Path, PATH_COLOR } from "@/lib/utils"; +import { Path } from "@/lib/utils"; import Input from './Input'; // import CommitList from './commitList'; import { Checkbox } from '@/components/ui/checkbox'; import type { Position } from "./graphView"; -import { prepareArg } from '../utils'; import { GraphRef } from "@/lib/utils"; -import { dataToGraphData, graphDataToData } from "@falkordb/canvas"; -import type { Node as CanvasNode, Link as CanvasLink, GraphData as CanvasData } from "@falkordb/canvas"; import GraphView from "./graphView"; +import { convertToCanvasData } from "./ForceGraph"; const AUTH_HEADERS: HeadersInit = import.meta.env.VITE_SECRET_TOKEN - ? { 'Authorization': `Bearer ${import.meta.env.VITE_SECRET_TOKEN}` } - : {}; + ? { 'Authorization': `Bearer ${import.meta.env.VITE_SECRET_TOKEN}` } + : {}; interface Props { id: "desktop" | "mobile" @@ -46,6 +44,8 @@ interface Props { setCooldownTicks: Dispatch> animation: boolean setAnimation: (animation: boolean) => void + manualDimmed: boolean + setManualDimmed: (dimmed: boolean) => void onCategoryClick: (name: string, show: boolean) => void handleDownloadImage: () => void zoomedNodes: Node[] @@ -78,6 +78,8 @@ export function CodeGraph({ setCooldownTicks, animation, setAnimation, + manualDimmed, + setManualDimmed, onCategoryClick, handleDownloadImage, zoomedNodes, @@ -87,7 +89,6 @@ export function CodeGraph({ }: Props) { const [url, setURL] = useState(""); - const [selectedObj, setSelectedObj] = useState(); const [selectedObjects, setSelectedObjects] = useState([]); const [position, setPosition] = useState(); const [graphName, setGraphName] = useState(""); @@ -114,9 +115,9 @@ export function CodeGraph({ useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Delete') { - if (selectedObjects.length === 0 && (!selectedObj || "source" in selectedObj)) return - - handleRemove([...selectedObjects.map(obj => obj.id), selectedObj?.id].filter(id => id !== undefined), "nodes"); + if (selectedObjects.length === 0) return + + handleRemove(selectedObjects.map(obj => obj.id), "nodes"); } }; @@ -125,7 +126,7 @@ export function CodeGraph({ return () => { document.removeEventListener('keydown', handleKeyDown); }; - }, [selectedObjects, selectedObj]); + }, [selectedObjects]); async function fetchCount() { const result = await fetch(`/api/repo_info`, { @@ -197,20 +198,16 @@ export function CodeGraph({ if (nodes.length === 0) return; const expandedNodes: Node[] = [] - const deleteIdsMap = new Set() graph.Elements = { nodes: graph.Elements.nodes.filter(node => { if (!node.collapsed) return true - const isTarget = graph.Elements.links.some(link => link.target === node.id && nodes.some(n => n.id === link.source)); - - if (!isTarget) return true + const isConnected = graph.Elements.links.some(link => (link.target === node.id || nodes.some(n => n.id === link.target)) && (link.source === node.id || nodes.some(n => n.id === link.source))); - deleteIdsMap.add(node.id) - const deleted = graph.NodesMap.delete(Number(node.id)) + if (!isConnected) return true - if (deleted && node.expand) { + if (graph.NodesMap.delete(Number(node.id)) && node.expand) { expandedNodes.push(node) } @@ -219,15 +216,14 @@ export function CodeGraph({ links: graph.Elements.links } - deleteNeighbors(expandedNodes)?.forEach(id => deleteIdsMap.add(id)) + deleteNeighbors(expandedNodes) - graph.removeLinks() - - return deleteIdsMap + graph.removeLinks(nodes.map(n => n.id)) } const handleExpand = async (nodes: Node[], expand: boolean) => { if (expand) { + // DON'T set expand state until after successful fetch const elements = await onFetchNode(nodes.map(n => n.id)) if (elements.nodes.length === 0) { @@ -235,82 +231,34 @@ export function CodeGraph({ title: `No neighbors found`, description: `No neighbors found`, }) + // Don't set expand if no neighbors found - no glow effect return } - const currentData = canvasRef.current?.getGraphData() - - if (!currentData) return - - // Get existing IDs - const existingNodeIds = new Set(currentData.nodes.map(n => n.id)) - const existingLinkIds = new Set(currentData.links.map(l => l.id)) - - // Filter for only new elements - const newDataElements = { - nodes: elements.nodes.filter(n => !existingNodeIds.has(n.id)) - .map(({ category, color, data, id, isPath, isPathSelected, visible }) => ({ - color: isPath ? PATH_COLOR : color, - id, - labels: [category], - data: { - ...data, - isPath, - isPathSelected - }, - visible, - } as CanvasNode)), - links: elements.links.filter(l => !existingLinkIds.has(l.id)) - .map(({ color, id, source, target, data, isPath, isPathSelected, visible, label }) => ({ - color: isPath ? PATH_COLOR : color, - id, - source, - target, - data: { - ...data, - isPath, - isPathSelected - }, - visible, - relationship: label, - } as CanvasLink)) - } + // Only NOW set expand state after successful fetch - glow animation triggers here + nodes.forEach((node) => { + node.expand = true + }) - // Convert only new data to GraphData format - const newGraphData = dataToGraphData( - newDataElements, - undefined, - new Map(currentData.nodes.map(n => [n.id, n])) - ) - - // Merge with existing data - canvasRef.current?.setGraphData(graphDataToData({ - nodes: [...currentData.nodes, ...newGraphData.nodes], - links: [...currentData.links, ...newGraphData.links] - })) + // Update the model with new elements (graph.Elements is already updated by onFetchNode) + // Convert the full model to canvas data and update + canvasRef.current?.setGraphData(convertToCanvasData(graph.Elements)) } else { const deleteNodes = nodes.filter(n => n.expand) if (deleteNodes.length > 0) { - const deleteIdsMap = deleteNeighbors(deleteNodes); - - if (!deleteIdsMap || deleteIdsMap.size === 0) return - - const currentData = canvasRef.current?.getGraphData() + deleteNeighbors(deleteNodes) + } - if (currentData) { - currentData.nodes = currentData.nodes.filter(node => !deleteIdsMap.has(Number(node.id))) - currentData.links = currentData.links.filter(link => !deleteIdsMap.has(Number(link.source.id)) && !deleteIdsMap.has(Number(link.target.id))) + // Set expand state AFTER deleteNeighbors so the filter above works + nodes.forEach((node) => { + node.expand = false + }) - canvasRef.current?.setGraphData(graphDataToData(currentData)) - } - } + // Convert the updated model to canvas data + canvasRef.current?.setGraphData(convertToCanvasData(graph.Elements)) } - nodes.forEach((node) => { - node.expand = expand - }) - - setSelectedObj(undefined) + setSelectedObjects([]) } const handleRemove = (ids: number[], type: "nodes" | "links") => { @@ -323,26 +271,24 @@ export function CodeGraph({ element.visible = false }) - const currentData = canvas.getGraphData() - - currentData[type].forEach(element => { - if (!ids.includes(Number(element.id))) return - element.visible = false - }) - if (type === "nodes") { - currentData.links.forEach((link) => { - if (ids.includes(link.source.id) || ids.includes(link.target.id)) { - link.visible = false - } - }) + graph.visibleLinks(false, ids) } - canvas.setGraphData(graphDataToData(currentData)) - graph.visibleLinks(false, ids) + // setGraphData doesn't update visible for existing nodes — mutate canvas nodes directly + const canvasData = canvas.getGraphData() + canvasData.nodes.forEach(canvasNode => { + const appNode = graph.NodesMap.get(canvasNode.id) + if (appNode) canvasNode.visible = appNode.visible + }) + canvasData.links.forEach(canvasLink => { + const appLink = graph.LinksMap.get(canvasLink.id) + if (appLink) canvasLink.visible = appLink.visible + }) + canvas.refresh() + setHasHiddenElements(true) - setSelectedObj(undefined) setSelectedObjects([]) } @@ -387,23 +333,24 @@ export function CodeGraph({ if (!canvas) return - graph.getElements().forEach((element) => { - element.isPath = false - element.isPathSelected = false + // Clear path flags from graph model + graph.Elements.nodes.forEach((node) => { + node.isPath = false + node.isPathSelected = false }) - const currentData = canvas.getGraphData(); - - [...currentData.nodes, ...currentData.links].forEach(element => { - element.data.isPath = false - element.data.isPathSelected = false - - if ("source" in element) { - element.color = "#999999" - } + graph.Elements.links.forEach((link) => { + link.isPath = false + link.isPathSelected = false + link.color = "#999999" // Reset link color }) - canvas.setGraphData(graphDataToData(currentData)) + // Update React state to trigger re-render + setData({ ...graph.Elements }) + + // Update canvas + canvas.setGraphData(convertToCanvasData(graph.Elements)) + canvas.zoomToFit() setIsPathResponse(false) }} > @@ -427,13 +374,10 @@ export function CodeGraph({ element.visible = true }); - const currentData = canvas.getGraphData(); - - [...currentData.nodes, ...currentData.links].forEach(element => { - element.visible = true - }); - - canvas.setGraphData(graphDataToData(currentData)); + const canvasData = canvas.getGraphData(); + canvasData.nodes.forEach((n: { visible: boolean }) => { n.visible = true; }); + canvasData.links.forEach((l: { visible: boolean }) => { l.visible = true; }); + canvas.refresh(); setHasHiddenElements(false); }} > @@ -444,11 +388,11 @@ export function CodeGraph({
{ setPath(path) - setSelectedObj(undefined) + setSelectedObjects([]) }} handleRemove={handleRemove} position={position} @@ -462,9 +406,7 @@ export function CodeGraph({ setData={setData} graph={graph} chartRef={canvasRef} - selectedObj={selectedObj} selectedObjects={selectedObjects} - setSelectedObj={setSelectedObj} setSelectedObjects={setSelectedObjects} setPosition={setPosition} handleExpand={handleExpand} @@ -473,9 +415,9 @@ export function CodeGraph({ isPathResponse={isPathResponse} selectedPathId={selectedPathId} setSelectedPathId={setSelectedPathId} - animation={animation} setZoomedNodes={setZoomedNodes} zoomedNodes={zoomedNodes} + manualDimmed={manualDimmed} />
@@ -509,6 +451,9 @@ export function CodeGraph({ handleDownloadImage={handleDownloadImage} animation={animation} setAnimation={setAnimation} + manualDimmed={manualDimmed} + setManualDimmed={setManualDimmed} + selectedObjects={selectedObjects} />
diff --git a/app/src/components/combobox.tsx b/app/src/components/combobox.tsx index 93c6720c..ec1b500a 100644 --- a/app/src/components/combobox.tsx +++ b/app/src/components/combobox.tsx @@ -37,7 +37,7 @@ export default function Combobox({ options, setOptions, selectedValue, onSelecte } const json = await result.json() - setOptions(json.repositories) + setOptions(json.repositories as string[]) } useEffect(() => { diff --git a/app/src/components/elementMenu.tsx b/app/src/components/elementMenu.tsx index e7d5764c..1c7d9a63 100644 --- a/app/src/components/elementMenu.tsx +++ b/app/src/components/elementMenu.tsx @@ -22,6 +22,7 @@ interface Props { export default function ElementMenu({ obj, objects, setPath, handleRemove, position, url, handleExpand, parentRef }: Props) { const [currentObj, setCurrentObj] = useState(); const [containerWidth, setContainerWidth] = useState(0); + const [containerHeight, setContainerHeight] = useState(0); useEffect(() => { setCurrentObj(undefined) @@ -33,19 +34,41 @@ export default function ElementMenu({ obj, objects, setPath, handleRemove, posit ? `${url}/tree/master/${obj.data.path}/${obj.data.name}` : `${url}/tree/master/${obj.data.path}#L${obj.data.src_start}-L${obj.data.src_end + 1}` + const parentRect = parentRef?.current?.getBoundingClientRect() + const parentW = parentRef?.current?.clientWidth || 0 + const parentH = parentRef?.current?.clientHeight || 0 + + const EDGE_MARGIN = 8 // fixed margin from container edges + const GAP = 12 // fixed gap between node edge and menu — constant at every zoom level + + const zoom = position.zoom ?? 1 + const nodeRadius = 9 * zoom // node visual radius in screen px (NODE_SIZE=9 world units) + + // Convert viewport coordinates to parent-relative coordinates + const relX = position.x - (parentRect?.left || 0) + const relY = position.y - (parentRect?.top || 0) + + // X axis: center on click, clamp to container edges + const rawLeft = relX - containerWidth / 2 + const left = Math.max(EDGE_MARGIN, Math.min(rawLeft, parentW - containerWidth - EDGE_MARGIN)) + + // Y axis: always place below at (node_center + node_radius + fixed_gap). + // Clamp to canvas edges — no flipping. If the node is larger than the canvas + // the menu stays at the bottom edge, which may overlap the node. + const rawTop = relY + nodeRadius + GAP + const top = Math.max(EDGE_MARGIN, Math.min(rawTop, parentH - containerHeight - EDGE_MARGIN)) + return ( <>
{ if (!ref) return setContainerWidth(ref.clientWidth) + setContainerHeight(ref.clientHeight) }} className="absolute z-10 bg-popover text-popover-foreground rounded-lg shadow-lg flex divide-x divide-border" id="elementMenu" - style={{ - left: Math.max(8, Math.min(position.x - containerWidth / 2, (parentRef?.current?.clientWidth || 0) - containerWidth - 8)), - top: Math.max(8, Math.min(position.y - 153, (parentRef?.current?.clientHeight || 0) - containerWidth - 8)), - }} + style={{ left, top }} > { objects.some(o => o.id === obj.id) && objects.length > 1 ? diff --git a/app/src/components/graphView.tsx b/app/src/components/graphView.tsx index 38fcad64..d0ac4051 100644 --- a/app/src/components/graphView.tsx +++ b/app/src/components/graphView.tsx @@ -1,16 +1,17 @@ import { Graph, GraphData, Link, Node } from './model'; import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react'; -import { Path, PATH_COLOR } from '@/lib/utils'; +import { Path } from '@/lib/utils'; import { Fullscreen } from 'lucide-react'; import { GraphRef } from '@/lib/utils'; import ForceGraph from './ForceGraph'; -import { GraphLink, GraphNode, NODE_SIZE, getContrastTextColor, wrapTextForCircularNode } from '@falkordb/canvas'; +import { GraphLink, GraphNode } from '@falkordb/canvas'; import { useTheme } from './theme-provider'; export interface Position { x: number, y: number, + zoom?: number, } interface Props { @@ -19,8 +20,6 @@ interface Props { graph: Graph chartRef: GraphRef id: "desktop" | "mobile" - selectedObj: Node | Link | undefined - setSelectedObj: Dispatch> selectedObjects: Node[] setSelectedObjects: Dispatch> setPosition: Dispatch> @@ -30,34 +29,22 @@ interface Props { isPathResponse: boolean | undefined selectedPathId: number | undefined setSelectedPathId: (selectedPathId: number) => void - animation: boolean setZoomedNodes: Dispatch> zoomedNodes: Node[] + manualDimmed: boolean } -const PADDING = 2; -const FONT_FAMILY = 'SofiaSans, Arial, sans-serif'; -const FONT_WEIGHT_NORMAL = 400; -const FONT_WEIGHT_SELECTED = 700; -const TEXT_FILL_RATIO = 0.85; -const STROKE_WIDTH_SELECTED = 1.5; -const STROKE_WIDTH_UNSELECTED = 0.5; const LIGHT_CANVAS_BACKGROUND = '#FFFFFF'; const DARK_CANVAS_BACKGROUND = '#1A1A1A'; const LIGHT_CANVAS_FOREGROUND = '#000000'; const DARK_CANVAS_FOREGROUND = '#F5F5F5'; -const LIGHT_DIMMED_NODE_FILL = '#E5E5E5'; -const DARK_DIMMED_NODE_FILL = '#525252'; -const LIGHT_DIMMED_NODE_STROKE = 'gray'; -const DARK_DIMMED_NODE_STROKE = '#A3A3A3'; + +const DOUBLE_CLICK_MS = 300; export default function GraphView({ data, - graph, chartRef: canvasRef, id, - selectedObj, - setSelectedObj, selectedObjects, setSelectedObjects, setPosition, @@ -67,20 +54,19 @@ export default function GraphView({ isPathResponse, selectedPathId, setSelectedPathId, - animation, zoomedNodes, - setZoomedNodes + setZoomedNodes, + manualDimmed }: Props) { - const lastClick = useRef<{ date: Date, name: string }>({ date: new Date(), name: "" }) + const lastClick = useRef<{ date: number, id: number }>({ date: 0, id: 0 }) + const isCenteringRef = useRef(false) const [screenSize, setScreenSize] = useState(0) const [hoverElement, setHoverElement] = useState() const { resolvedTheme } = useTheme() const isDark = resolvedTheme === 'dark' const canvasBackgroundColor = isDark ? DARK_CANVAS_BACKGROUND : LIGHT_CANVAS_BACKGROUND const canvasForegroundColor = isDark ? DARK_CANVAS_FOREGROUND : LIGHT_CANVAS_FOREGROUND - const dimmedNodeFillColor = isDark ? DARK_DIMMED_NODE_FILL : LIGHT_DIMMED_NODE_FILL - const dimmedNodeStrokeColor = isDark ? DARK_DIMMED_NODE_STROKE : LIGHT_DIMMED_NODE_STROKE useEffect(() => { const handleResize = () => { @@ -97,10 +83,9 @@ export default function GraphView({ }, []) const unsetSelectedObjects = useCallback((evt?: MouseEvent) => { - if (evt?.ctrlKey || (!selectedObj && selectedObjects.length === 0)) return - setSelectedObj(undefined) + if (evt?.ctrlKey || selectedObjects.length === 0) return setSelectedObjects([]) - }, [selectedObj, selectedObjects, setSelectedObj, setSelectedObjects]) + }, [selectedObjects, setSelectedObjects]) const handleRightClick = useCallback((element: Node | Link, evt: MouseEvent) => { if (evt.ctrlKey && "category" in element) { @@ -111,17 +96,84 @@ export default function GraphView({ setSelectedObjects([...selectedObjects, element as Node]) } } else { - setSelectedObjects([]) + setSelectedObjects([element as Node]) } - setSelectedObj(element) - setPosition({ x: evt.clientX, y: evt.clientY }) - }, [selectedObjects, setSelectedObjects, setSelectedObj, setPosition]) + // Center on node or link midpoint when focus mode is ON, then show menu + if ((manualDimmed || isPathResponse) && canvasRef.current) { + // Clear any existing menu immediately so it doesn't flash at old position + setPosition(undefined) + + const graphData = canvasRef.current.getGraphData() + let cx: number | undefined + let cy: number | undefined + + if ("category" in element) { + const graphNode = graphData?.nodes.find(n => n.id === element.id) + cx = graphNode?.x + cy = graphNode?.y + } else { + const src = graphData?.nodes.find(n => n.id === (element as Link).source) + const tgt = graphData?.nodes.find(n => n.id === (element as Link).target) + if (src?.x !== undefined && tgt?.x !== undefined) { + cx = ((src.x ?? 0) + (tgt.x ?? 0)) / 2 + cy = ((src.y ?? 0) + (tgt.y ?? 0)) / 2 + } + } + + if (cx !== undefined && cy !== undefined) { + isCenteringRef.current = true + canvasRef.current.centerAt(cx, cy, 300) + // Show menu after animation fully settles (400ms > 300ms animation) + setTimeout(() => { + isCenteringRef.current = false + const canvasBounds = canvasRef.current?.getBoundingClientRect() + if (canvasBounds) { + setPosition({ + x: canvasBounds.left + canvasBounds.width / 2, + y: canvasBounds.top + canvasBounds.height / 2, + zoom: canvasRef.current?.getZoom() ?? 1 + }) + } + }, 400) + return + } + } + // Focus mode OFF: use the node's actual screen-center so the gap in + // elementMenu is always the same fixed value regardless of where on the + // node the user clicked. + if ("category" in element && canvasRef.current) { + const gd = canvasRef.current.getGraphData() + const gn = gd?.nodes.find(n => n.id === element.id) + const vp = canvasRef.current.getViewport() + const cb = canvasRef.current.getBoundingClientRect() + if (gn?.x !== undefined && gn?.y !== undefined && vp && cb) { + const z = vp.zoom + const sx = (gn.x - vp.centerX) * z + cb.width / 2 + const sy = (gn.y - vp.centerY) * z + cb.height / 2 + setPosition({ x: cb.left + sx, y: cb.top + sy, zoom: z }) + return + } + } + setPosition({ x: evt.clientX, y: evt.clientY, zoom: canvasRef.current?.getZoom() ?? 1 }) + }, [selectedObjects, setSelectedObjects, setPosition, canvasRef, manualDimmed, isPathResponse]) const handleLinkClick = (link: Link, evt: MouseEvent) => { unsetSelectedObjects(evt) if (!isPathResponse || link.id === selectedPathId) return setSelectedPathId(link.id) + + // Zoom to fit selected path nodes with 2x multiplier + if (canvasRef.current) { + const graphData = canvasRef.current.getGraphData() + const selectedPathNodeIds = new Set(graphData?.nodes + .filter(n => n.data?.isPathSelected) + .map(n => n.id) ?? []) + + if (selectedPathNodeIds.size > 0) { + canvasRef.current.zoomToFit(2, (n: GraphNode) => selectedPathNodeIds.has(n.id)) + } + } } const handleNodeHover = useCallback((node: Node | null) => { @@ -135,31 +187,29 @@ export default function GraphView({ const isNodeSelected = useCallback((node: GraphNode) => { if (isPathResponse) { return node.data.isPathSelected - } else { - return selectedObjects.some(obj => "category" in obj && obj.id === node.id) || (selectedObj && "category" in selectedObj && selectedObj?.id === node.id) || (hoverElement && ('category' in hoverElement) && hoverElement.id === node.id) } - }, [isPathResponse, selectedObjects, selectedObj, hoverElement]) + // Highlight selected and hovered nodes - dimming handled separately by isNodeDimmed + return selectedObjects.some(obj => "category" in obj && obj.id === node.id) || (hoverElement && ('category' in hoverElement) && hoverElement.id === node.id) + }, [isPathResponse, selectedObjects, hoverElement]) const isLinkSelected = useCallback((link: GraphLink) => { if (isPathResponse) { return link.data.isPathSelected - } else { - return selectedObjects.some(obj => "source" in obj && obj.id === link.id) || (selectedObj && "source" in selectedObj && selectedObj?.id === link.id) || (hoverElement && 'source' in hoverElement && hoverElement.id === link.id) } - }, [isPathResponse, selectedObjects, selectedObj, hoverElement]) + // Highlight selected and hovered links - dimming handled separately by isLinkDimmed + return selectedObjects.some(obj => "source" in obj && obj.id === link.id) || (hoverElement && 'source' in hoverElement && hoverElement.id === link.id) + }, [isPathResponse, selectedObjects, hoverElement]) const handleNodeClick = useCallback(async (node: Node) => { - const now = new Date() - const { date, name } = lastClick.current + const now = Date.now() + const { date, id } = lastClick.current - const isDoubleClick = now.getTime() - date.getTime() < 1000 && name === node.data.name - lastClick.current = { date: now, name: node.data.name } + const isDoubleClick = now - date < DOUBLE_CLICK_MS && id === node.id + lastClick.current = isDoubleClick ? { date: 0, id: 0 } : { date: now, id: node.id } if (isDoubleClick) { - lastClick.current = { date: now, name: "" } handleExpand([node], !node.expand) } else if (isShowPath) { - lastClick.current = { date: now, name: "" } setPath(prev => { if (!prev?.start?.name || (prev.end?.name && prev.end?.name !== "")) { return ({ start: { id: Number(node.id), name: node.data.name } }) @@ -178,146 +228,102 @@ export default function GraphView({ } }, [zoomedNodes, canvasRef]) - const nodeCanvasObject = useCallback((node: GraphNode, ctx: CanvasRenderingContext2D) => { - if (node.x === undefined || node.y === undefined) { - node.x = 0; - node.y = 0; - } - - const isHovered = !!hoverElement && !('source' in hoverElement) && hoverElement.id === node.id - const isSelected = selectedObjects.some(obj => obj.id === node.id) || selectedObj?.id === node.id - const nodeSelected = isSelected || isHovered - - // --- Determine colors based on path state --- + const isNodeDimmed = useCallback((node: GraphNode) => { if (isPathResponse) { - if (node.data.isPathSelected) { - ctx.fillStyle = node.color; - ctx.strokeStyle = PATH_COLOR; - ctx.lineWidth = STROKE_WIDTH_SELECTED; - } else if (node.data.isPath) { - ctx.fillStyle = node.color; - ctx.strokeStyle = PATH_COLOR; - ctx.lineWidth = STROKE_WIDTH_UNSELECTED; - } else { - ctx.fillStyle = dimmedNodeFillColor; - ctx.strokeStyle = dimmedNodeStrokeColor; - ctx.lineWidth = STROKE_WIDTH_UNSELECTED; - } - } else if (isPathResponse === undefined) { - if (node.data.isPathSelected) { - ctx.fillStyle = node.color; - ctx.strokeStyle = PATH_COLOR; - ctx.lineWidth = STROKE_WIDTH_SELECTED; - } else if (node.data.isPath) { - ctx.fillStyle = node.color; - ctx.strokeStyle = PATH_COLOR; - ctx.lineWidth = STROKE_WIDTH_UNSELECTED; + return !node.data.isPath && !node.data.isPathSelected + } + // Only apply dimming when focus mode is ON (manualDimmed=true) + // When manualDimmed=false, nothing should be dimmed regardless of selection + if (!manualDimmed) { + return false + } + + if (selectedObjects.length === 0 && !hoverElement) return false + + // Collect all active elements (selected + hovered) + const activeElements: (Node | Link)[] = [...selectedObjects] + if (hoverElement && !activeElements.includes(hoverElement)) { + activeElements.push(hoverElement) + } + + // Build selected node IDs and link endpoint IDs + const selectedNodeIds = new Set() + const linkEndpointIds = new Set() + + for (const el of activeElements) { + if (!('source' in el)) { + selectedNodeIds.add((el as any).id) } else { - ctx.fillStyle = node.color; - ctx.strokeStyle = canvasForegroundColor; - ctx.lineWidth = nodeSelected ? STROKE_WIDTH_SELECTED : STROKE_WIDTH_UNSELECTED; + linkEndpointIds.add((el as any).source as number) + linkEndpointIds.add((el as any).target as number) } - } else { - ctx.fillStyle = node.color; - ctx.strokeStyle = canvasForegroundColor; - ctx.lineWidth = nodeSelected ? STROKE_WIDTH_SELECTED : STROKE_WIDTH_UNSELECTED; } - - const radius = NODE_SIZE + ctx.lineWidth / 2; - - // Draw stroke circle - ctx.beginPath(); - ctx.arc(node.x, node.y, radius, 0, 2 * Math.PI, false); - ctx.stroke(); - - // Draw fill circle - ctx.beginPath(); - ctx.arc(node.x, node.y, NODE_SIZE, 0, 2 * Math.PI, false); - ctx.fill(); - - // Skip labels when zoomed out (large graph optimisation) - const zoom = ctx.getTransform().a; - if (zoom < 1) return; - - // --- Draw text (matching canvas logic) --- - const fillColor = ctx.fillStyle as string; - ctx.fillStyle = getContrastTextColor(fillColor); - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - - const textRadius = NODE_SIZE - PADDING / 2; - const name = node.data.name || node.data.title || String(node.id); - const nodeFontWeight = nodeSelected ? FONT_WEIGHT_SELECTED : FONT_WEIGHT_NORMAL; - const baseFontSize = 4; - - // Measure at the base size for line-wrapping decisions - ctx.font = `${nodeFontWeight} ${baseFontSize}px ${FONT_FAMILY}`; - const [line1, line2] = wrapTextForCircularNode(ctx, name, textRadius); - - let chosenSize = baseFontSize; - - if (TEXT_FILL_RATIO > 0 && !line2) { - // Auto-size mode: scale text to fill textFillRatio × nodeRadius - const REF = 20; - ctx.font = `${nodeFontWeight} ${REF}px ${FONT_FAMILY}`; - const refMetrics = ctx.measureText(line1); - const visualWidth = (refMetrics.actualBoundingBoxLeft ?? 0) - + (refMetrics.actualBoundingBoxRight ?? 0); - const refWidth = Math.max(visualWidth, refMetrics.width); - const refHeight = (refMetrics.actualBoundingBoxAscent ?? 0) - + (refMetrics.actualBoundingBoxDescent ?? 0); - - const r = TEXT_FILL_RATIO * textRadius; - if (refWidth > 0 && refHeight > 0) { - const diagonal = Math.sqrt(refWidth * refWidth + refHeight * refHeight); - chosenSize = REF * (2 * r / diagonal); - } else if (refWidth > 0) { - chosenSize = REF * (2 * r / refWidth); + + const allActiveIds = new Set([...selectedNodeIds, ...linkEndpointIds]) + if (allActiveIds.size === 0) return false + if (allActiveIds.has(node.id)) return false + + // Expand neighbourhood only for directly selected nodes + for (const link of data.links) { + if ((selectedNodeIds.has(link.source) && link.target === node.id) || + (selectedNodeIds.has(link.target) && link.source === node.id)) { + return false } } + + return true + }, [isPathResponse, manualDimmed, selectedObjects, hoverElement, data.links]) - ctx.font = `${nodeFontWeight} ${chosenSize}px ${FONT_FAMILY}`; - - const textMetrics = ctx.measureText(line1); - const textHeight = textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent; - const halfTextHeight = (textHeight / 2) * 1.5; - - if (line1) { - const yCorrection = line2 - ? 0 - : (textMetrics.actualBoundingBoxAscent - textMetrics.actualBoundingBoxDescent) / 2; - ctx.fillText(line1, node.x, line2 ? node.y - halfTextHeight : node.y + yCorrection); + const isLinkDimmed = useCallback((link: GraphLink) => { + if (isPathResponse) { + return !link.data.isPath && !link.data.isPathSelected } - if (line2) { - ctx.fillText(line2, node.x, node.y + halfTextHeight); + // Only apply dimming when focus mode is ON (manualDimmed=true) + // When manualDimmed=false, nothing should be dimmed regardless of selection + if (!manualDimmed) { + return false } - }, [ - selectedObj, - selectedObjects, - isPathResponse, - hoverElement, - dimmedNodeFillColor, - dimmedNodeStrokeColor, - canvasForegroundColor, - ]) - - const nodePointerAreaPaint = useCallback((node: GraphNode, color: string, ctx: CanvasRenderingContext2D) => { - if (node.x === undefined || node.y === undefined) { - node.x = 0; - node.y = 0; + + if (selectedObjects.length === 0 && !hoverElement) return false + + // Don't dim the link itself if it's selected/hovered + if (hoverElement && 'source' in hoverElement && hoverElement.id === link.id) return false + if (selectedObjects.some(obj => 'source' in obj && obj.id === link.id)) return false + + // Collect all active elements (selected + hovered) + const activeElements: (Node | Link)[] = [...selectedObjects] + if (hoverElement && !activeElements.includes(hoverElement)) { + activeElements.push(hoverElement) + } + + // Build selected node IDs only (not link endpoints) + const selectedNodeIds = new Set() + for (const el of activeElements) { + if (!('source' in el)) { + selectedNodeIds.add((el as any).id) + } } + + if (selectedNodeIds.size === 0) return true + + // Link is undimmed if one endpoint is a directly selected node + const srcId = typeof link.source === 'object' ? (link.source as any).id : link.source + const tgtId = typeof link.target === 'object' ? (link.target as any).id : link.target + + if (selectedNodeIds.has(srcId) || selectedNodeIds.has(tgtId)) return false - ctx.fillStyle = color; - ctx.beginPath(); - ctx.arc(node.x, node.y, NODE_SIZE + 2 + ctx.lineWidth / 2, 0, 2 * Math.PI, false); - ctx.fill(); - }, []) + return true + }, [isPathResponse, manualDimmed, selectedObjects, hoverElement, data.links]) const linkLineDash = useCallback((link: GraphLink) => { if (link.data.isPath && !link.data.isPathSelected) return [5, 5] return [] }, []) + const handleNodeDragEnd = useCallback(() => { + setPosition(undefined) + }, [setPosition]) + const mobileBreakpointRaw = Number(import.meta.env.VITE_MOBILE_BREAKPOINT) const mobileBreakpoint = Number.isFinite(mobileBreakpointRaw) ? mobileBreakpointRaw : 0 const isDesktop = screenSize > mobileBreakpoint @@ -341,14 +347,15 @@ export default function GraphView({ onLinkHover={handleLinkHover} onLinkRightClick={handleRightClick} isLinkSelected={isLinkSelected} + isNodeDimmed={isNodeDimmed} + isLinkDimmed={isLinkDimmed} + dimmed={isPathResponse === true || manualDimmed} + linkLineDash={linkLineDash} onBackgroundClick={unsetSelectedObjects} onBackgroundRightClick={unsetSelectedObjects} - onZoom={() => unsetSelectedObjects()} + onZoom={() => { if (!isCenteringRef.current) unsetSelectedObjects() }} onEngineStop={handleEngineStop} - nodeCanvasObject={nodeCanvasObject} - nodePointerAreaPaint={nodePointerAreaPaint} - linkLineDash={linkLineDash} - animation={animation} + onNodeDragEnd={handleNodeDragEnd} backgroundColor={canvasBackgroundColor} foregroundColor={canvasForegroundColor} /> diff --git a/app/src/components/model.ts b/app/src/components/model.ts index 2fe40608..b40cc365 100644 --- a/app/src/components/model.ts +++ b/app/src/components/model.ts @@ -34,6 +34,7 @@ export interface Link { target: number, label: string, visible: boolean, + collapsed: boolean, isPathSelected: boolean, isPath: boolean, color: string, @@ -246,6 +247,7 @@ export class Graph { target: edgeData.dest_node, label: edgeData.relation, visible: true, + collapsed, color: "#999999", isPathSelected: false, isPath: !!path, @@ -260,12 +262,21 @@ export class Graph { return newElements } - public removeLinks() { + public removeLinks(ids: number[] = []) { + const connectedLinks = this.elements.links.filter(link => ids.includes(link.source) || ids.includes(link.target)) + this.elements = { nodes: this.elements.nodes, links: this.elements.links.map(link => { - if (this.nodesMap.get(link.source) && this.nodesMap.get(link.target)) { - return link + if ( + (ids.length !== 0 && !connectedLinks.includes(link)) || + (this.nodesMap.get(link.source) && this.nodesMap.get(link.target)) + ) { + if (link.collapsed && connectedLinks.includes(link)) { + // Remove collapsed links connected to the given ids even if both endpoints exist + } else { + return link + } } this.linksMap.delete(link.id) }).filter(link => link !== undefined) diff --git a/app/src/components/toolbar.tsx b/app/src/components/toolbar.tsx index 5553c1c7..052bb8a3 100644 --- a/app/src/components/toolbar.tsx +++ b/app/src/components/toolbar.tsx @@ -1,9 +1,10 @@ import { useState } from "react"; -import { ChevronDown, Circle, Download, Fullscreen, Pause, Pin, PinOff, Play, ZoomIn, ZoomOut } from "lucide-react"; +import { ChevronDown, Circle, Download, Fullscreen, Pause, Pin, PinOff, Play, Telescope, ZoomIn, ZoomOut } from "lucide-react"; import type { LayoutMode, HierarchyDirection, RadialDirection } from "@falkordb/canvas"; import { cn } from "@/lib/utils" import { GraphRef } from "@/lib/utils"; import { Switch } from "@/components/ui/switch"; +import type { Node, Link } from "./model"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; const LAYOUTS: { value: LayoutMode; label: string }[] = [ @@ -24,15 +25,66 @@ const RADIAL_DIRECTIONS: { value: RadialDirection; label: string }[] = [ { value: 'in', label: 'Inward' }, ]; +interface ZoomControlsProps { + canvasRef: GraphRef + className?: string + selectedObjects?: (Node | Link)[] +} + +export function ZoomControls({ canvasRef, className, selectedObjects }: ZoomControlsProps) { + const handleZoomClick = (changefactor: number) => { + const canvas = canvasRef.current + if (!canvas) return + if (selectedObjects && selectedObjects.length > 0) { + const graphData = canvas.getGraphData() + const selectedNodeIds = new Set() + for (const el of selectedObjects) { + if ('source' in el) { + selectedNodeIds.add(el.source as number) + selectedNodeIds.add(el.target as number) + } else { + selectedNodeIds.add(el.id) + } + } + const focusedNodes = graphData?.nodes.filter(n => selectedNodeIds.has(n.id)) ?? [] + if (focusedNodes.length > 0) { + const cx = focusedNodes.reduce((s, n) => s + (n.x ?? 0), 0) / focusedNodes.length + const cy = focusedNodes.reduce((s, n) => s + (n.y ?? 0), 0) / focusedNodes.length + canvas.centerAt(cx, cy, 300) + } + } + canvas.zoom(canvas.getZoom() * changefactor) + } + + return ( +
+ + + +
+ ) +} + interface Props { canvasRef: GraphRef className?: string handleDownloadImage?: () => void animation: boolean setAnimation: (animation: boolean) => void + manualDimmed: boolean + setManualDimmed: (dimmed: boolean) => void + selectedObjects?: (Node | Link)[] + /** Hide the three zoom buttons and download, show everything else. */ + hideZoom?: boolean } -export function Toolbar({ canvasRef, className, handleDownloadImage, animation, setAnimation }: Props) { +export function Toolbar({ canvasRef, className, handleDownloadImage, animation, setAnimation, manualDimmed, setManualDimmed, selectedObjects, hideZoom: hideZoom }: Props) { const [layout, setLayout] = useState('force'); const [direction, setDirection] = useState(''); @@ -41,9 +93,32 @@ export function Toolbar({ canvasRef, className, handleDownloadImage, animation, const handleZoomClick = (changefactor: number) => { const canvas = canvasRef.current - if (canvas) { - canvas.zoom(canvas.getZoom() * changefactor) + if (!canvas) return + + if (selectedObjects && selectedObjects.length > 0) { + const graphData = canvas.getGraphData() + const selectedNodeIds = new Set() + + for (const el of selectedObjects) { + if ('source' in el) { + // It's a link - center on both endpoints + selectedNodeIds.add(el.source as number) + selectedNodeIds.add(el.target as number) + } else { + // It's a node + selectedNodeIds.add(el.id) + } + } + + const focusedNodes = graphData?.nodes.filter(n => selectedNodeIds.has(n.id)) ?? [] + if (focusedNodes.length > 0) { + const cx = focusedNodes.reduce((s, n) => s + (n.x ?? 0), 0) / focusedNodes.length + const cy = focusedNodes.reduce((s, n) => s + (n.y ?? 0), 0) / focusedNodes.length + canvas.centerAt(cx, cy, 300) + } } + + canvas.zoom(canvas.getZoom() * changefactor) } const handleCenterClick = () => { @@ -55,7 +130,14 @@ export function Toolbar({ canvasRef, className, handleDownloadImage, animation, } const handleAnimationToggle = () => { - setAnimation(!animation) + const next = !animation; + setAnimation(next); + canvasRef.current?.setAnimation(next); + } + + const handleDimToggle = (checked: boolean) => { + setManualDimmed(checked); + canvasRef.current?.setDimmed(checked); } const handlePinToggle = () => { @@ -110,6 +192,12 @@ export function Toolbar({ canvasRef, className, handleDownloadImage, animation, disabled={animationDisabled} onCheckedChange={handleAnimationToggle} /> + + - - - - -
@@ -146,6 +146,8 @@ export function Toolbar({ canvasRef, className, handleDownloadImage, animation, canvasRef.current?.setPinOnDragEnd(next); } + const isAnimationDisabled = layout !== 'force' || pinned + const handleLayoutChange = (value: string) => { const mode = value as LayoutMode; setLayout(mode); @@ -164,10 +166,16 @@ export function Toolbar({ canvasRef, className, handleDownloadImage, animation, canvasRef.current?.setLayout(mode); - // Non-force layouts auto-pin + // Non-force layouts auto-pin and auto-disable animation const nextPinned = mode !== 'force'; setPinned(nextPinned); canvasRef.current?.setPinOnDragEnd(nextPinned); + + // If switching to non-force layout, disable animation + if (mode !== 'force' && animation) { + setAnimation(false); + canvasRef.current?.setAnimation(false); + } } const handleDirectionChange = (value: string, targetLayout?: string) => { diff --git a/app/src/lib/utils.ts b/app/src/lib/utils.ts index a67780c7..1a8d6af3 100644 --- a/app/src/lib/utils.ts +++ b/app/src/lib/utils.ts @@ -27,13 +27,12 @@ export enum MessageTypes { Text, } -let messageIdCounter = 0; export function createMessage(msg: Omit): Message { - return { ...msg, id: ++messageIdCounter }; + return { ...msg, id: crypto.randomUUID() }; } export interface Message { - id: number; + id: string; type: MessageTypes; text?: string; paths?: { nodes: any[], links: any[] }[]; diff --git a/e2e/tests/toolbar.spec.ts b/e2e/tests/toolbar.spec.ts index d7a4faf1..2b1f4405 100644 --- a/e2e/tests/toolbar.spec.ts +++ b/e2e/tests/toolbar.spec.ts @@ -46,7 +46,7 @@ test.describe("Toolbar and Zoom Controls tests", () => { // Zoom in first await codeGraph.clickZoomIn(); await codeGraph.clickZoomIn(); - const zoomedScale = await codeGraph.getCanvasScaling(); + await codeGraph.getCanvasScaling(); // Click center to reset await codeGraph.clickCenter(); From 7b0cde03279be125a0f384789a7bea120ad794d6 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Wed, 8 Jul 2026 14:15:20 +0300 Subject: [PATCH 05/41] fix: resolve build and compilation errors - Fix missing closing brace in graphView.tsx setTimeout handler - Remove unused cooldownTicks state from App.tsx and CodeGraph - Fix chat.tsx path markings to iterate nodes and links separately - Remove stale EDGE_MARGIN references in elementMenu.tsx --- app/src/App.tsx | 4 ---- app/src/components/chat.tsx | 14 ++++++++------ app/src/components/code-graph.tsx | 4 ---- app/src/components/elementMenu.tsx | 2 +- app/src/components/graphView.tsx | 3 ++- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/app/src/App.tsx b/app/src/App.tsx index 91da2ac6..4a2e731a 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -490,8 +490,6 @@ export default function App() { handleSearchSubmit={(node) => handleSearchSubmit(node, desktopChartRef)} searchNode={searchNode} setSearchNode={setSearchNode} - cooldownTicks={cooldownTicks} - setCooldownTicks={setCooldownTicks} animation={animation} setAnimation={setAnimation} manualDimmed={manualDimmed} @@ -620,8 +618,6 @@ export default function App() { handleSearchSubmit={(node) => handleSearchSubmit(node, mobileChartRef)} setSearchNode={setSearchNode} searchNode={searchNode} - cooldownTicks={cooldownTicks} - setCooldownTicks={setCooldownTicks} animation={animation} setAnimation={setAnimation} manualDimmed={manualDimmed} diff --git a/app/src/components/chat.tsx b/app/src/components/chat.tsx index 564527d7..80dce2d3 100644 --- a/app/src/components/chat.tsx +++ b/app/src/components/chat.tsx @@ -329,12 +329,14 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set setIsPathResponse(true) // Clear previous path markings before applying new ones - graph.Elements.forEach((element: any) => { - element.isPath = false; - element.isPathSelected = false; - if ("source" in element) { - element.color = "#999999"; - } + graph.Elements.nodes.forEach((node: any) => { + node.isPath = false; + node.isPathSelected = false; + }); + graph.Elements.links.forEach((link: any) => { + link.isPath = false; + link.isPathSelected = false; + link.color = "#999999"; }); // Mark path elements on the model diff --git a/app/src/components/code-graph.tsx b/app/src/components/code-graph.tsx index 9cc69d33..1666c3c2 100644 --- a/app/src/components/code-graph.tsx +++ b/app/src/components/code-graph.tsx @@ -40,8 +40,6 @@ interface Props { handleSearchSubmit: (node: any) => void searchNode: any setSearchNode: Dispatch> - cooldownTicks: number | undefined - setCooldownTicks: Dispatch> animation: boolean setAnimation: (animation: boolean) => void manualDimmed: boolean @@ -74,8 +72,6 @@ export function CodeGraph({ handleSearchSubmit, searchNode, setSearchNode, - cooldownTicks, - setCooldownTicks, animation, setAnimation, manualDimmed, diff --git a/app/src/components/elementMenu.tsx b/app/src/components/elementMenu.tsx index 4d5ca618..b532e7a6 100644 --- a/app/src/components/elementMenu.tsx +++ b/app/src/components/elementMenu.tsx @@ -50,7 +50,7 @@ export default function ElementMenu({ obj, objects, setPath, handleRemove, posit // X axis: center on click, clamp to container edges const rawLeft = relX - containerWidth / 2 - const left = Math.max(EDGE_MARGIN, Math.min(rawLeft, parentW - containerWidth - EDGE_MARGIN)) + const left = Math.max(edgeMargin, Math.min(rawLeft, parentW - containerWidth - edgeMargin)) // Y axis: always place below at (node_center + node_radius + fixed_gap). // Clamp to canvas edges — no flipping. If the node is larger than the canvas diff --git a/app/src/components/graphView.tsx b/app/src/components/graphView.tsx index bb37f00d..8082bebb 100644 --- a/app/src/components/graphView.tsx +++ b/app/src/components/graphView.tsx @@ -125,7 +125,7 @@ export default function GraphView({ isCenteringRef.current = true canvasRef.current.centerAt(cx, cy, 300) // Show menu after animation fully settles (400ms > 300ms animation) - const timeoutId = setTimeout(() => { + setTimeout(() => { isCenteringRef.current = false const canvasBounds = canvasRef.current?.getBoundingClientRect() if (canvasBounds) { @@ -137,6 +137,7 @@ export default function GraphView({ } }, 400) return + } } // Focus mode OFF: use the node's actual screen-center so the gap in // elementMenu is always the same fixed value regardless of where on the From da01b496fa946169ab1d93aca42d22c05a2af23a Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Wed, 8 Jul 2026 14:36:49 +0300 Subject: [PATCH 06/41] Fix remaining PR #697 code quality issues - Add aria-labels to animation, dimming, and pin toggle switches for accessibility - Add setData() calls after graph mutations in handleExpand and handleRemove to keep React state synced - Remove unused dimmed dependency from ForceGraph setConfig effect - All changes maintain existing code style and pass TypeScript compilation --- app/src/components/ForceGraph.tsx | 1 - app/src/components/code-graph.tsx | 4 ++++ app/src/components/graphView.tsx | 4 ++-- app/src/components/toolbar.tsx | 3 +++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/components/ForceGraph.tsx b/app/src/components/ForceGraph.tsx index dd915a00..71819a68 100644 --- a/app/src/components/ForceGraph.tsx +++ b/app/src/components/ForceGraph.tsx @@ -187,7 +187,6 @@ export default function ForceGraph({ isLinkSelected, isNodeDimmed, isLinkDimmed, - dimmed, onBackgroundClick, onBackgroundRightClick, handleEngineStop, diff --git a/app/src/components/code-graph.tsx b/app/src/components/code-graph.tsx index 1666c3c2..d30d492a 100644 --- a/app/src/components/code-graph.tsx +++ b/app/src/components/code-graph.tsx @@ -243,6 +243,7 @@ export function CodeGraph({ // Update the model with new elements (graph.Elements is already updated by onFetchNode) // Convert the full model to canvas data and update canvasRef.current?.setGraphData(convertToCanvasData(graph.Elements)) + setData({ ...graph.Elements }) } else { const deleteNodes = nodes.filter(n => n.expand) if (deleteNodes.length > 0) { @@ -256,6 +257,7 @@ export function CodeGraph({ // Convert the updated model to canvas data canvasRef.current?.setGraphData(convertToCanvasData(graph.Elements)) + setData({ ...graph.Elements }) } setSelectedObjects([]) @@ -289,6 +291,8 @@ export function CodeGraph({ setHasHiddenElements(true) + setData({ ...graph.Elements }) + setSelectedObjects([]) } diff --git a/app/src/components/graphView.tsx b/app/src/components/graphView.tsx index 8082bebb..f49fc28b 100644 --- a/app/src/components/graphView.tsx +++ b/app/src/components/graphView.tsx @@ -176,7 +176,7 @@ export default function GraphView({ canvasRef.current.zoomToFit(2, (n: GraphNode) => selectedPathNodeIds.has(n.id)) } } - }, [selectedPathId, isPathResponse]) + }, [selectedPathId, isPathResponse, canvasRef]) const handleNodeHover = useCallback((node: Node | null) => { setHoverElement(node) @@ -345,7 +345,7 @@ export default function GraphView({ onNodeHover={handleNodeHover} onNodeRightClick={handleRightClick} isNodeSelected={isNodeSelected} - onLinkClick={isDesktop && isPathResponse ? handleLinkClick : handleRightClick} + onLinkClick={isDesktop ? handleLinkClick : handleRightClick} onLinkHover={handleLinkHover} onLinkRightClick={handleRightClick} isLinkSelected={isLinkSelected} diff --git a/app/src/components/toolbar.tsx b/app/src/components/toolbar.tsx index 23abfe51..d95d53f2 100644 --- a/app/src/components/toolbar.tsx +++ b/app/src/components/toolbar.tsx @@ -199,17 +199,20 @@ export function Toolbar({ canvasRef, className, handleDownloadImage, animation, checked={animation} disabled={animationDisabled} onCheckedChange={handleAnimationToggle} + aria-label={animation ? "Pause animation" : "Play animation"} /> From b86d5f966868b00b547b76b28661dedfe2c66fea Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Wed, 8 Jul 2026 14:40:46 +0300 Subject: [PATCH 07/41] Improve path zoom handling and remove unnecessary state updates - Move path zoom logic from graphView useEffect to chat.tsx for better stability - Remove unnecessary setData() calls - setGraphData() is sufficient for canvas updates - Cap zoom multiplier for path links (1.3x) since they take less visual space than nodes - All zoom handling now happens in chat.tsx where path responses originate --- app/src/components/chat.tsx | 5 ++++- app/src/components/code-graph.tsx | 4 ---- app/src/components/graphView.tsx | 14 -------------- 3 files changed, 4 insertions(+), 19 deletions(-) diff --git a/app/src/components/chat.tsx b/app/src/components/chat.tsx index 80dce2d3..7c4932f6 100644 --- a/app/src/components/chat.tsx +++ b/app/src/components/chat.tsx @@ -365,7 +365,10 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set setTimeout(() => { const nodesMap = new Map(formattedPaths.flatMap(p => p.nodes.map((n: Node) => [n.id, n]))) - canvas.zoomToFit(2, (n: GraphNode) => formattedPaths.some(p => nodesMap.has(n.id))); + // Cap zoom multiplier for links (1.3x) since links take less visual space than nodes (2x) + const hasLinks = formattedPaths.some(p => p.links.length > 0) + const multiplier = hasLinks ? 1.3 : 2 + canvas.zoomToFit(multiplier, (n: GraphNode) => formattedPaths.some(p => nodesMap.has(n.id))); }, 0) } diff --git a/app/src/components/code-graph.tsx b/app/src/components/code-graph.tsx index d30d492a..1666c3c2 100644 --- a/app/src/components/code-graph.tsx +++ b/app/src/components/code-graph.tsx @@ -243,7 +243,6 @@ export function CodeGraph({ // Update the model with new elements (graph.Elements is already updated by onFetchNode) // Convert the full model to canvas data and update canvasRef.current?.setGraphData(convertToCanvasData(graph.Elements)) - setData({ ...graph.Elements }) } else { const deleteNodes = nodes.filter(n => n.expand) if (deleteNodes.length > 0) { @@ -257,7 +256,6 @@ export function CodeGraph({ // Convert the updated model to canvas data canvasRef.current?.setGraphData(convertToCanvasData(graph.Elements)) - setData({ ...graph.Elements }) } setSelectedObjects([]) @@ -291,8 +289,6 @@ export function CodeGraph({ setHasHiddenElements(true) - setData({ ...graph.Elements }) - setSelectedObjects([]) } diff --git a/app/src/components/graphView.tsx b/app/src/components/graphView.tsx index f49fc28b..886987d5 100644 --- a/app/src/components/graphView.tsx +++ b/app/src/components/graphView.tsx @@ -164,20 +164,6 @@ export default function GraphView({ setSelectedPathId(link.id) } - // Zoom to fit selected path nodes when selectedPathId changes - useEffect(() => { - if (selectedPathId && canvasRef.current && isPathResponse) { - const graphData = canvasRef.current.getGraphData() - const selectedPathNodeIds = new Set(graphData?.nodes - .filter(n => n.data?.isPathSelected) - .map(n => n.id) ?? []) - - if (selectedPathNodeIds.size > 0) { - canvasRef.current.zoomToFit(2, (n: GraphNode) => selectedPathNodeIds.has(n.id)) - } - } - }, [selectedPathId, isPathResponse, canvasRef]) - const handleNodeHover = useCallback((node: Node | null) => { setHoverElement(node) }, []) From bcf5e7ad47cf6d4f611e2095dac08cf353c2254a Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Wed, 8 Jul 2026 14:42:42 +0300 Subject: [PATCH 08/41] Reduce link zoom multiplier to 1.1x for better spacing --- app/src/components/chat.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/components/chat.tsx b/app/src/components/chat.tsx index 7c4932f6..a8de3864 100644 --- a/app/src/components/chat.tsx +++ b/app/src/components/chat.tsx @@ -365,9 +365,9 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set setTimeout(() => { const nodesMap = new Map(formattedPaths.flatMap(p => p.nodes.map((n: Node) => [n.id, n]))) - // Cap zoom multiplier for links (1.3x) since links take less visual space than nodes (2x) + // Cap zoom multiplier for links (1.1x) since links take very little visual space const hasLinks = formattedPaths.some(p => p.links.length > 0) - const multiplier = hasLinks ? 1.3 : 2 + const multiplier = hasLinks ? 1.1 : 2 canvas.zoomToFit(multiplier, (n: GraphNode) => formattedPaths.some(p => nodesMap.has(n.id))); }, 0) } From bf30fd76e80605f1b71837d75fd6ce234069c7d0 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Wed, 8 Jul 2026 14:45:31 +0300 Subject: [PATCH 09/41] Remove automatic path zoom - just highlight paths without forcing zoom --- app/src/components/chat.tsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/app/src/components/chat.tsx b/app/src/components/chat.tsx index a8de3864..fd19c981 100644 --- a/app/src/components/chat.tsx +++ b/app/src/components/chat.tsx @@ -356,20 +356,6 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set // Update the canvas from the model canvasRef.current?.setGraphData(convertToCanvasData(graph.Elements)) - - // Zoom to fit all path nodes after coloring - const pathNodeIds = new Set(formattedPaths.flatMap(p => p.nodes).map(n => n.id)) - if (pathNodeIds.size > 0 && canvasRef.current) { - canvasRef.current.zoomToFit(1, (n: any) => pathNodeIds.has(n.id)) - } - - setTimeout(() => { - const nodesMap = new Map(formattedPaths.flatMap(p => p.nodes.map((n: Node) => [n.id, n]))) - // Cap zoom multiplier for links (1.1x) since links take very little visual space - const hasLinks = formattedPaths.some(p => p.links.length > 0) - const multiplier = hasLinks ? 1.1 : 2 - canvas.zoomToFit(multiplier, (n: GraphNode) => formattedPaths.some(p => nodesMap.has(n.id))); - }, 0) } const getTip = (className?: string) => From d64403ea415376b0a614f4ed998d4ed05a2bf955 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Wed, 8 Jul 2026 14:55:27 +0300 Subject: [PATCH 10/41] fix: improve path zoom behavior - zoom all paths on response, then zoom selected path - Remove default path selection on response - Add zoom to fit all paths (multiplier 1.2) when paths arrive - Add zoom to fit selected path nodes (multiplier 1.5) when path is clicked - Use setTimeout to ensure canvas is ready before zooming --- app/src/components/chat.tsx | 46 ++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/app/src/components/chat.tsx b/app/src/components/chat.tsx index fd19c981..8c38243b 100644 --- a/app/src/components/chat.tsx +++ b/app/src/components/chat.tsx @@ -110,6 +110,44 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set setPaths([]) }, [isPathResponse]) + // Update selected path highlighting when selectedPathId changes + useEffect(() => { + if (!selectedPathId || !isPathResponse || paths.length === 0) return + + const canvas = canvasRef.current + if (!canvas) return + + // Find which path contains the selected link + const selectedPath = paths.find(p => p.links.some(l => l.id === selectedPathId)) + if (!selectedPath) return + + // Clear all path selections first + graph.Elements.nodes.forEach((node: any) => { + if (node.isPathSelected) node.isPathSelected = false + }) + graph.Elements.links.forEach((link: any) => { + if (link.isPathSelected) link.isPathSelected = false + }) + + // Mark selected path elements + selectedPath.nodes.forEach(n => { + const node = graph.Elements.nodes.find(gn => gn.id === n.id) + if (node) node.isPathSelected = true + }) + selectedPath.links.forEach(l => { + const link = graph.Elements.links.find(gl => gl.id === l.id) + if (link) link.isPathSelected = true + }) + + canvas.setGraphData(convertToCanvasData(graph.Elements)) + + // Zoom to fit selected path nodes + const selectedNodeIds = new Set(selectedPath.nodes.map((n: Node) => n.id)) + setTimeout(() => { + canvas.zoomToFit(1.5, (n: GraphNode) => selectedNodeIds.has(n.id)) + }, 100) + }, [selectedPathId, isPathResponse, paths]) + const handleSetSelectedPath = (p: PathData) => { const canvas = canvasRef.current @@ -355,7 +393,13 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set }); // Update the canvas from the model - canvasRef.current?.setGraphData(convertToCanvasData(graph.Elements)) + const pathCanvas = canvasRef.current + pathCanvas?.setGraphData(convertToCanvasData(graph.Elements)) + + // Zoom to fit all paths + setTimeout(() => { + pathCanvas?.zoomToFit(1.2) + }, 100) } const getTip = (className?: string) => From babe0ba1b519f8abf49d24e31e9237bdf02cb87a Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Wed, 8 Jul 2026 16:48:08 +0300 Subject: [PATCH 11/41] fix: add graphId to ForceGraph for incremental data updates - Use canvas.setData() only when graph identity changes (full reset) - Use canvas.setGraphData() for same-graph data updates (preserves positions) - Pass graphId from graph.Id through graphView to ForceGraph - Prevents unnecessary canvas remounting when data changes - Fixes z-index for element menu (z-[15] above toolbar, below data panel) --- app/package-lock.json | 189 +++++------------------------ app/package.json | 2 +- app/src/components/ForceGraph.tsx | 19 ++- app/src/components/chat.tsx | 9 +- app/src/components/elementMenu.tsx | 2 +- app/src/components/graphView.tsx | 2 + 6 files changed, 53 insertions(+), 170 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 4c55da26..7f8a9b78 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -8,7 +8,7 @@ "name": "code-graph", "version": "0.3.0", "dependencies": { - "@falkordb/canvas": "v0.2.2", + "@falkordb/canvas": "file:../../falkordb-canvas", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -52,6 +52,31 @@ "vite": "^6.3.5" } }, + "../../falkordb-canvas": { + "name": "@falkordb/canvas", + "version": "0.0.0-dev", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "force-graph": "^1.44.4", + "react": "^19.2.3" + }, + "devDependencies": { + "@types/d3": "^7.4.3", + "@types/react": "^19.2.7", + "@typescript-eslint/eslint-plugin": "^8.18.2", + "@typescript-eslint/parser": "^8.18.2", + "eslint": "^10.0.2", + "jsdom": "^29.0.2", + "promise-retry": "^2.0.1", + "tsup": "^8.5.1", + "typescript": "^6.0.3", + "vitest": "^4.1.4" + } + }, + "../falkordb-canvas": { + "extraneous": true + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -516,24 +541,8 @@ } }, "node_modules/@falkordb/canvas": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.2.2.tgz", - "integrity": "sha512-oGo4/tXrLqUzzf39wjpcZ2lfoSo0InTsHqORGBpLVyQtgyNRSLgNdgKUrAbO3vjVnjbfNFzKIwHrUJFf2R6mHA==", - "license": "MIT", - "dependencies": { - "d3": "^7.9.0", - "force-graph": "^1.44.4", - "react": "^19.2.3" - } - }, - "node_modules/@falkordb/canvas/node_modules/react": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", - "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "resolved": "../../falkordb-canvas", + "link": true }, "node_modules/@floating-ui/core": { "version": "1.7.5", @@ -2302,12 +2311,6 @@ "@swc/counter": "^0.1.3" } }, - "node_modules/@tweenjs/tween.js": { - "version": "25.0.0", - "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", - "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", - "license": "MIT" - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2403,15 +2406,6 @@ "vite": "^4 || ^5 || ^6 || ^7" } }, - "node_modules/accessor-fn": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.3.tgz", - "integrity": "sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -2508,16 +2502,6 @@ "node": ">=6.0.0" } }, - "node_modules/bezier-js": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-6.1.4.tgz", - "integrity": "sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==", - "license": "MIT", - "funding": { - "type": "individual", - "url": "https://github.com/Pomax/bezierjs/blob/master/FUNDING.md" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2606,18 +2590,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/canvas-color-tracker": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.3.2.tgz", - "integrity": "sha512-ryQkDX26yJ3CXzb3hxUVNlg1NKE4REc5crLBq661Nxzr8TNd236SaEf2ffYLXyI5tSABSeguHLqcVq4vf9L3Zg==", - "license": "MIT", - "dependencies": { - "tinycolor2": "^1.6.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/canvas2svg": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/canvas2svg/-/canvas2svg-1.0.16.tgz", @@ -2860,12 +2832,6 @@ "node": ">=12" } }, - "node_modules/d3-binarytree": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", - "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", - "license": "MIT" - }, "node_modules/d3-brush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", @@ -3009,22 +2975,6 @@ "node": ">=12" } }, - "node_modules/d3-force-3d": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", - "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", - "license": "MIT", - "dependencies": { - "d3-binarytree": "1", - "d3-dispatch": "1 - 3", - "d3-octree": "1", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-format": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", @@ -3067,12 +3017,6 @@ "node": ">=12" } }, - "node_modules/d3-octree": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", - "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", - "license": "MIT" - }, "node_modules/d3-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", @@ -3403,46 +3347,6 @@ "node": ">=8" } }, - "node_modules/float-tooltip": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/float-tooltip/-/float-tooltip-1.7.5.tgz", - "integrity": "sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==", - "license": "MIT", - "dependencies": { - "d3-selection": "2 - 3", - "kapsule": "^1.16", - "preact": "10" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/force-graph": { - "version": "1.51.4", - "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.51.4.tgz", - "integrity": "sha512-TdJ2KbkoiDQ7NIRx8IPGD0mAXXpLhamS7c+b7W98b0MHG7lphnda1VOQX/98UDTsttIAdH4TcP0l0MauSnLK8w==", - "license": "MIT", - "dependencies": { - "@tweenjs/tween.js": "18 - 25", - "accessor-fn": "1", - "bezier-js": "3 - 6", - "canvas-color-tracker": "^1.3", - "d3-array": "1 - 3", - "d3-drag": "2 - 3", - "d3-force-3d": "2 - 3", - "d3-scale": "1 - 4", - "d3-scale-chromatic": "1 - 3", - "d3-selection": "2 - 3", - "d3-zoom": "2 - 3", - "float-tooltip": "^1.7", - "index-array-by": "1", - "kapsule": "^1.16", - "lodash-es": "4" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -3588,15 +3492,6 @@ "node": ">=0.10.0" } }, - "node_modules/index-array-by": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/index-array-by/-/index-array-by-1.4.2.tgz", - "integrity": "sha512-SP23P27OUKzXWEC/TOyWlwLviofQkCSCKONnc62eItjp69yCZZPqDQtr3Pw5gJDnPeUMqExmKydNZaJO0FU9pw==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -3728,18 +3623,6 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, - "node_modules/kapsule": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.16.3.tgz", - "integrity": "sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==", - "license": "MIT", - "dependencies": { - "lodash-es": "4" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -4100,16 +3983,6 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, - "node_modules/preact": { - "version": "10.29.5", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.5.tgz", - "integrity": "sha512-zIai7HLEIz9c4GNfqpiuu5K9I1szXNtx0Ykr8f1Vbm1NasSVIvXGRxc8R8alLC+G+zonAt0TiUUBMn4+CiTgaA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -4653,12 +4526,6 @@ "node": ">=0.8" } }, - "node_modules/tinycolor2": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", - "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", diff --git a/app/package.json b/app/package.json index 6a5e1fe4..0d7c2e83 100644 --- a/app/package.json +++ b/app/package.json @@ -11,7 +11,7 @@ "preview": "vite preview" }, "dependencies": { - "@falkordb/canvas": "v0.2.2", + "@falkordb/canvas": "file:../../falkordb-canvas", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", diff --git a/app/src/components/ForceGraph.tsx b/app/src/components/ForceGraph.tsx index 71819a68..41549f86 100644 --- a/app/src/components/ForceGraph.tsx +++ b/app/src/components/ForceGraph.tsx @@ -1,11 +1,12 @@ -import { useCallback, useEffect, useState } from "react" +import { useCallback, useEffect, useRef, useState } from "react" import type { Data, GraphLink, GraphNode } from "@falkordb/canvas" import { GraphRef, PATH_COLOR } from "@/lib/utils" import { GraphData, Link, Node } from "./model" interface Props { id: "desktop" | "mobile" + graphId: string data: GraphData canvasRef: GraphRef onNodeClick: (node: Node, event: MouseEvent) => void @@ -53,6 +54,7 @@ export const convertToCanvasData = (graphData: GraphData): Data => ({ export default function ForceGraph({ id, + graphId, data, canvasRef, onNodeClick, @@ -203,14 +205,25 @@ export default function ForceGraph({ canvasRef.current.setDimmed(dimmed) }, [dimmed, canvasRef, canvasLoaded]) + // Track the last graphId that triggered a full setData + const lastGraphIdRef = useRef(undefined) + // Update canvas data useEffect(() => { const canvas = canvasRef.current if (!canvas || !canvasLoaded) return const canvasData = convertToCanvasData(data) - canvas.setData(canvasData) - }, [canvasRef, data, canvasLoaded]) + + if (graphId !== lastGraphIdRef.current) { + // New graph identity: full replacement (resets positions, runs simulation) + lastGraphIdRef.current = graphId + canvas.setData(canvasData) + } else { + // Same graph: incremental update (preserves node positions) + canvas.setGraphData(canvasData) + } + }, [canvasRef, graphId, data, canvasLoaded]) return ( diff --git a/app/src/components/chat.tsx b/app/src/components/chat.tsx index 8c38243b..e1409fbb 100644 --- a/app/src/components/chat.tsx +++ b/app/src/components/chat.tsx @@ -144,7 +144,7 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set // Zoom to fit selected path nodes const selectedNodeIds = new Set(selectedPath.nodes.map((n: Node) => n.id)) setTimeout(() => { - canvas.zoomToFit(1.5, (n: GraphNode) => selectedNodeIds.has(n.id)) + canvas.zoomToFit(1, (n: GraphNode) => selectedNodeIds.has(n.id)) }, 100) }, [selectedPathId, isPathResponse, paths]) @@ -222,7 +222,7 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set canvas.setGraphData(convertToCanvasData(graph.Elements)) setTimeout(() => { - canvas.zoomToFit(2, (n: GraphNode) => pNodeIds.has(n.id)); + canvas.zoomToFit(1, (n: GraphNode) => pNodeIds.has(n.id)); }, 300) setChatOpen && setChatOpen(false) } @@ -396,9 +396,10 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set const pathCanvas = canvasRef.current pathCanvas?.setGraphData(convertToCanvasData(graph.Elements)) - // Zoom to fit all paths + // Zoom to fit all path nodes (same as selected path zoom but for all paths) + const allPathNodeIds = new Set(formattedPaths.flatMap(p => p.nodes).map((n: Node) => n.id)) setTimeout(() => { - pathCanvas?.zoomToFit(1.2) + pathCanvas?.zoomToFit(1, (n: GraphNode) => allPathNodeIds.has(n.id)) }, 100) } diff --git a/app/src/components/elementMenu.tsx b/app/src/components/elementMenu.tsx index b532e7a6..fdc57c16 100644 --- a/app/src/components/elementMenu.tsx +++ b/app/src/components/elementMenu.tsx @@ -66,7 +66,7 @@ export default function ElementMenu({ obj, objects, setPath, handleRemove, posit setContainerWidth(ref.clientWidth) setContainerHeight(ref.clientHeight) }} - className="absolute z-10 bg-popover text-popover-foreground rounded-lg shadow-lg flex divide-x divide-border" + className="absolute z-[15] bg-popover text-popover-foreground rounded-lg shadow-lg flex divide-x divide-border" id="elementMenu" style={{ left, top }} > diff --git a/app/src/components/graphView.tsx b/app/src/components/graphView.tsx index 886987d5..951b2f4c 100644 --- a/app/src/components/graphView.tsx +++ b/app/src/components/graphView.tsx @@ -43,6 +43,7 @@ const DOUBLE_CLICK_MS = 300; export default function GraphView({ data, + graph, chartRef: canvasRef, id, selectedObjects, @@ -325,6 +326,7 @@ export default function GraphView({
handleNodeClick(node) : (node: Node, evt: MouseEvent) => handleRightClick(node, evt)} From 6745207577f0db616406fcf22cbe62cb47d5e0f3 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 9 Jul 2026 12:21:52 +0300 Subject: [PATCH 12/41] fix: update @falkordb/canvas dependency to version 0.2.5 --- app/package-lock.json | 194 ++++++++++++++++++++++++++++++++++++------ app/package.json | 2 +- 2 files changed, 170 insertions(+), 26 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 7f8a9b78..29ebc16e 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -8,7 +8,7 @@ "name": "code-graph", "version": "0.3.0", "dependencies": { - "@falkordb/canvas": "file:../../falkordb-canvas", + "@falkordb/canvas": "^0.2.5", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -52,28 +52,6 @@ "vite": "^6.3.5" } }, - "../../falkordb-canvas": { - "name": "@falkordb/canvas", - "version": "0.0.0-dev", - "license": "MIT", - "dependencies": { - "d3": "^7.9.0", - "force-graph": "^1.44.4", - "react": "^19.2.3" - }, - "devDependencies": { - "@types/d3": "^7.4.3", - "@types/react": "^19.2.7", - "@typescript-eslint/eslint-plugin": "^8.18.2", - "@typescript-eslint/parser": "^8.18.2", - "eslint": "^10.0.2", - "jsdom": "^29.0.2", - "promise-retry": "^2.0.1", - "tsup": "^8.5.1", - "typescript": "^6.0.3", - "vitest": "^4.1.4" - } - }, "../falkordb-canvas": { "extraneous": true }, @@ -541,8 +519,24 @@ } }, "node_modules/@falkordb/canvas": { - "resolved": "../../falkordb-canvas", - "link": true + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.2.5.tgz", + "integrity": "sha512-BSVDHYC9WeuGF9iPUkGmh11KgyV3whVAjmLWDaai29GKXyJrG49LPiwxRAaKY2bA9ha7bU8ZWMJ77EqfUPXhmA==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "force-graph": "^1.44.4", + "react": "^19.2.3" + } + }, + "node_modules/@falkordb/canvas/node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, "node_modules/@floating-ui/core": { "version": "1.7.5", @@ -2311,6 +2305,12 @@ "@swc/counter": "^0.1.3" } }, + "node_modules/@tweenjs/tween.js": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", + "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2406,6 +2406,15 @@ "vite": "^4 || ^5 || ^6 || ^7" } }, + "node_modules/accessor-fn": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.3.tgz", + "integrity": "sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -2502,6 +2511,16 @@ "node": ">=6.0.0" } }, + "node_modules/bezier-js": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-6.1.4.tgz", + "integrity": "sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==", + "license": "MIT", + "funding": { + "type": "individual", + "url": "https://github.com/Pomax/bezierjs/blob/master/FUNDING.md" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2590,6 +2609,18 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas-color-tracker": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.3.2.tgz", + "integrity": "sha512-ryQkDX26yJ3CXzb3hxUVNlg1NKE4REc5crLBq661Nxzr8TNd236SaEf2ffYLXyI5tSABSeguHLqcVq4vf9L3Zg==", + "license": "MIT", + "dependencies": { + "tinycolor2": "^1.6.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/canvas2svg": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/canvas2svg/-/canvas2svg-1.0.16.tgz", @@ -2832,6 +2863,12 @@ "node": ">=12" } }, + "node_modules/d3-binarytree": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", + "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", + "license": "MIT" + }, "node_modules/d3-brush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", @@ -2975,6 +3012,22 @@ "node": ">=12" } }, + "node_modules/d3-force-3d": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", + "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", + "license": "MIT", + "dependencies": { + "d3-binarytree": "1", + "d3-dispatch": "1 - 3", + "d3-octree": "1", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-format": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", @@ -3017,6 +3070,12 @@ "node": ">=12" } }, + "node_modules/d3-octree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", + "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", + "license": "MIT" + }, "node_modules/d3-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", @@ -3347,6 +3406,46 @@ "node": ">=8" } }, + "node_modules/float-tooltip": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/float-tooltip/-/float-tooltip-1.7.5.tgz", + "integrity": "sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==", + "license": "MIT", + "dependencies": { + "d3-selection": "2 - 3", + "kapsule": "^1.16", + "preact": "10" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/force-graph": { + "version": "1.51.4", + "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.51.4.tgz", + "integrity": "sha512-TdJ2KbkoiDQ7NIRx8IPGD0mAXXpLhamS7c+b7W98b0MHG7lphnda1VOQX/98UDTsttIAdH4TcP0l0MauSnLK8w==", + "license": "MIT", + "dependencies": { + "@tweenjs/tween.js": "18 - 25", + "accessor-fn": "1", + "bezier-js": "3 - 6", + "canvas-color-tracker": "^1.3", + "d3-array": "1 - 3", + "d3-drag": "2 - 3", + "d3-force-3d": "2 - 3", + "d3-scale": "1 - 4", + "d3-scale-chromatic": "1 - 3", + "d3-selection": "2 - 3", + "d3-zoom": "2 - 3", + "float-tooltip": "^1.7", + "index-array-by": "1", + "kapsule": "^1.16", + "lodash-es": "4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -3492,6 +3591,15 @@ "node": ">=0.10.0" } }, + "node_modules/index-array-by": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/index-array-by/-/index-array-by-1.4.2.tgz", + "integrity": "sha512-SP23P27OUKzXWEC/TOyWlwLviofQkCSCKONnc62eItjp69yCZZPqDQtr3Pw5gJDnPeUMqExmKydNZaJO0FU9pw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -3623,6 +3731,18 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/kapsule": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.16.3.tgz", + "integrity": "sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==", + "license": "MIT", + "dependencies": { + "lodash-es": "4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -3983,6 +4103,24 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/preact": { + "version": "10.29.7", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.7.tgz", + "integrity": "sha512-DCHYrK/B10yUD3ZjLfhZ3WIE/9Vf9VFUODcRE2dRomTYDpJk6z6L9wecSfhfE6M9ZTHUdyQkoC46arIDhEV84Q==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + }, + "peerDependencies": { + "preact-render-to-string": ">=5" + }, + "peerDependenciesMeta": { + "preact-render-to-string": { + "optional": true + } + } + }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -4526,6 +4664,12 @@ "node": ">=0.8" } }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", diff --git a/app/package.json b/app/package.json index 0d7c2e83..8a6fd379 100644 --- a/app/package.json +++ b/app/package.json @@ -11,7 +11,7 @@ "preview": "vite preview" }, "dependencies": { - "@falkordb/canvas": "file:../../falkordb-canvas", + "@falkordb/canvas": "^0.2.5", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", From 4807a2963c000b9f3da38d3110de4a398c1880f1 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 9 Jul 2026 13:14:22 +0300 Subject: [PATCH 13/41] fix: improve element menu removal test for deterministic assertions --- e2e/tests/elementMenu.spec.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/e2e/tests/elementMenu.spec.ts b/e2e/tests/elementMenu.spec.ts index 0a8f67e8..37452e53 100644 --- a/e2e/tests/elementMenu.spec.ts +++ b/e2e/tests/elementMenu.spec.ts @@ -78,19 +78,21 @@ test.describe("Element Menu and Right-click Menu tests", () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await browser.setPageToFullScreen(); await codeGraph.selectGraph(GRAPHRAG_SDK); - - // Get initial node count + + // Remove a specific node to keep the assertion deterministic. const initialGraphData = await codeGraph.getGraphNodes(); - const initialNodeCount = initialGraphData.length; - - // Right-click at canvas center and remove - await codeGraph.rightClickAtCanvasCenter(); + const targetNode = findNodeByName(initialGraphData, nodes[0].nodeName); + expect(targetNode).toBeDefined(); + + await codeGraph.rightClickAtNode(targetNode.screenX, targetNode.screenY); await codeGraph.clickOnRemoveNodeViaElementMenu(); - - // Wait for animation and verify node count decreased + + // Hidden nodes remain in graph data; verify visibility rather than length. await codeGraph.waitForCanvasAnimationToEnd(); const updatedGraphData = await codeGraph.getGraphNodes(); - expect(updatedGraphData.length).toBeLessThan(initialNodeCount); + const updatedTargetNode = findNodeByName(updatedGraphData, nodes[0].nodeName); + expect(updatedTargetNode).toBeDefined(); + expect(updatedTargetNode.visible).toBe(false); }); test("Verify element menu positioning respects canvas boundaries", async () => { From 0bb7392fe3738640f179f872d0168804b4eff893 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 9 Jul 2026 13:56:07 +0300 Subject: [PATCH 14/41] fix: update API proxy target to local development server --- app/vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/vite.config.ts b/app/vite.config.ts index 7ed134cd..791b8dd7 100644 --- a/app/vite.config.ts +++ b/app/vite.config.ts @@ -8,7 +8,7 @@ export default defineConfig({ port: 3000, proxy: { '/api': { - target: 'https://code-graph.falkordb.com', + target: 'http://127.0.0.1:5000', changeOrigin: true, secure: false, }, From d063625f686b018b539207edd53c61c5df715d8e Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 9 Jul 2026 17:44:07 +0300 Subject: [PATCH 15/41] fix: set viewport in browser context to ensure desktop layout on page load The issue was that the page was loading with a default small viewport before setPageToFullScreen() was called. This caused the CSS media query for md:flex to not match, so the #desktop container was hidden or not rendered properly. By setting the viewport in the BrowserContext options (1920x1080), every page created will have the correct viewport from the start, before any page load happens. This ensures the CSS media queries match correctly and the #desktop container renders with the ComboBox button visible. Fixes: ComboBox button is not visible error in all Playwright E2E tests --- e2e/infra/ui/browserWrapper.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/infra/ui/browserWrapper.ts b/e2e/infra/ui/browserWrapper.ts index 77fa5d07..0108092d 100644 --- a/e2e/infra/ui/browserWrapper.ts +++ b/e2e/infra/ui/browserWrapper.ts @@ -15,6 +15,7 @@ export default class BrowserWrapper { } this.context = await this.browser.newContext({ permissions: ['clipboard-read', 'clipboard-write'], + viewport: { width: 1920, height: 1080 }, }); if (!this.page) { this.page = await this.context.newPage(); From e39f528fe827ad4c43f348c2c5d692e8cb70dd37 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Sun, 12 Jul 2026 16:42:35 +0300 Subject: [PATCH 16/41] fix: resolve combobox crash from repo objects and legacy graph name lookup - api/list_repos returns {project, branch, graph} dicts, but combobox.tsx cast the payload to string[] and rendered the object directly, crashing React (minified error #31) on every page load. This blocked virtually all e2e tests (111/112 failing on both CI shards). - Add a RepoOption type and repoLabel() helper; use option.graph as the select value and a friendly project/branch label for display. - Fix Graph/AsyncGraphQuery construction to prefer an existing legacy (pre-T17) bare-named graph over always recomposing to code:{project}:_default, which silently queried a different (empty) graph and broke node/edge counts and path lookups for legacy repos. - Add missing setData() calls after handleExpand/handleRemove/Unhide Nodes so React state stays in sync with canvas mutations (CodeRabbit). - Remove redundant double right-click in elementMenu.spec.ts (CodeRabbit nit). --- api/graph.py | 34 ++++++++++++++++++++++++++++--- app/src/App.tsx | 6 +++--- app/src/components/code-graph.tsx | 10 ++++++--- app/src/components/combobox.tsx | 11 +++++----- app/src/lib/utils.ts | 14 +++++++++++++ e2e/tests/elementMenu.spec.ts | 1 - 6 files changed, 61 insertions(+), 15 deletions(-) diff --git a/api/graph.py b/api/graph.py index ffa208fb..2b182b21 100644 --- a/api/graph.py +++ b/api/graph.py @@ -131,9 +131,17 @@ def __init__(self, name: str, branch: Optional[str] = None) -> None: # Normalize empty / None to DEFAULT_BRANCH so the stored # branch matches the key actually used by compose_graph_name. self.branch = branch or DEFAULT_BRANCH - self.name = compose_graph_name(self.project, self.branch) + self.db = create_falkordb() + # Legacy (pre-T17) graphs are stored under the bare project + # name. Prefer that existing graph over composing a new, + # empty ``code:{project}:{branch}`` key out from under it. + if branch in (None, "", DEFAULT_BRANCH) and name in self.db.list_graphs(): + self.name = name + else: + self.name = compose_graph_name(self.project, self.branch) - self.db = create_falkordb() + if not hasattr(self, "db"): + self.db = create_falkordb() self.g = self.db.select_graph(self.name) # Initialize the backlog as disabled by default @@ -832,12 +840,32 @@ def __init__(self, name: str, branch: Optional[str] = None) -> None: else: self.project = name self.branch = branch or DEFAULT_BRANCH - self.name = compose_graph_name(self.project, self.branch) + # Composition is deferred until ``graph_exists``/first query so + # we can prefer an existing legacy (pre-T17) bare graph over + # composing a new, empty ``code:{project}:{branch}`` key; see + # ``_resolve_name``. + self.name = name + self._needs_resolution = branch in (None, "", DEFAULT_BRANCH) self.db = _async_db() self.g = self.db.select_graph(self.name) + async def _resolve_name(self) -> None: + """Resolve a bare project name to its actual FalkorDB graph key. + + Prefers an existing legacy graph (stored under the bare project + name) over composing a new ``code:{project}:{branch}`` key. + """ + if not getattr(self, "_needs_resolution", False): + return + self._needs_resolution = False + graphs = await self.db.list_graphs() + if self.name not in graphs: + self.name = compose_graph_name(self.project, self.branch) + self.g = self.db.select_graph(self.name) + async def graph_exists(self) -> bool: """Check if this graph exists, reusing the current connection.""" + await self._resolve_name() graphs = await self.db.list_graphs() return self.name in graphs diff --git a/app/src/App.tsx b/app/src/App.tsx index 4a2e731a..252e81cd 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -13,7 +13,7 @@ import { Drawer, DrawerContent, DrawerDescription, DrawerTitle, DrawerTrigger } import Input from './components/Input'; import { Labels } from './components/labels'; import { Toolbar, ZoomControls } from './components/toolbar'; -import { cn, GraphRef, Message, Path, PathData, PathNode } from '@/lib/utils'; +import { cn, DEFAULT_BRANCH, GraphRef, Message, Path, PathData, PathNode, RepoOption } from '@/lib/utils'; import type { GraphNode } from '@falkordb/canvas'; import { convertToCanvasData } from './components/ForceGraph'; import { Toaster } from '@/components/ui/toaster'; @@ -72,7 +72,7 @@ export default function App() { const [createURL, setCreateURL] = useState("") const [createOpen, setCreateOpen] = useState(false) const [tipOpen, setTipOpen] = useState(false) - const [options, setOptions] = useState([]); + const [options, setOptions] = useState([]); const [path, setPath] = useState(); const [isSubmit, setIsSubmit] = useState(false); const desktopChartRef = useRef(null) @@ -142,7 +142,7 @@ export default function App() { const graphName = createURL.split('/').pop()! - setOptions(prev => [...prev, graphName]) + setOptions(prev => [...prev, { project: graphName, branch: DEFAULT_BRANCH, graph: graphName }]) setSelectedValue(graphName) setCreateURL("") setCreateOpen(false) diff --git a/app/src/components/code-graph.tsx b/app/src/components/code-graph.tsx index 1666c3c2..7c8c1fe1 100644 --- a/app/src/components/code-graph.tsx +++ b/app/src/components/code-graph.tsx @@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button"; import ElementMenu from "./elementMenu"; import Combobox from "./combobox"; import { toast } from '@/components/ui/use-toast'; -import { Path } from "@/lib/utils"; +import { Path, RepoOption } from "@/lib/utils"; import Input from './Input'; // import CommitList from './commitList'; import { Checkbox } from '@/components/ui/checkbox'; @@ -27,8 +27,8 @@ interface Props { setData: Dispatch>, onFetchGraph: (graphName: string) => Promise, onFetchNode: (nodeIds: number[]) => Promise, - options: string[] - setOptions: Dispatch> + options: RepoOption[] + setOptions: Dispatch> isShowPath: boolean setPath: Dispatch> canvasRef: GraphRef @@ -243,6 +243,7 @@ export function CodeGraph({ // Update the model with new elements (graph.Elements is already updated by onFetchNode) // Convert the full model to canvas data and update canvasRef.current?.setGraphData(convertToCanvasData(graph.Elements)) + setData({ ...graph.Elements }) } else { const deleteNodes = nodes.filter(n => n.expand) if (deleteNodes.length > 0) { @@ -256,6 +257,7 @@ export function CodeGraph({ // Convert the updated model to canvas data canvasRef.current?.setGraphData(convertToCanvasData(graph.Elements)) + setData({ ...graph.Elements }) } setSelectedObjects([]) @@ -287,6 +289,7 @@ export function CodeGraph({ }) canvas.refresh() + setData({ ...graph.Elements }) setHasHiddenElements(true) setSelectedObjects([]) @@ -378,6 +381,7 @@ export function CodeGraph({ canvasData.nodes.forEach((n: { visible: boolean }) => { n.visible = true; }); canvasData.links.forEach((l: { visible: boolean }) => { l.visible = true; }); canvas.refresh(); + setData({ ...graph.Elements }); setHasHiddenElements(false); }} > diff --git a/app/src/components/combobox.tsx b/app/src/components/combobox.tsx index ec1b500a..d3d64362 100644 --- a/app/src/components/combobox.tsx +++ b/app/src/components/combobox.tsx @@ -1,14 +1,15 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { toast } from "@/components/ui/use-toast"; import { useEffect, useState } from "react"; +import { RepoOption, repoLabel } from "@/lib/utils"; const AUTH_HEADERS: HeadersInit = import.meta.env.VITE_SECRET_TOKEN ? { 'Authorization': `Bearer ${import.meta.env.VITE_SECRET_TOKEN}` } : {}; interface Props { - options: string[] - setOptions: (options: string[]) => void + options: RepoOption[] + setOptions: (options: RepoOption[]) => void selectedValue: string onSelectedValue: (value: string) => Promise @@ -37,7 +38,7 @@ export default function Combobox({ options, setOptions, selectedValue, onSelecte } const json = await result.json() - setOptions(json.repositories as string[]) + setOptions(json.repositories as RepoOption[]) } useEffect(() => { @@ -65,8 +66,8 @@ export default function Combobox({ options, setOptions, selectedValue, onSelecte { options.length !== 0 && options.map((option) => ( - - {option} + + {repoLabel(option)} )) } diff --git a/app/src/lib/utils.ts b/app/src/lib/utils.ts index 1a8d6af3..59c5434d 100644 --- a/app/src/lib/utils.ts +++ b/app/src/lib/utils.ts @@ -8,6 +8,20 @@ export type PathData = { links: any[] } +export const DEFAULT_BRANCH = "_default" + +// A single entry returned by /api/list_repos: `graph` is the underlying +// FalkorDB graph name to query, `project`/`branch` are used for display. +export type RepoOption = { + project: string + branch: string + graph: string +} + +export function repoLabel(repo: RepoOption): string { + return repo.branch === DEFAULT_BRANCH ? repo.project : `${repo.project} (${repo.branch})` +} + export type PathNode = { id?: number name?: string diff --git a/e2e/tests/elementMenu.spec.ts b/e2e/tests/elementMenu.spec.ts index 37452e53..c6a89f76 100644 --- a/e2e/tests/elementMenu.spec.ts +++ b/e2e/tests/elementMenu.spec.ts @@ -36,7 +36,6 @@ test.describe("Element Menu and Right-click Menu tests", () => { expect(targetNode).toBeDefined(); // Right-click on the node to trigger element menu - await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); await codeGraph.rightClickAtNode(targetNode.screenX, targetNode.screenY); // Verify menu is visible From a18f422843ba06a24ad509144fb76e5f15df296d Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 13 Jul 2026 12:13:28 +0300 Subject: [PATCH 17/41] fix: compose AsyncGraphQuery graph name immediately for explicit branches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit My previous commit deferred name resolution for bare project names to prefer an existing legacy graph, but set _needs_resolution based only on whether a default branch was requested — leaving self.name as the bare project name (never composed to code:{project}:{branch}) whenever a caller passed a bare name with an explicit non-default branch, e.g. the MCP test fixtures (Graph/AsyncGraphQuery(project, branch="test-")). Every query then hit a nonexistent/empty graph, failing 28 MCP tests with "symbol not found in graph" / empty results. Only defer resolution (to check for a legacy bare graph) when the branch is unset/default; otherwise compose immediately as before. Verified with 'uv run pytest tests/mcp/' (104 passed, was 28 failed/76 passed). --- api/graph.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/api/graph.py b/api/graph.py index 2b182b21..a57866ec 100644 --- a/api/graph.py +++ b/api/graph.py @@ -837,15 +837,22 @@ def __init__(self, name: str, branch: Optional[str] = None) -> None: if parsed is not None: self.project, self.branch = parsed self.name = name + self._needs_resolution = False else: self.project = name self.branch = branch or DEFAULT_BRANCH - # Composition is deferred until ``graph_exists``/first query so - # we can prefer an existing legacy (pre-T17) bare graph over - # composing a new, empty ``code:{project}:{branch}`` key; see - # ``_resolve_name``. - self.name = name - self._needs_resolution = branch in (None, "", DEFAULT_BRANCH) + if branch in (None, "", DEFAULT_BRANCH): + # Composition is deferred until ``graph_exists``/first query + # so we can prefer an existing legacy (pre-T17) bare graph + # over composing a new, empty ``code:{project}:_default`` + # key; see ``_resolve_name``. + self.name = name + self._needs_resolution = True + else: + # A specific branch was requested: compose immediately, + # same as before this bare-name fallback existed. + self.name = compose_graph_name(self.project, self.branch) + self._needs_resolution = False self.db = _async_db() self.g = self.db.select_graph(self.name) From 8492264c9b7a91724908e0c7592e157be02e92d2 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 13 Jul 2026 13:03:12 +0300 Subject: [PATCH 18/41] fix: normalize /api/list_repos payload for both string[] and object backends Some deployed backends (e.g. prod) still return repositories as plain strings instead of {project, branch, graph} objects. Combobox blindly cast the payload to RepoOption[], so every entry rendered as 'undefined (undefined)' when pointed at such a backend. Add toRepoOption() to normalize either shape into a RepoOption. --- app/src/components/combobox.tsx | 5 +++-- app/src/lib/utils.ts | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/src/components/combobox.tsx b/app/src/components/combobox.tsx index d3d64362..820acd64 100644 --- a/app/src/components/combobox.tsx +++ b/app/src/components/combobox.tsx @@ -1,7 +1,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { toast } from "@/components/ui/use-toast"; import { useEffect, useState } from "react"; -import { RepoOption, repoLabel } from "@/lib/utils"; +import { RepoOption, repoLabel, toRepoOption } from "@/lib/utils"; const AUTH_HEADERS: HeadersInit = import.meta.env.VITE_SECRET_TOKEN ? { 'Authorization': `Bearer ${import.meta.env.VITE_SECRET_TOKEN}` } @@ -38,7 +38,8 @@ export default function Combobox({ options, setOptions, selectedValue, onSelecte } const json = await result.json() - setOptions(json.repositories as RepoOption[]) + const repositories: unknown[] = Array.isArray(json.repositories) ? json.repositories : [] + setOptions(repositories.map(toRepoOption)) } useEffect(() => { diff --git a/app/src/lib/utils.ts b/app/src/lib/utils.ts index 59c5434d..27c33b1d 100644 --- a/app/src/lib/utils.ts +++ b/app/src/lib/utils.ts @@ -18,6 +18,23 @@ export type RepoOption = { graph: string } +// Normalizes a /api/list_repos entry into a RepoOption. Older/production +// backends may still return plain repo name strings instead of +// {project, branch, graph} objects — handle both so the UI never renders +// "undefined (undefined)" or crashes when pointed at a mismatched backend. +export function toRepoOption(entry: unknown): RepoOption { + if (typeof entry === "string") { + return { project: entry, branch: DEFAULT_BRANCH, graph: entry } + } + const repo = entry as Partial | null | undefined + const graph = repo?.graph ?? repo?.project ?? "" + return { + project: repo?.project ?? graph, + branch: repo?.branch ?? DEFAULT_BRANCH, + graph, + } +} + export function repoLabel(repo: RepoOption): string { return repo.branch === DEFAULT_BRANCH ? repo.project : `${repo.project} (${repo.branch})` } From eb13fa63ededefd165df44cb7ca23e5bde41142d Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 13 Jul 2026 14:26:56 +0300 Subject: [PATCH 19/41] fix(e2e): seed synthesized CALLS edges into the actual indexed graph ensure_calls_edges/ensure_search_term_variety wrote into a raw, bare-named 'GraphRAG-SDK' graph via a hardcoded string, but Project(name="GraphRAG-SDK", path=, ...) actually indexes into the composed 'code:GraphRAG-SDK:_default' graph (branch auto-detects to default since the temp copy isn't a git checkout). The synthesized merge_with->combine / import_data->add_node CALLS edges and test_* searchable functions landed in an empty, never-queried graph, so path-finding and autocomplete-dependent e2e tests failed with 'Path option N is not visible!' / no search results. Use the Project's actual composed graph name (sdk_project.graph.name) instead of the hardcoded bare string. Verified locally: canvas/chat/ searchBar suites went from ~19 failures to 2 (both pre-existing, unrelated: a metrics-panel read race and a force-layout-dependent element-menu-at-canvas-center test). --- e2e/seed_test_data.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/e2e/seed_test_data.py b/e2e/seed_test_data.py index a6fa19ac..bd209022 100644 --- a/e2e/seed_test_data.py +++ b/e2e/seed_test_data.py @@ -98,11 +98,12 @@ def main(): getattr(graphrag_sdk, "__version__", "?"), sdk_path, ) - Project( + sdk_project = Project( name="GraphRAG-SDK", path=sdk_path, url="https://github.com/FalkorDB/GraphRAG-SDK", - ).analyze_sources() + ) + sdk_project.analyze_sources() for url in REPOS: logger.info("Seeding %s ...", url) @@ -110,8 +111,12 @@ def main(): proj.analyze_sources() logger.info("Done seeding %s", url) - ensure_calls_edges("GraphRAG-SDK") - ensure_search_term_variety("GraphRAG-SDK") + # Use the actual composed graph name (e.g. "code:GraphRAG-SDK:_default") + # that analyze_sources() wrote into -- not the bare project name, which + # would silently create/write a separate, unused graph. + graph_name = sdk_project.graph.name + ensure_calls_edges(graph_name) + ensure_search_term_variety(graph_name) logger.info("All test data seeded successfully.") From 087579641021fd59f79c77ff5eeb151361041739 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 13 Jul 2026 16:40:39 +0300 Subject: [PATCH 20/41] fix(e2e): correct category checkbox order and metrics panel read race - testData.ts assumed categories are first-encountered in the order ['File', 'Class', 'Function'], but the analyzer actually emits ['File', 'Function', 'Class'] (verified against the real backend). canvas.spec.ts maps checkbox index -> categories[index], so checkbox 2 toggled 'Function' while the test asserted on 'Class' visibility (and vice versa for checkbox 3), always failing. - getMetricsPanelInfo() read the metrics panel immediately after selectGraph() resolved, but the panel is populated by a separate async /api/repo_info fetch that can still be in flight (briefly showing '0'), causing a race in 'Validate node and edge counts in canvas match API data'. Poll for a non-zero reading before returning. --- e2e/config/testData.ts | 2 +- e2e/logic/POM/codeGraph.ts | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/e2e/config/testData.ts b/e2e/config/testData.ts index 3c4c3785..e8bf166e 100644 --- a/e2e/config/testData.ts +++ b/e2e/config/testData.ts @@ -26,7 +26,7 @@ export const nodes: { nodeName: string; }[] = [ { nodeName: "ask"} ]; -export const categories: string[] = ['File', 'Class', 'Function']; +export const categories: string[] = ['File', 'Function', 'Class']; export const graphs: { graphName: string; }[] = [ { graphName: "GraphRAG-SDK" }, diff --git a/e2e/logic/POM/codeGraph.ts b/e2e/logic/POM/codeGraph.ts index 4c6ef3fd..148dfed8 100644 --- a/e2e/logic/POM/codeGraph.ts +++ b/e2e/logic/POM/codeGraph.ts @@ -613,8 +613,18 @@ export default class CodeGraph extends BasePage { } async getMetricsPanelInfo(): Promise<{ nodes: string, edges: string }> { - const nodes = await this.canvasMetricsPanel("1").innerHTML(); - const edges = await this.canvasMetricsPanel("3").innerHTML(); + // The metrics panel is populated by an async /api/repo_info fetch + // that fires after graph selection resolves, so it can briefly read + // "0" right after selectGraph(). Poll until it settles on a + // non-zero value (or give up and return whatever is last read). + let nodes = "0"; + let edges = "0"; + for (let attempt = 0; attempt < 10; attempt++) { + nodes = await this.canvasMetricsPanel("1").innerHTML(); + edges = await this.canvasMetricsPanel("3").innerHTML(); + if (nodes !== "0" || edges !== "0") break; + await this.page.waitForTimeout(500); + } return { nodes, edges } } From 89166c6743ad71435ea8b94a5f09252a0cb36d99 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 20 Jul 2026 14:18:11 +0300 Subject: [PATCH 21/41] fix: update API proxy target to use the production URL --- app/vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/vite.config.ts b/app/vite.config.ts index 791b8dd7..7ed134cd 100644 --- a/app/vite.config.ts +++ b/app/vite.config.ts @@ -8,7 +8,7 @@ export default defineConfig({ port: 3000, proxy: { '/api': { - target: 'http://127.0.0.1:5000', + target: 'https://code-graph.falkordb.com', changeOrigin: true, secure: false, }, From 7463dff1451a25eb01998ba856a83bd9d2c69028 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 20 Jul 2026 14:19:03 +0300 Subject: [PATCH 22/41] Revert "fix: update API proxy target to use the production URL" This reverts commit 89166c6743ad71435ea8b94a5f09252a0cb36d99. --- app/vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/vite.config.ts b/app/vite.config.ts index 7ed134cd..791b8dd7 100644 --- a/app/vite.config.ts +++ b/app/vite.config.ts @@ -8,7 +8,7 @@ export default defineConfig({ port: 3000, proxy: { '/api': { - target: 'https://code-graph.falkordb.com', + target: 'http://127.0.0.1:5000', changeOrigin: true, secure: false, }, From 7b5c15673f8a15fd7d64e79c7add6a05368bb551 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Thu, 23 Jul 2026 11:48:26 +0300 Subject: [PATCH 23/41] test(e2e): wait for element menu to appear after right-click The element menu is shown ~400ms after the focus-mode pan animation, but the POM checked isVisible() immediately, which never waits and always missed the delayed menu. Use waitFor({ state: 'visible' }) in rightClickAtNode and rightClickAtCanvasCenter instead. --- e2e/logic/POM/codeGraph.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/e2e/logic/POM/codeGraph.ts b/e2e/logic/POM/codeGraph.ts index 148dfed8..20027fd7 100644 --- a/e2e/logic/POM/codeGraph.ts +++ b/e2e/logic/POM/codeGraph.ts @@ -750,7 +750,13 @@ export default class CodeGraph extends BasePage { await this.page.mouse.move(centerX, centerY); await this.page.waitForTimeout(500); await this.page.mouse.click(centerX, centerY, { button: 'right' }); - if (await this.elementMenu.isVisible()) { + // In focus mode the app centers the clicked element first and only + // shows the menu ~400ms after the pan animation, so wait for it. + const menuAppeared = await this.elementMenu + .waitFor({ state: 'visible', timeout: 3000 }) + .then(() => true) + .catch(() => false); + if (menuAppeared) { return; } await this.page.waitForTimeout(1000); @@ -894,8 +900,13 @@ export default class CodeGraph extends BasePage { await this.page.waitForTimeout(300); await this.page.mouse.click(x, y, { button: 'right' }); - // Wait for element menu to appear - const isMenuVisible = await this.elementMenu.isVisible({ timeout: 3000 }).catch(() => false); + // Wait for element menu to appear. In focus mode the app first centers + // the clicked node (300ms pan) and shows the menu ~400ms later, so we + // must actually wait for visibility rather than checking immediately. + const isMenuVisible = await this.elementMenu + .waitFor({ state: 'visible', timeout: 5000 }) + .then(() => true) + .catch(() => false); if (!isMenuVisible) { throw new Error(`Element menu not visible after right-clicking at (${x}, ${y})`); } From 4d4e72753596eec4f1329b817a5d7e59cae4fea7 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Thu, 23 Jul 2026 11:48:47 +0300 Subject: [PATCH 24/41] test(e2e): select category checkboxes by label instead of index The labels panel renders categories in API first-seen order (File, Class, Function), not the order assumed by the tests (File, Function, Class), so index-based checkbox clicks hid the wrong category. Click the checkbox next to the category label text so the tests are independent of panel ordering. --- e2e/logic/POM/codeGraph.ts | 8 ++++++++ e2e/tests/canvas.spec.ts | 11 +++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/e2e/logic/POM/codeGraph.ts b/e2e/logic/POM/codeGraph.ts index 20027fd7..3d507f8d 100644 --- a/e2e/logic/POM/codeGraph.ts +++ b/e2e/logic/POM/codeGraph.ts @@ -218,6 +218,10 @@ export default class CodeGraph extends BasePage { return (checkbox: string) => this.scopedLocator(`(//button[@role='checkbox'])[${checkbox}]`); } + private get categoryCheckbox(): (category: string) => Locator { + return (category: string) => this.scopedLocator(`//p[text()='${category}']/preceding-sibling::button[@role='checkbox']`); + } + private get clearGraphBtn(): Locator { return this.container.getByRole('button', { name: 'Reset Graph' }); } @@ -550,6 +554,10 @@ export default class CodeGraph extends BasePage { await interactWhenVisible(this.codeGraphCheckbox(checkbox), (el) => el.click(), `Checkbox ${checkbox}`); } + async selectCategoryCheckbox(category: string): Promise { + await interactWhenVisible(this.categoryCheckbox(category), (el) => el.click(), `Category checkbox ${category}`); + } + async clickOnClearGraphBtn(): Promise { await this.page.mouse.click(10, 10); await interactWhenVisible(this.clearGraphBtn, (el) => el.click(), 'Clear Graph button'); diff --git a/e2e/tests/canvas.spec.ts b/e2e/tests/canvas.spec.ts index 946f77ac..1427a7f4 100644 --- a/e2e/tests/canvas.spec.ts +++ b/e2e/tests/canvas.spec.ts @@ -91,12 +91,11 @@ test.describe("Canvas tests", () => { expect(targetNodeForUpdateGraph.visible).toBe(true); }); - categories.forEach((category, index) => { - const checkboxIndex = index + 1; + categories.forEach((category) => { test(`Verify that unchecking the ${category} checkbox hides ${category} nodes on the canvas`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await codeGraph.selectGraph(GRAPHRAG_SDK); - await codeGraph.selectCodeGraphCheckbox(checkboxIndex.toString()); + await codeGraph.selectCategoryCheckbox(category); const result = await codeGraph.getGraphNodes(); const findItem = result.find((item: { category: string; }) => item.category === category); expect(findItem.visible).toBeFalsy(); @@ -321,11 +320,11 @@ test.describe("Canvas tests", () => { const sampleNode = categoryNodesBefore[0]; const { x: xBefore, y: yBefore } = sampleNode; - // Hide the category (checkbox 1 = categories[0]) - await codeGraph.selectCodeGraphCheckbox("1"); + // Hide the category + await codeGraph.selectCategoryCheckbox(categories[0]); // Show the category again — canvas must not re-simulate; positions are kept - await codeGraph.selectCodeGraphCheckbox("1"); + await codeGraph.selectCategoryCheckbox(categories[0]); const updatedGraph = await codeGraph.getGraphNodes(); const restoredNode = updatedGraph.find((n: any) => n.id === sampleNode.id); From 8cde46f19aeef687d91c2a1cf9861c01da454811 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Thu, 23 Jul 2026 11:48:57 +0300 Subject: [PATCH 25/41] test(e2e): fix metrics panel poll to parse ' Nodes' text The metrics panel markup changed to ' Nodes' / ' Edges', so the poll's nodes !== '0' check exited on the first read while the count was still loading. Parse the leading number instead so the poll actually waits for a non-zero count. --- e2e/logic/POM/codeGraph.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/e2e/logic/POM/codeGraph.ts b/e2e/logic/POM/codeGraph.ts index 3d507f8d..76c01108 100644 --- a/e2e/logic/POM/codeGraph.ts +++ b/e2e/logic/POM/codeGraph.ts @@ -630,7 +630,8 @@ export default class CodeGraph extends BasePage { for (let attempt = 0; attempt < 10; attempt++) { nodes = await this.canvasMetricsPanel("1").innerHTML(); edges = await this.canvasMetricsPanel("3").innerHTML(); - if (nodes !== "0" || edges !== "0") break; + // innerHTML is e.g. "495 Nodes" / "806 Edges" — parse the number + if (parseInt(nodes) > 0 || parseInt(edges) > 0) break; await this.page.waitForTimeout(500); } return { nodes, edges } From 9f5f56aa920c4cad20d960e6965c3f8cfb39a336 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Thu, 23 Jul 2026 12:52:31 +0300 Subject: [PATCH 26/41] fix(api): resolve repo_info metadata lookup for full graph keys The frontend now sends the composed graph key (code:{project}:{branch}) as the repo value, but repo_info passed it raw into async_get_repo_info, building a non-existent Redis info key and returning 400 for every graph selection (node/edge counts stuck at 0). Use the project/branch already parsed by AsyncGraphQuery so both plain repo names and full graph keys resolve. --- api/index.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/index.py b/api/index.py index e608456c..ff6dfeba 100644 --- a/api/index.py +++ b/api/index.py @@ -193,7 +193,9 @@ async def repo_info(data: RepoRequest, _=Depends(public_or_auth)): stats = await g.stats() finally: await g.close() - info = await async_get_repo_info(data.repo, data.branch) + # Use the parsed project/branch so full graph keys + # (``code:{project}:{branch}``) resolve the same as plain repo names. + info = await async_get_repo_info(g.project, g.branch) if info is None: return JSONResponse({"status": f'Missing repository "{data.repo}"'}, status_code=400) From 56aa648e34d6562e917545e8a5d855d28819fa75 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Thu, 23 Jul 2026 13:04:03 +0300 Subject: [PATCH 27/41] test(e2e): focus a node before right-clicking canvas center --- e2e/tests/elementMenu.spec.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/e2e/tests/elementMenu.spec.ts b/e2e/tests/elementMenu.spec.ts index c6a89f76..675fe46d 100644 --- a/e2e/tests/elementMenu.spec.ts +++ b/e2e/tests/elementMenu.spec.ts @@ -20,6 +20,11 @@ test.describe("Element Menu and Right-click Menu tests", () => { test("Verify element menu appears on right-click at canvas center", async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await codeGraph.selectGraph(GRAPHRAG_SDK); + // Focus a known node first so it is centered on the canvas — the layout + // is not deterministic, so a blind center click may hit empty background. + await codeGraph.fillSearchBar(nodes[0].nodeName); + await codeGraph.selectSearchBarOptionBtn("1"); + await codeGraph.waitForCanvasAnimationToEnd(); await codeGraph.rightClickAtCanvasCenter(); const isMenuVisible = await codeGraph.isElementMenuVisible(); expect(isMenuVisible).toBe(true); @@ -51,6 +56,10 @@ test.describe("Element Menu and Right-click Menu tests", () => { test("Verify element menu contains 'View Node' button", async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await codeGraph.selectGraph(GRAPHRAG_SDK); + // Focus a known node so the canvas-center right-click hits it reliably. + await codeGraph.fillSearchBar(nodes[0].nodeName); + await codeGraph.selectSearchBarOptionBtn("1"); + await codeGraph.waitForCanvasAnimationToEnd(); await codeGraph.rightClickAtCanvasCenter(); const hasViewNodeBtn = await codeGraph.hasElementMenuButton("View Node"); @@ -99,6 +108,10 @@ test.describe("Element Menu and Right-click Menu tests", () => { await browser.setPageToFullScreen(); await codeGraph.selectGraph(GRAPHRAG_SDK); + // Focus a known node so the canvas-center right-click hits it reliably. + await codeGraph.fillSearchBar(nodes[0].nodeName); + await codeGraph.selectSearchBarOptionBtn("1"); + await codeGraph.waitForCanvasAnimationToEnd(); // Right-click at canvas center await codeGraph.rightClickAtCanvasCenter(); From d87b7e9d9ac6c2427ca2680d3ea32cc636f66034 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jul 2026 13:32:41 +0300 Subject: [PATCH 28/41] fix: address unresolved PR review feedback --- app/src/components/code-graph.tsx | 8 +++++++- app/src/components/model.ts | 23 ++++++++--------------- app/src/components/toolbar.tsx | 30 ++++++++++++++---------------- app/src/lib/utils.ts | 3 ++- 4 files changed, 31 insertions(+), 33 deletions(-) diff --git a/app/src/components/code-graph.tsx b/app/src/components/code-graph.tsx index 7c8c1fe1..000e0c94 100644 --- a/app/src/components/code-graph.tsx +++ b/app/src/components/code-graph.tsx @@ -113,7 +113,13 @@ export function CodeGraph({ if (event.key === 'Delete') { if (selectedObjects.length === 0) return - handleRemove(selectedObjects.map(obj => obj.id), "nodes"); + const selectedNodeIds = selectedObjects + .filter((obj): obj is Node => 'category' in obj) + .map((obj) => obj.id) + + if (selectedNodeIds.length === 0) return + + handleRemove(selectedNodeIds, "nodes"); } }; diff --git a/app/src/components/model.ts b/app/src/components/model.ts index be91b880..64957c4d 100644 --- a/app/src/components/model.ts +++ b/app/src/components/model.ts @@ -263,23 +263,16 @@ export class Graph { } public removeLinks(ids: number[] = []) { - const connectedLinks = this.elements.links.filter(link => ids.includes(link.source) || ids.includes(link.target)) - this.elements.links = this.elements.links.filter(link => { - if ( - (ids.length !== 0 && !connectedLinks.includes(link)) || - (this.nodesMap.get(link.source) && this.nodesMap.get(link.target)) - ) { - if (link.collapsed && connectedLinks.includes(link)) { - // Remove collapsed links connected to the given ids even if both endpoints exist - this.linksMap.delete(link.id) - return false - } else { - return true - } + const isConnectedToIds = ids.length !== 0 && (ids.includes(link.source) || ids.includes(link.target)) + const hasEndpoints = this.nodesMap.get(link.source) && this.nodesMap.get(link.target) + const shouldKeep = Boolean(hasEndpoints) && !(link.collapsed && isConnectedToIds) + + if (!shouldKeep) { + this.linksMap.delete(link.id) } - this.linksMap.delete(link.id) - return false + + return shouldKeep }) } diff --git a/app/src/components/toolbar.tsx b/app/src/components/toolbar.tsx index d95d53f2..afb137bb 100644 --- a/app/src/components/toolbar.tsx +++ b/app/src/components/toolbar.tsx @@ -87,7 +87,8 @@ interface Props { export function Toolbar({ canvasRef, className, handleDownloadImage, animation, setAnimation, manualDimmed, setManualDimmed, selectedObjects, hideZoom: hideZoom }: Props) { const [layout, setLayout] = useState('force'); - const [direction, setDirection] = useState(''); + const [treeDirection, setTreeDirection] = useState('td'); + const [radialDirection, setRadialDirection] = useState('out'); const [pinned, setPinned] = useState(false); const handleZoomClick = (changefactor: number) => { @@ -153,15 +154,9 @@ export function Toolbar({ canvasRef, className, handleDownloadImage, animation, setLayout(mode); if (mode === 'tree') { - const dir = direction || 'td'; - setDirection(dir); - canvasRef.current?.setLayoutOptions({ tree: { direction: dir as HierarchyDirection } }); + canvasRef.current?.setLayoutOptions({ tree: { direction: treeDirection } }); } else if (mode === 'radial') { - const dir = direction || 'out'; - setDirection(dir); - canvasRef.current?.setLayoutOptions({ radial: { direction: dir as RadialDirection } }); - } else { - setDirection(''); + canvasRef.current?.setLayoutOptions({ radial: { direction: radialDirection } }); } canvasRef.current?.setLayout(mode); @@ -180,12 +175,15 @@ export function Toolbar({ canvasRef, className, handleDownloadImage, animation, const handleDirectionChange = (value: string, targetLayout?: string) => { const effectiveLayout = targetLayout || layout; - setDirection(value); if (effectiveLayout === 'tree') { - canvasRef.current?.setLayoutOptions({ tree: { direction: value as HierarchyDirection } }); + const direction = value as HierarchyDirection; + setTreeDirection(direction); + canvasRef.current?.setLayoutOptions({ tree: { direction } }); } else if (effectiveLayout === 'radial') { - canvasRef.current?.setLayoutOptions({ radial: { direction: value as RadialDirection } }); + const direction = value as RadialDirection; + setRadialDirection(direction); + canvasRef.current?.setLayoutOptions({ radial: { direction } }); } } @@ -244,13 +242,13 @@ export function Toolbar({ canvasRef, className, handleDownloadImage, animation, {HIERARCHY_DIRECTIONS.map(d => ( { if (layout !== 'tree') handleLayoutChange('tree'); handleDirectionChange(d.value, 'tree'); }} > - {layout === 'tree' && direction === d.value && ( + {layout === 'tree' && treeDirection === d.value && ( @@ -273,13 +271,13 @@ export function Toolbar({ canvasRef, className, handleDownloadImage, animation, {RADIAL_DIRECTIONS.map(d => ( { if (layout !== 'radial') handleLayoutChange('radial'); handleDirectionChange(d.value, 'radial'); }} > - {layout === 'radial' && direction === d.value && ( + {layout === 'radial' && radialDirection === d.value && ( diff --git a/app/src/lib/utils.ts b/app/src/lib/utils.ts index 27c33b1d..0a1f8e03 100644 --- a/app/src/lib/utils.ts +++ b/app/src/lib/utils.ts @@ -59,7 +59,8 @@ export enum MessageTypes { } export function createMessage(msg: Omit): Message { - return { ...msg, id: crypto.randomUUID() }; + const id = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(16).slice(2)}` + return { ...msg, id } } export interface Message { From ec3289ac86c21bc605cef2da3e892854823fb588 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jul 2026 13:59:43 +0300 Subject: [PATCH 29/41] fix: resolve new PR review comments --- api/graph.py | 1 + app/package-lock.json | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/api/graph.py b/api/graph.py index a57866ec..153262c4 100644 --- a/api/graph.py +++ b/api/graph.py @@ -877,6 +877,7 @@ async def graph_exists(self) -> bool: return self.name in graphs async def _query(self, q: str, params: Optional[dict] = None): + await self._resolve_name() return await self.g.query(q, params) async def get_sub_graph(self, limit: int) -> dict: diff --git a/app/package-lock.json b/app/package-lock.json index 90e66b1f..e30ab2fb 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -52,9 +52,6 @@ "vite": "^6.4.2" } }, - "../falkordb-canvas": { - "extraneous": true - }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", From 8b72a66dba87af1cadaeb8fb5562eecc4001b041 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jul 2026 14:13:22 +0300 Subject: [PATCH 30/41] fix: address latest PR review threads --- app/src/components/code-graph.tsx | 4 ++-- app/src/components/elementMenu.tsx | 15 ++++++++------- app/src/components/graphView.tsx | 8 ++++---- app/src/components/toolbar.tsx | 2 -- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/app/src/components/code-graph.tsx b/app/src/components/code-graph.tsx index 000e0c94..0cf108a5 100644 --- a/app/src/components/code-graph.tsx +++ b/app/src/components/code-graph.tsx @@ -1,5 +1,5 @@ import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react"; -import { Graph, GraphData, Node } from "./model"; +import { Graph, GraphData, Link, Node } from "./model"; import { Toolbar } from "./toolbar"; import { Labels } from "./labels"; import { Download, GitFork, Search, X } from "lucide-react"; @@ -85,7 +85,7 @@ export function CodeGraph({ }: Props) { const [url, setURL] = useState(""); - const [selectedObjects, setSelectedObjects] = useState([]); + const [selectedObjects, setSelectedObjects] = useState<(Node | Link)[]>([]); const [position, setPosition] = useState(); const [graphName, setGraphName] = useState(""); const [commits, setCommits] = useState([]); diff --git a/app/src/components/elementMenu.tsx b/app/src/components/elementMenu.tsx index fdc57c16..365c1930 100644 --- a/app/src/components/elementMenu.tsx +++ b/app/src/components/elementMenu.tsx @@ -9,7 +9,7 @@ import { GraphNode } from "@falkordb/canvas"; interface Props { obj: Node | Link | undefined; - objects: Node[]; + objects: (Node | Link)[]; setPath: Dispatch>; handleRemove: (ids: number[], type: "nodes" | "links") => void; position: Position | undefined; @@ -23,6 +23,7 @@ export default function ElementMenu({ obj, objects, setPath, handleRemove, posit const [currentObj, setCurrentObj] = useState(); const [containerWidth, setContainerWidth] = useState(0); const [containerHeight, setContainerHeight] = useState(0); + const nodeObjects = objects.filter((o): o is Node => "category" in o) useEffect(() => { setCurrentObj(undefined) @@ -71,14 +72,14 @@ export default function ElementMenu({ obj, objects, setPath, handleRemove, posit style={{ left, top }} > { - objects.some(o => o.id === obj.id) && objects.length > 1 ? + "category" in obj && nodeObjects.some(o => o.id === obj.id) && nodeObjects.length > 1 ? <> { - objects.length === 2 && + nodeObjects.length === 2 && @@ -86,19 +87,19 @@ export default function ElementMenu({ obj, objects, setPath, handleRemove, posit diff --git a/app/src/components/graphView.tsx b/app/src/components/graphView.tsx index 951b2f4c..5517cbae 100644 --- a/app/src/components/graphView.tsx +++ b/app/src/components/graphView.tsx @@ -20,8 +20,8 @@ interface Props { graph: Graph chartRef: GraphRef id: "desktop" | "mobile" - selectedObjects: Node[] - setSelectedObjects: Dispatch> + selectedObjects: (Node | Link)[] + setSelectedObjects: Dispatch> setPosition: Dispatch> handleExpand: (nodes: Node[], expand: boolean) => void isShowPath: boolean @@ -94,10 +94,10 @@ export default function GraphView({ setSelectedObjects(selectedObjects.filter(obj => obj.id !== element.id)) return } else { - setSelectedObjects([...selectedObjects, element as Node]) + setSelectedObjects([...selectedObjects, element]) } } else { - setSelectedObjects([element as Node]) + setSelectedObjects([element]) } // Center on node or link midpoint when focus mode is ON, then show menu diff --git a/app/src/components/toolbar.tsx b/app/src/components/toolbar.tsx index afb137bb..38eea7f7 100644 --- a/app/src/components/toolbar.tsx +++ b/app/src/components/toolbar.tsx @@ -147,8 +147,6 @@ export function Toolbar({ canvasRef, className, handleDownloadImage, animation, canvasRef.current?.setPinOnDragEnd(next); } - const isAnimationDisabled = layout !== 'force' || pinned - const handleLayoutChange = (value: string) => { const mode = value as LayoutMode; setLayout(mode); From ba2054de368be24cff69461d95ed017f390b27ad Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jul 2026 14:41:25 +0300 Subject: [PATCH 31/41] fix: address latest review comments --- app/src/components/elementMenu.tsx | 9 +++++++-- app/src/components/toolbar.tsx | 7 +++---- e2e/logic/POM/codeGraph.ts | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/src/components/elementMenu.tsx b/app/src/components/elementMenu.tsx index 365c1930..db8a56c9 100644 --- a/app/src/components/elementMenu.tsx +++ b/app/src/components/elementMenu.tsx @@ -64,8 +64,13 @@ export default function ElementMenu({ obj, objects, setPath, handleRemove, posit
{ if (!ref) return - setContainerWidth(ref.clientWidth) - setContainerHeight(ref.clientHeight) + const { clientWidth, clientHeight } = ref + if (clientWidth !== containerWidth) { + setContainerWidth(clientWidth) + } + if (clientHeight !== containerHeight) { + setContainerHeight(clientHeight) + } }} className="absolute z-[15] bg-popover text-popover-foreground rounded-lg shadow-lg flex divide-x divide-border" id="elementMenu" diff --git a/app/src/components/toolbar.tsx b/app/src/components/toolbar.tsx index 38eea7f7..fdc10c56 100644 --- a/app/src/components/toolbar.tsx +++ b/app/src/components/toolbar.tsx @@ -130,10 +130,9 @@ export function Toolbar({ canvasRef, className, handleDownloadImage, animation, } } - const handleAnimationToggle = () => { - const next = !animation; - setAnimation(next); - canvasRef.current?.setAnimation(next); + const handleAnimationToggle = (checked: boolean) => { + setAnimation(checked); + canvasRef.current?.setAnimation(checked); } const handleDimToggle = (checked: boolean) => { diff --git a/e2e/logic/POM/codeGraph.ts b/e2e/logic/POM/codeGraph.ts index 76c01108..24a249ac 100644 --- a/e2e/logic/POM/codeGraph.ts +++ b/e2e/logic/POM/codeGraph.ts @@ -631,7 +631,7 @@ export default class CodeGraph extends BasePage { nodes = await this.canvasMetricsPanel("1").innerHTML(); edges = await this.canvasMetricsPanel("3").innerHTML(); // innerHTML is e.g. "495 Nodes" / "806 Edges" — parse the number - if (parseInt(nodes) > 0 || parseInt(edges) > 0) break; + if (parseInt(nodes, 10) > 0 || parseInt(edges, 10) > 0) break; await this.page.waitForTimeout(500); } return { nodes, edges } From dfe62ff4bae706db071581da86d60ea4be184a97 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jul 2026 15:08:27 +0300 Subject: [PATCH 32/41] fix: address newest PR review findings --- app/src/components/graphView.tsx | 111 +++++++++++++++++-------------- app/src/components/toolbar.tsx | 39 ----------- 2 files changed, 61 insertions(+), 89 deletions(-) diff --git a/app/src/components/graphView.tsx b/app/src/components/graphView.tsx index 5517cbae..0311f71b 100644 --- a/app/src/components/graphView.tsx +++ b/app/src/components/graphView.tsx @@ -1,6 +1,6 @@ import { Graph, GraphData, Link, Node } from './model'; -import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react'; +import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Path } from '@/lib/utils'; import { Fullscreen } from 'lucide-react'; import { GraphRef } from '@/lib/utils'; @@ -217,6 +217,51 @@ export default function GraphView({ } }, [zoomedNodes, canvasRef]) + const activeElements = useMemo(() => { + const elements: (Node | Link)[] = [...selectedObjects] + if (hoverElement && !elements.includes(hoverElement)) { + elements.push(hoverElement) + } + return elements + }, [selectedObjects, hoverElement]) + + const selectedNodeIds = useMemo(() => { + const ids = new Set() + for (const el of activeElements) { + if (!('source' in el)) { + ids.add(el.id) + } + } + return ids + }, [activeElements]) + + const activeEndpointIds = useMemo(() => { + const ids = new Set() + for (const el of activeElements) { + if ('source' in el) { + ids.add(el.source as number) + ids.add(el.target as number) + } + } + return ids + }, [activeElements]) + + const adjacentToSelectedNodeIds = useMemo(() => { + const ids = new Set() + if (selectedNodeIds.size === 0) return ids + + for (const link of data.links) { + if (selectedNodeIds.has(link.source)) { + ids.add(link.target) + } + if (selectedNodeIds.has(link.target)) { + ids.add(link.source) + } + } + + return ids + }, [selectedNodeIds, data.links]) + const isNodeDimmed = useCallback((node: GraphNode) => { if (isPathResponse) { return !node.data.isPath && !node.data.isPathSelected @@ -227,41 +272,21 @@ export default function GraphView({ return false } - if (selectedObjects.length === 0 && !hoverElement) return false - - // Collect all active elements (selected + hovered) - const activeElements: (Node | Link)[] = [...selectedObjects] - if (hoverElement && !activeElements.includes(hoverElement)) { - activeElements.push(hoverElement) - } - - // Build selected node IDs and link endpoint IDs - const selectedNodeIds = new Set() - const linkEndpointIds = new Set() - - for (const el of activeElements) { - if (!('source' in el)) { - selectedNodeIds.add((el as any).id) - } else { - linkEndpointIds.add((el as any).source as number) - linkEndpointIds.add((el as any).target as number) - } - } - - const allActiveIds = new Set([...selectedNodeIds, ...linkEndpointIds]) + if (activeElements.length === 0) return false + + const allActiveIds = new Set([...selectedNodeIds, ...activeEndpointIds]) if (allActiveIds.size === 0) return false if (allActiveIds.has(node.id)) return false - - // Expand neighbourhood only for directly selected nodes - for (const link of data.links) { - if ((selectedNodeIds.has(link.source) && link.target === node.id) || - (selectedNodeIds.has(link.target) && link.source === node.id)) { - return false - } - } - - return true - }, [isPathResponse, manualDimmed, selectedObjects, hoverElement, data.links]) + + return !adjacentToSelectedNodeIds.has(node.id) + }, [ + isPathResponse, + manualDimmed, + activeElements, + selectedNodeIds, + activeEndpointIds, + adjacentToSelectedNodeIds, + ]) const isLinkDimmed = useCallback((link: GraphLink) => { if (isPathResponse) { @@ -273,26 +298,12 @@ export default function GraphView({ return false } - if (selectedObjects.length === 0 && !hoverElement) return false + if (activeElements.length === 0) return false // Don't dim the link itself if it's selected/hovered if (hoverElement && 'source' in hoverElement && hoverElement.id === link.id) return false if (selectedObjects.some(obj => 'source' in obj && obj.id === link.id)) return false - // Collect all active elements (selected + hovered) - const activeElements: (Node | Link)[] = [...selectedObjects] - if (hoverElement && !activeElements.includes(hoverElement)) { - activeElements.push(hoverElement) - } - - // Build selected node IDs only (not link endpoints) - const selectedNodeIds = new Set() - for (const el of activeElements) { - if (!('source' in el)) { - selectedNodeIds.add((el as any).id) - } - } - if (selectedNodeIds.size === 0) return true // Link is undimmed if one endpoint is a directly selected node @@ -302,7 +313,7 @@ export default function GraphView({ if (selectedNodeIds.has(srcId) || selectedNodeIds.has(tgtId)) return false return true - }, [isPathResponse, manualDimmed, selectedObjects, hoverElement, data.links]) + }, [isPathResponse, manualDimmed, selectedObjects, hoverElement, activeElements, selectedNodeIds]) const linkLineDash = useCallback((link: GraphLink) => { if (link.data.isPath && !link.data.isPathSelected) return [5, 5] diff --git a/app/src/components/toolbar.tsx b/app/src/components/toolbar.tsx index fdc10c56..23d11527 100644 --- a/app/src/components/toolbar.tsx +++ b/app/src/components/toolbar.tsx @@ -91,45 +91,6 @@ export function Toolbar({ canvasRef, className, handleDownloadImage, animation, const [radialDirection, setRadialDirection] = useState('out'); const [pinned, setPinned] = useState(false); - const handleZoomClick = (changefactor: number) => { - const canvas = canvasRef.current - - if (!canvas) return - - if (selectedObjects && selectedObjects.length > 0) { - const graphData = canvas.getGraphData() - const selectedNodeIds = new Set() - - for (const el of selectedObjects) { - if ('source' in el) { - // It's a link - center on both endpoints - selectedNodeIds.add(el.source as number) - selectedNodeIds.add(el.target as number) - } else { - // It's a node - selectedNodeIds.add(el.id) - } - } - - const focusedNodes = graphData?.nodes.filter(n => selectedNodeIds.has(n.id)) ?? [] - if (focusedNodes.length > 0) { - const cx = focusedNodes.reduce((s, n) => s + (n.x ?? 0), 0) / focusedNodes.length - const cy = focusedNodes.reduce((s, n) => s + (n.y ?? 0), 0) / focusedNodes.length - canvas.centerAt(cx, cy, 300) - } - } - - canvas.zoom(canvas.getZoom() * changefactor) - } - - const handleCenterClick = () => { - const canvas = canvasRef.current - - if (canvas) { - canvas.zoomToFit() - } - } - const handleAnimationToggle = (checked: boolean) => { setAnimation(checked); canvasRef.current?.setAnimation(checked); From d6b7c2aff78660fdaaef77e7e99afe2def1c23cd Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jul 2026 15:25:57 +0300 Subject: [PATCH 33/41] fix: align canvas with local React 19 setup --- app/package-lock.json | 1481 ++++++++++++++++++++++++++++------------- app/package.json | 10 +- 2 files changed, 1028 insertions(+), 463 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index e30ab2fb..51c45f33 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -8,7 +8,7 @@ "name": "code-graph", "version": "0.3.0", "dependencies": { - "@falkordb/canvas": "^0.2.5", + "@falkordb/canvas": "file:../falkordb-canvas", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -27,8 +27,8 @@ "embla-carousel-react": "^8.5.2", "html2canvas": "^1.4.1", "lucide-react": "^0.486.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.2.3", + "react-dom": "^19.2.3", "react-gtm-module": "^2.0.11", "react-json-tree": "^0.19.0", "react-resizable-panels": "^2.1.7", @@ -40,8 +40,8 @@ }, "devDependencies": { "@types/node": "^20.19.4", - "@types/react": "^18.3.18", - "@types/react-dom": "^18.3.5", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", "@types/react-gtm-module": "^2.0.4", "@types/react-syntax-highlighter": "^15.5.13", "@vitejs/plugin-react-swc": "^3.10.2", @@ -52,6 +52,7 @@ "vite": "^6.4.2" } }, + "../falkordb-canvas": {}, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -516,51 +517,35 @@ } }, "node_modules/@falkordb/canvas": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.2.5.tgz", - "integrity": "sha512-BSVDHYC9WeuGF9iPUkGmh11KgyV3whVAjmLWDaai29GKXyJrG49LPiwxRAaKY2bA9ha7bU8ZWMJ77EqfUPXhmA==", - "license": "MIT", - "dependencies": { - "d3": "^7.9.0", - "force-graph": "^1.44.4", - "react": "^19.2.3" - } - }, - "node_modules/@falkordb/canvas/node_modules/react": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", - "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "resolved": "../falkordb-canvas", + "link": true }, "node_modules/@floating-ui/core": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", - "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.11" + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", - "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.5", - "@floating-ui/utils": "^0.2.11" + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", - "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.6" + "@floating-ui/dom": "^1.8.0" }, "peerDependencies": { "react": ">=16.8.0", @@ -568,9 +553,9 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", - "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", "license": "MIT" }, "node_modules/@jridgewell/gen-mapping": { @@ -783,25 +768,26 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", - "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.21.tgz", + "integrity": "sha512-h+7qMDDmZJ8qTSPrwNyKb/PACY0ehtN8QOBlCz+C2C1jgehKekdhmHddG9YQk8BF/sHJqglPjte+jA1Jrp9HcA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.4", + "@radix-ui/react-context": "1.2.1", + "@radix-ui/react-dismissable-layer": "1.1.17", + "@radix-ui/react-focus-guards": "1.1.5", + "@radix-ui/react-focus-scope": "1.1.14", + "@radix-ui/react-id": "1.1.3", + "@radix-ui/react-portal": "1.1.15", + "@radix-ui/react-presence": "1.1.9", + "@radix-ui/react-primitive": "2.1.8", + "@radix-ui/react-slot": "1.3.1", + "@radix-ui/react-use-controllable-state": "1.2.5", + "@radix-ui/react-use-layout-effect": "1.1.3", "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" + "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", @@ -818,14 +804,32 @@ } } }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/primitive": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.4.tgz", + "integrity": "sha512-pWJo6lQAfR6uy1n7ii7PaCc9dLPwTXDYbQpORZU5B548Aqvl2pP1SM1vJGKyxIFqZMHRopRO4CQYX2iXAIB5jA==", "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.1.tgz", + "integrity": "sha512-EraVbFjiIjibpLr6EjvEDmSCYJU2SlKDMiO+qEK/D9GOWnQoAQlpQo2occGYC1UM9MBeEx5Bek3UtW/Qi57vAg==", + "license": "MIT", "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -836,10 +840,10 @@ } } }, - "node_modules/@radix-ui/react-direction": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", - "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.5.tgz", + "integrity": "sha512-UQvlB7L/BYh3P8MLvwZnQkH521EDos40Rwnbt5+Qpg4Vbk0z3xJjRUmR6+aka4aT1IQQXFdO5bNPoE7cvFl5xQ==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -851,17 +855,31 @@ } } }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", - "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-id": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.3.tgz", + "integrity": "sha512-f/Wxm0ctyMymUJK0fqTSQlm85rbzdAkoNbPXJQ5+6caowVO8Yx+NWGjGz/oGhs/D+WIbbQpOrU0hU2Li2/42xQ==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-escape-keydown": "1.1.1" + "@radix-ui/react-use-layout-effect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.9.tgz", + "integrity": "sha512-LTi1v05bprIb8/GSY/GWusI0jfsYjQ3CD3Nin8o7jVxnpHzVQfzjOQJoJTQkE9bdmOnsS7SFdhkXiBv8PrYnxw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.3" }, "peerDependencies": { "@types/react": "*", @@ -878,19 +896,13 @@ } } }, - "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", - "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.8.tgz", + "integrity": "sha512-DOlK1BdcIeYYUcFkSYFka4v1h95XTov93b0jCgW1EEiZuIhdwHY2NlE1teLIh+p0uBsuZI5A+voay+iVWpprfA==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-menu": "2.1.16", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" + "@radix-ui/react-slot": "1.3.1" }, "peerDependencies": { "@types/react": "*", @@ -907,11 +919,15 @@ } } }, - "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", - "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.1.tgz", + "integrity": "sha512-Bu/aAQHFFh6/QAvXAeUMurJ9fbW0JUIqlojU/yBXZ7cAVqy75Y7JYYyuCr9zLNF0p4WWoJYV54CTUIf4l7FzTw==", "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.4" + }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -922,70 +938,64 @@ } } }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", - "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.5.tgz", + "integrity": "sha512-UB1dXpxvHjR48poyKdKdTm7jT0kp3elkUKdKQiOkirlbYumqXinSJtrjDsr9maXNPvL12bKI4CDSmydms/9Aeg==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-use-effect-event": "0.0.4", + "@radix-ui/react-use-layout-effect": "1.1.3" }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { - "optional": true } } }, - "node_modules/@radix-ui/react-hover-card": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", - "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.4.tgz", + "integrity": "sha512-XYcfa6wlXDCwQtePuEiPmXLSAhGL4DWtedSyRgGbG3y10mw+OnrLp6SyeY1gJFMiYF0Dx0nMAX9InylKbLEFQQ==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" + "@radix-ui/react-use-layout-effect": "1.1.3" }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.3.tgz", + "integrity": "sha512-rDiah9wvtqihWtWz02XreeRKIxt2EJF8y5D9rtY9l5A2zxePAtcPiOMpDugNRw5bFHz+1/8viVoc7ZVKiJknCw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { "optional": true } } }, - "node_modules/@radix-ui/react-id": { + "node_modules/@radix-ui/react-direction": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", - "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -996,30 +1006,17 @@ } } }, - "node_modules/@radix-ui/react-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", - "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.17.tgz", + "integrity": "sha512-QAXwa38pG0xNAYh1pjdSaf86NrkqsMoDNmget/Y7X8O8E/C3Iqlj9GAPE4DfX9BPLXc7WH2TWSzMRnIoCdcjzQ==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.4", + "@radix-ui/react-primitive": "2.1.8", + "@radix-ui/react-use-callback-ref": "1.1.3", + "@radix-ui/react-use-effect-event": "0.0.4" }, "peerDependencies": { "@types/react": "*", @@ -1036,14 +1033,17 @@ } } }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/primitive": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.4.tgz", + "integrity": "sha512-pWJo6lQAfR6uy1n7ii7PaCc9dLPwTXDYbQpORZU5B548Aqvl2pP1SM1vJGKyxIFqZMHRopRO4CQYX2iXAIB5jA==", "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -1054,22 +1054,13 @@ } } }, - "node_modules/@radix-ui/react-popper": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", - "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-primitive": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.8.tgz", + "integrity": "sha512-DOlK1BdcIeYYUcFkSYFka4v1h95XTov93b0jCgW1EEiZuIhdwHY2NlE1teLIh+p0uBsuZI5A+voay+iVWpprfA==", "license": "MIT", "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-rect": "1.1.1", - "@radix-ui/react-use-size": "1.1.1", - "@radix-ui/rect": "1.1.1" + "@radix-ui/react-slot": "1.3.1" }, "peerDependencies": { "@types/react": "*", @@ -1086,85 +1077,63 @@ } } }, - "node_modules/@radix-ui/react-portal": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", - "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-slot": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.1.tgz", + "integrity": "sha512-Bu/aAQHFFh6/QAvXAeUMurJ9fbW0JUIqlojU/yBXZ7cAVqy75Y7JYYyuCr9zLNF0p4WWoJYV54CTUIf4l7FzTw==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.4" }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { - "optional": true } } }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", - "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.3.tgz", + "integrity": "sha512-AUS7HoBBAncIsGMLNG+CcpLuJ+JIBbZzmyM8Qdb1eIThX0AlhSSC6wn40xfBlPE+ypx/vSSiRWnklUAjy3U3UA==", "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { - "optional": true } } }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", - "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.4.tgz", + "integrity": "sha512-XYcfa6wlXDCwQtePuEiPmXLSAhGL4DWtedSyRgGbG3y10mw+OnrLp6SyeY1gJFMiYF0Dx0nMAX9InylKbLEFQQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.2.3" + "@radix-ui/react-use-layout-effect": "1.1.3" }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { - "optional": true } } }, - "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.3.tgz", + "integrity": "sha512-rDiah9wvtqihWtWz02XreeRKIxt2EJF8y5D9rtY9l5A2zxePAtcPiOMpDugNRw5bFHz+1/8viVoc7ZVKiJknCw==", "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -1175,13 +1144,593 @@ } } }, - "node_modules/@radix-ui/react-progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz", - "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==", + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", "license": "MIT", "dependencies": { - "@radix-ui/react-context": "1.1.3", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.14.tgz", + "integrity": "sha512-/x4htnJfmW53MplkrePaDpf1o/rN1C++g88WpVobULXbSyC19NtLkXmewuJ/HCaceSmfKDNL5gOXcBGnuAvnvQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.4", + "@radix-ui/react-primitive": "2.1.8", + "@radix-ui/react-use-callback-ref": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/primitive": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.4.tgz", + "integrity": "sha512-pWJo6lQAfR6uy1n7ii7PaCc9dLPwTXDYbQpORZU5B548Aqvl2pP1SM1vJGKyxIFqZMHRopRO4CQYX2iXAIB5jA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-primitive": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.8.tgz", + "integrity": "sha512-DOlK1BdcIeYYUcFkSYFka4v1h95XTov93b0jCgW1EEiZuIhdwHY2NlE1teLIh+p0uBsuZI5A+voay+iVWpprfA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-slot": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.1.tgz", + "integrity": "sha512-Bu/aAQHFFh6/QAvXAeUMurJ9fbW0JUIqlojU/yBXZ7cAVqy75Y7JYYyuCr9zLNF0p4WWoJYV54CTUIf4l7FzTw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.3.tgz", + "integrity": "sha512-AUS7HoBBAncIsGMLNG+CcpLuJ+JIBbZzmyM8Qdb1eIThX0AlhSSC6wn40xfBlPE+ypx/vSSiRWnklUAjy3U3UA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", + "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.15.tgz", + "integrity": "sha512-kAfBVJUKNNKZuyGQXXG6rKolAV2KAmxxVkPXJgoq9dEFTl39286RufHQFNTL8rzha4vP8159BJ6hMGpB+bqv7A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.8", + "@radix-ui/react-use-layout-effect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/primitive": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.4.tgz", + "integrity": "sha512-pWJo6lQAfR6uy1n7ii7PaCc9dLPwTXDYbQpORZU5B548Aqvl2pP1SM1vJGKyxIFqZMHRopRO4CQYX2iXAIB5jA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-primitive": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.8.tgz", + "integrity": "sha512-DOlK1BdcIeYYUcFkSYFka4v1h95XTov93b0jCgW1EEiZuIhdwHY2NlE1teLIh+p0uBsuZI5A+voay+iVWpprfA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-slot": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.1.tgz", + "integrity": "sha512-Bu/aAQHFFh6/QAvXAeUMurJ9fbW0JUIqlojU/yBXZ7cAVqy75Y7JYYyuCr9zLNF0p4WWoJYV54CTUIf4l7FzTw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.3.tgz", + "integrity": "sha512-rDiah9wvtqihWtWz02XreeRKIxt2EJF8y5D9rtY9l5A2zxePAtcPiOMpDugNRw5bFHz+1/8viVoc7ZVKiJknCw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz", + "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.3", "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { @@ -1199,28 +1748,129 @@ } } }, - "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", - "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", + "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.2.4" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -1237,21 +1887,15 @@ } } }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", - "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2" + "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -1268,33 +1912,14 @@ } } }, - "node_modules/@radix-ui/react-select": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", - "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", "license": "MIT", "dependencies": { - "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-visually-hidden": "1.2.3", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -1433,6 +2058,57 @@ } } }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-visually-hidden": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", @@ -1490,6 +2166,57 @@ } } }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", @@ -2302,12 +3029,6 @@ "@swc/counter": "^0.1.3" } }, - "node_modules/@tweenjs/tween.js": { - "version": "25.0.0", - "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", - "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", - "license": "MIT" - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2340,30 +3061,23 @@ "undici-types": "~6.21.0" } }, - "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==", - "license": "MIT" - }, "node_modules/@types/react": { - "version": "18.3.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", - "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "license": "MIT", "dependencies": { - "@types/prop-types": "*", "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", "peerDependencies": { - "@types/react": "^18.0.0" + "@types/react": "^19.2.0" } }, "node_modules/@types/react-gtm-module": { @@ -2403,15 +3117,6 @@ "vite": "^4 || ^5 || ^6 || ^7" } }, - "node_modules/accessor-fn": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.3.tgz", - "integrity": "sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -2508,16 +3213,6 @@ "node": ">=6.0.0" } }, - "node_modules/bezier-js": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-6.1.4.tgz", - "integrity": "sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==", - "license": "MIT", - "funding": { - "type": "individual", - "url": "https://github.com/Pomax/bezierjs/blob/master/FUNDING.md" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2606,18 +3301,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/canvas-color-tracker": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.3.2.tgz", - "integrity": "sha512-ryQkDX26yJ3CXzb3hxUVNlg1NKE4REc5crLBq661Nxzr8TNd236SaEf2ffYLXyI5tSABSeguHLqcVq4vf9L3Zg==", - "license": "MIT", - "dependencies": { - "tinycolor2": "^1.6.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/canvas2svg": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/canvas2svg/-/canvas2svg-1.0.16.tgz", @@ -2860,12 +3543,6 @@ "node": ">=12" } }, - "node_modules/d3-binarytree": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", - "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", - "license": "MIT" - }, "node_modules/d3-brush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", @@ -3009,22 +3686,6 @@ "node": ">=12" } }, - "node_modules/d3-force-3d": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", - "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", - "license": "MIT", - "dependencies": { - "d3-binarytree": "1", - "d3-dispatch": "1 - 3", - "d3-octree": "1", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-format": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", @@ -3067,12 +3728,6 @@ "node": ">=12" } }, - "node_modules/d3-octree": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", - "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", - "license": "MIT" - }, "node_modules/d3-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", @@ -3403,46 +4058,6 @@ "node": ">=8" } }, - "node_modules/float-tooltip": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/float-tooltip/-/float-tooltip-1.7.5.tgz", - "integrity": "sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==", - "license": "MIT", - "dependencies": { - "d3-selection": "2 - 3", - "kapsule": "^1.16", - "preact": "10" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/force-graph": { - "version": "1.51.4", - "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.51.4.tgz", - "integrity": "sha512-TdJ2KbkoiDQ7NIRx8IPGD0mAXXpLhamS7c+b7W98b0MHG7lphnda1VOQX/98UDTsttIAdH4TcP0l0MauSnLK8w==", - "license": "MIT", - "dependencies": { - "@tweenjs/tween.js": "18 - 25", - "accessor-fn": "1", - "bezier-js": "3 - 6", - "canvas-color-tracker": "^1.3", - "d3-array": "1 - 3", - "d3-drag": "2 - 3", - "d3-force-3d": "2 - 3", - "d3-scale": "1 - 4", - "d3-scale-chromatic": "1 - 3", - "d3-selection": "2 - 3", - "d3-zoom": "2 - 3", - "float-tooltip": "^1.7", - "index-array-by": "1", - "kapsule": "^1.16", - "lodash-es": "4" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -3588,15 +4203,6 @@ "node": ">=0.10.0" } }, - "node_modules/index-array-by": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/index-array-by/-/index-array-by-1.4.2.tgz", - "integrity": "sha512-SP23P27OUKzXWEC/TOyWlwLviofQkCSCKONnc62eItjp69yCZZPqDQtr3Pw5gJDnPeUMqExmKydNZaJO0FU9pw==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -3726,19 +4332,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/kapsule": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.16.3.tgz", - "integrity": "sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==", "license": "MIT", - "dependencies": { - "lodash-es": "4" - }, - "engines": { - "node": ">=12" - } + "peer": true }, "node_modules/lilconfig": { "version": "3.1.3", @@ -3769,6 +4364,7 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "license": "MIT", + "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -4100,24 +4696,6 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, - "node_modules/preact": { - "version": "10.29.7", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.7.tgz", - "integrity": "sha512-DCHYrK/B10yUD3ZjLfhZ3WIE/9Vf9VFUODcRE2dRomTYDpJk6z6L9wecSfhfE6M9ZTHUdyQkoC46arIDhEV84Q==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - }, - "peerDependencies": { - "preact-render-to-string": ">=5" - }, - "peerDependenciesMeta": { - "preact-render-to-string": { - "optional": true - } - } - }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -4173,13 +4751,10 @@ "license": "MIT" }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } @@ -4197,16 +4772,15 @@ } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.2.8" } }, "node_modules/react-gtm-module": { @@ -4496,13 +5070,10 @@ "license": "MIT" }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" }, "node_modules/simple-swizzle": { "version": "0.2.4", @@ -4661,12 +5232,6 @@ "node": ">=0.8" } }, - "node_modules/tinycolor2": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", - "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", diff --git a/app/package.json b/app/package.json index a3794b69..215bffec 100644 --- a/app/package.json +++ b/app/package.json @@ -11,7 +11,7 @@ "preview": "vite preview" }, "dependencies": { - "@falkordb/canvas": "^0.2.5", + "@falkordb/canvas": "file:../falkordb-canvas", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -30,8 +30,8 @@ "embla-carousel-react": "^8.5.2", "html2canvas": "^1.4.1", "lucide-react": "^0.486.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.2.3", + "react-dom": "^19.2.3", "react-gtm-module": "^2.0.11", "react-json-tree": "^0.19.0", "react-resizable-panels": "^2.1.7", @@ -47,8 +47,8 @@ }, "devDependencies": { "@types/node": "^20.19.4", - "@types/react": "^18.3.18", - "@types/react-dom": "^18.3.5", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", "@types/react-gtm-module": "^2.0.4", "@types/react-syntax-highlighter": "^15.5.13", "@vitejs/plugin-react-swc": "^3.10.2", From 56b496b21414ff84a7671471e5c0b2d9ec209094 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jul 2026 15:35:34 +0300 Subject: [PATCH 34/41] fix: update dependencies and improve component implementations - Updated @falkordb/canvas dependency from local file to version ^0.2.5 in package.json. - Refactored ForceGraph component to use createElement for rendering the falkordb-canvas element. - Changed containerRef type definition in Chat component for better type safety. - Modified parentRef type in elementMenu component to allow null values. --- app/package-lock.json | 275 ++++++++++++++++++++++++----- app/package.json | 2 +- app/src/components/ForceGraph.tsx | 6 +- app/src/components/chat.tsx | 2 +- app/src/components/elementMenu.tsx | 2 +- 5 files changed, 238 insertions(+), 49 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 51c45f33..5047a99b 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -8,7 +8,7 @@ "name": "code-graph", "version": "0.3.0", "dependencies": { - "@falkordb/canvas": "file:../falkordb-canvas", + "@falkordb/canvas": "^0.2.5", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -52,11 +52,11 @@ "vite": "^6.4.2" } }, - "../falkordb-canvas": {}, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -517,8 +517,15 @@ } }, "node_modules/@falkordb/canvas": { - "resolved": "../falkordb-canvas", - "link": true + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.2.5.tgz", + "integrity": "sha512-BSVDHYC9WeuGF9iPUkGmh11KgyV3whVAjmLWDaai29GKXyJrG49LPiwxRAaKY2bA9ha7bU8ZWMJ77EqfUPXhmA==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "force-graph": "^1.44.4", + "react": "^19.2.3" + } }, "node_modules/@floating-ui/core": { "version": "1.8.0", @@ -562,6 +569,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", @@ -572,6 +580,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" @@ -581,12 +590,14 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "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", @@ -597,6 +608,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -610,6 +622,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -619,6 +632,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -3029,6 +3043,12 @@ "@swc/counter": "^0.1.3" } }, + "node_modules/@tweenjs/tween.js": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", + "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -3065,6 +3085,7 @@ "version": "19.2.17", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -3074,7 +3095,7 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "devOptional": true, + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" @@ -3117,16 +3138,27 @@ "vite": "^4 || ^5 || ^6 || ^7" } }, + "node_modules/accessor-fn": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.3.tgz", + "integrity": "sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, "license": "MIT" }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -3140,6 +3172,7 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, "license": "MIT" }, "node_modules/aria-hidden": { @@ -3213,10 +3246,21 @@ "node": ">=6.0.0" } }, + "node_modules/bezier-js": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-6.1.4.tgz", + "integrity": "sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==", + "license": "MIT", + "funding": { + "type": "individual", + "url": "https://github.com/Pomax/bezierjs/blob/master/FUNDING.md" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3229,6 +3273,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -3275,6 +3320,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -3301,6 +3347,18 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas-color-tracker": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.3.2.tgz", + "integrity": "sha512-ryQkDX26yJ3CXzb3hxUVNlg1NKE4REc5crLBq661Nxzr8TNd236SaEf2ffYLXyI5tSABSeguHLqcVq4vf9L3Zg==", + "license": "MIT", + "dependencies": { + "tinycolor2": "^1.6.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/canvas2svg": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/canvas2svg/-/canvas2svg-1.0.16.tgz", @@ -3341,6 +3399,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -3365,6 +3424,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -3467,6 +3527,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -3543,6 +3604,12 @@ "node": ">=12" } }, + "node_modules/d3-binarytree": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", + "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", + "license": "MIT" + }, "node_modules/d3-brush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", @@ -3686,6 +3753,22 @@ "node": ">=12" } }, + "node_modules/d3-force-3d": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", + "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", + "license": "MIT", + "dependencies": { + "d3-binarytree": "1", + "d3-dispatch": "1 - 3", + "d3-octree": "1", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-format": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", @@ -3728,6 +3811,12 @@ "node": ">=12" } }, + "node_modules/d3-octree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", + "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", + "license": "MIT" + }, "node_modules/d3-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", @@ -3901,12 +3990,14 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, "license": "Apache-2.0" }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, "license": "MIT" }, "node_modules/electron-to-chromium": { @@ -4000,6 +4091,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -4016,6 +4108,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -4028,6 +4121,7 @@ "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -4050,6 +4144,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -4058,6 +4153,46 @@ "node": ">=8" } }, + "node_modules/float-tooltip": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/float-tooltip/-/float-tooltip-1.7.5.tgz", + "integrity": "sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==", + "license": "MIT", + "dependencies": { + "d3-selection": "2 - 3", + "kapsule": "^1.16", + "preact": "10" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/force-graph": { + "version": "1.51.4", + "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.51.4.tgz", + "integrity": "sha512-TdJ2KbkoiDQ7NIRx8IPGD0mAXXpLhamS7c+b7W98b0MHG7lphnda1VOQX/98UDTsttIAdH4TcP0l0MauSnLK8w==", + "license": "MIT", + "dependencies": { + "@tweenjs/tween.js": "18 - 25", + "accessor-fn": "1", + "bezier-js": "3 - 6", + "canvas-color-tracker": "^1.3", + "d3-array": "1 - 3", + "d3-drag": "2 - 3", + "d3-force-3d": "2 - 3", + "d3-scale": "1 - 4", + "d3-scale-chromatic": "1 - 3", + "d3-selection": "2 - 3", + "d3-zoom": "2 - 3", + "float-tooltip": "^1.7", + "index-array-by": "1", + "kapsule": "^1.16", + "lodash-es": "4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -4084,6 +4219,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -4098,6 +4234,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" @@ -4116,6 +4253,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -4128,6 +4266,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -4203,6 +4342,15 @@ "node": ">=0.10.0" } }, + "node_modules/index-array-by": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/index-array-by/-/index-array-by-1.4.2.tgz", + "integrity": "sha512-SP23P27OUKzXWEC/TOyWlwLviofQkCSCKONnc62eItjp69yCZZPqDQtr3Pw5gJDnPeUMqExmKydNZaJO0FU9pw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -4246,6 +4394,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -4258,6 +4407,7 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -4283,6 +4433,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4292,6 +4443,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -4314,6 +4466,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -4323,22 +4476,29 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "node_modules/kapsule": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.16.3.tgz", + "integrity": "sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==", "license": "MIT", - "peer": true + "dependencies": { + "lodash-es": "4" + }, + "engines": { + "node": ">=12" + } }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, "license": "MIT", "engines": { "node": ">=14" @@ -4351,6 +4511,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/lodash-es": { @@ -4359,19 +4520,6 @@ "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/lowlight": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", @@ -4399,6 +4547,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -4408,6 +4557,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -4421,6 +4571,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0", @@ -4432,6 +4583,7 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, "funding": [ { "type": "github", @@ -4457,6 +4609,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4466,6 +4619,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" @@ -4475,6 +4629,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -4502,18 +4657,21 @@ "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/picocolors": { "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": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -4526,6 +4684,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4535,6 +4694,7 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -4544,6 +4704,7 @@ "version": "8.5.8", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -4572,6 +4733,7 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.0.0", @@ -4589,6 +4751,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -4614,6 +4777,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, "funding": [ { "type": "opencollective", @@ -4656,6 +4820,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -4681,6 +4846,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -4694,8 +4860,27 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, "license": "MIT" }, + "node_modules/preact": { + "version": "10.29.7", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.7.tgz", + "integrity": "sha512-DCHYrK/B10yUD3ZjLfhZ3WIE/9Vf9VFUODcRE2dRomTYDpJk6z6L9wecSfhfE6M9ZTHUdyQkoC46arIDhEV84Q==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + }, + "peerDependencies": { + "preact-render-to-string": ">=5" + }, + "peerDependenciesMeta": { + "preact-render-to-string": { + "optional": true + } + } + }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -4705,18 +4890,6 @@ "node": ">=6" } }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "license": "MIT", - "peer": true, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/property-information": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", @@ -4734,6 +4907,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -4789,13 +4963,6 @@ "integrity": "sha512-8gyj4TTxeP7eEyc2QKawEuQoAZdjKvMY4pgWfycGmqGByhs17fR+zEBs0JUDq4US/l+vbTl+6zvUIx27iDo/Vw==", "license": "MIT" }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT", - "peer": true - }, "node_modules/react-json-tree": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.19.0.tgz", @@ -4921,6 +5088,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, "license": "MIT", "dependencies": { "pify": "^2.3.0" @@ -4930,6 +5098,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -4957,6 +5126,7 @@ "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", @@ -4977,6 +5147,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -5038,6 +5209,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { "type": "github", @@ -5088,6 +5260,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -5107,6 +5280,7 @@ "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -5129,6 +5303,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -5138,6 +5313,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" @@ -5160,6 +5336,7 @@ "version": "3.4.19", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -5215,6 +5392,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0" @@ -5224,6 +5402,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" @@ -5232,10 +5411,17 @@ "node": ">=0.8" } }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -5252,6 +5438,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -5269,6 +5456,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -5281,6 +5469,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -5293,6 +5482,7 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, "license": "Apache-2.0" }, "node_modules/tslib": { @@ -5400,6 +5590,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, "node_modules/utrie": { diff --git a/app/package.json b/app/package.json index 215bffec..63edb7d8 100644 --- a/app/package.json +++ b/app/package.json @@ -11,7 +11,7 @@ "preview": "vite preview" }, "dependencies": { - "@falkordb/canvas": "file:../falkordb-canvas", + "@falkordb/canvas": "^0.2.5", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", diff --git a/app/src/components/ForceGraph.tsx b/app/src/components/ForceGraph.tsx index 41549f86..93c09c10 100644 --- a/app/src/components/ForceGraph.tsx +++ b/app/src/components/ForceGraph.tsx @@ -1,5 +1,5 @@ -import { useCallback, useEffect, useRef, useState } from "react" +import { createElement, useCallback, useEffect, useRef, useState } from "react" import type { Data, GraphLink, GraphNode } from "@falkordb/canvas" import { GraphRef, PATH_COLOR } from "@/lib/utils" import { GraphData, Link, Node } from "./model" @@ -225,7 +225,5 @@ export default function ForceGraph({ } }, [canvasRef, graphId, data, canvasLoaded]) - return ( - - ) + return createElement("falkordb-canvas", { ref: canvasRef, "node-mode": "replace" }) } diff --git a/app/src/components/chat.tsx b/app/src/components/chat.tsx index e1409fbb..ab1cacb2 100644 --- a/app/src/components/chat.tsx +++ b/app/src/components/chat.tsx @@ -76,7 +76,7 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set const [sugOpen, setSugOpen] = useState(false); // A reference to the chat container to allow scrolling to the bottom - const containerRef: React.RefObject = useRef(null); + const containerRef = useRef(null); const isSendMessage = messages.some(m => m.type === MessageTypes.Pending) || (messages.some(m => m.text === "Please select a starting point and the end point. Select or press relevant item on the graph") && !messages.some(m => m.type === MessageTypes.Path)) diff --git a/app/src/components/elementMenu.tsx b/app/src/components/elementMenu.tsx index db8a56c9..4aa1727b 100644 --- a/app/src/components/elementMenu.tsx +++ b/app/src/components/elementMenu.tsx @@ -15,7 +15,7 @@ interface Props { position: Position | undefined; url: string; handleExpand: (nodes: Node[], expand: boolean) => void; - parentRef: RefObject; + parentRef: RefObject; } From cb2f7c9edc22a7f154b0363c7b5285167e355811 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jul 2026 15:40:19 +0300 Subject: [PATCH 35/41] chore: downgrade React and React DOM to version 18.3.1 and update corresponding type definitions --- app/package-lock.json | 182 +++++++++++++++++++----------------------- app/package.json | 8 +- 2 files changed, 88 insertions(+), 102 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 5047a99b..4a8e1a42 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -27,8 +27,8 @@ "embla-carousel-react": "^8.5.2", "html2canvas": "^1.4.1", "lucide-react": "^0.486.0", - "react": "^19.2.3", - "react-dom": "^19.2.3", + "react": "^18.3.1", + "react-dom": "^18.3.1", "react-gtm-module": "^2.0.11", "react-json-tree": "^0.19.0", "react-resizable-panels": "^2.1.7", @@ -40,8 +40,8 @@ }, "devDependencies": { "@types/node": "^20.19.4", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.2.3", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", "@types/react-gtm-module": "^2.0.4", "@types/react-syntax-highlighter": "^15.5.13", "@vitejs/plugin-react-swc": "^3.10.2", @@ -56,7 +56,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -527,6 +526,15 @@ "react": "^19.2.3" } }, + "node_modules/@falkordb/canvas/node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@floating-ui/core": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", @@ -569,7 +577,6 @@ "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", @@ -580,7 +587,6 @@ "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" @@ -590,14 +596,12 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "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", @@ -608,7 +612,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -622,7 +625,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -632,7 +634,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -3081,24 +3082,30 @@ "undici-types": "~6.21.0" } }, + "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==", + "license": "MIT" + }, "node_modules/@types/react": { - "version": "19.2.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", - "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", - "dev": true, + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", "license": "MIT", "dependencies": { + "@types/prop-types": "*", "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "devOptional": true, "license": "MIT", "peerDependencies": { - "@types/react": "^19.2.0" + "@types/react": "^18.0.0" } }, "node_modules/@types/react-gtm-module": { @@ -3151,14 +3158,12 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, "license": "MIT" }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -3172,7 +3177,6 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true, "license": "MIT" }, "node_modules/aria-hidden": { @@ -3260,7 +3264,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3273,7 +3276,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -3320,7 +3322,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -3399,7 +3400,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -3424,7 +3424,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -3527,7 +3526,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -3990,14 +3988,12 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true, "license": "Apache-2.0" }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true, "license": "MIT" }, "node_modules/electron-to-chromium": { @@ -4091,7 +4087,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -4108,7 +4103,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -4121,7 +4115,6 @@ "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -4144,7 +4137,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -4219,7 +4211,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -4234,7 +4225,6 @@ "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" @@ -4253,7 +4243,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -4266,7 +4255,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -4394,7 +4382,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -4407,7 +4394,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -4433,7 +4419,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4443,7 +4428,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -4466,7 +4450,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -4476,12 +4459,17 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, "node_modules/kapsule": { "version": "1.16.3", "resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.16.3.tgz", @@ -4498,7 +4486,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, "license": "MIT", "engines": { "node": ">=14" @@ -4511,7 +4498,6 @@ "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/lodash-es": { @@ -4520,6 +4506,18 @@ "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lowlight": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", @@ -4547,7 +4545,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -4557,7 +4554,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -4571,7 +4567,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0", @@ -4583,7 +4578,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -4609,7 +4603,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4619,7 +4612,6 @@ "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" @@ -4629,7 +4621,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -4657,21 +4648,18 @@ "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/picocolors": { "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": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -4684,7 +4672,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4694,7 +4681,6 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -4704,7 +4690,6 @@ "version": "8.5.8", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -4733,7 +4718,6 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.0.0", @@ -4751,7 +4735,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -4777,7 +4760,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", - "dev": true, "funding": [ { "type": "opencollective", @@ -4820,7 +4802,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -4846,7 +4827,6 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -4860,7 +4840,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, "license": "MIT" }, "node_modules/preact": { @@ -4890,6 +4869,18 @@ "node": ">=6" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/property-information": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", @@ -4907,7 +4898,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -4925,10 +4915,13 @@ "license": "MIT" }, "node_modules/react": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", - "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, "engines": { "node": ">=0.10.0" } @@ -4946,15 +4939,16 @@ } }, "node_modules/react-dom": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", - "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", "dependencies": { - "scheduler": "^0.27.0" + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^19.2.8" + "react": "^18.3.1" } }, "node_modules/react-gtm-module": { @@ -4963,6 +4957,13 @@ "integrity": "sha512-8gyj4TTxeP7eEyc2QKawEuQoAZdjKvMY4pgWfycGmqGByhs17fR+zEBs0JUDq4US/l+vbTl+6zvUIx27iDo/Vw==", "license": "MIT" }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT", + "peer": true + }, "node_modules/react-json-tree": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.19.0.tgz", @@ -5088,7 +5089,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, "license": "MIT", "dependencies": { "pify": "^2.3.0" @@ -5098,7 +5098,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -5126,7 +5125,6 @@ "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", @@ -5147,7 +5145,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -5209,7 +5206,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -5242,10 +5238,13 @@ "license": "MIT" }, "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } }, "node_modules/simple-swizzle": { "version": "0.2.4", @@ -5260,7 +5259,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -5280,7 +5278,6 @@ "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -5303,7 +5300,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -5313,7 +5309,6 @@ "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" @@ -5336,7 +5331,6 @@ "version": "3.4.19", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", - "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -5392,7 +5386,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0" @@ -5402,7 +5395,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" @@ -5421,7 +5413,6 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -5438,7 +5429,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -5456,7 +5446,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -5469,7 +5458,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -5482,7 +5470,6 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, "license": "Apache-2.0" }, "node_modules/tslib": { @@ -5590,7 +5577,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/utrie": { diff --git a/app/package.json b/app/package.json index 63edb7d8..a3794b69 100644 --- a/app/package.json +++ b/app/package.json @@ -30,8 +30,8 @@ "embla-carousel-react": "^8.5.2", "html2canvas": "^1.4.1", "lucide-react": "^0.486.0", - "react": "^19.2.3", - "react-dom": "^19.2.3", + "react": "^18.3.1", + "react-dom": "^18.3.1", "react-gtm-module": "^2.0.11", "react-json-tree": "^0.19.0", "react-resizable-panels": "^2.1.7", @@ -47,8 +47,8 @@ }, "devDependencies": { "@types/node": "^20.19.4", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.2.3", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", "@types/react-gtm-module": "^2.0.4", "@types/react-syntax-highlighter": "^15.5.13", "@vitejs/plugin-react-swc": "^3.10.2", From 3639af3581821cd9a6df79003221ac3f0dce6566 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jul 2026 15:46:45 +0300 Subject: [PATCH 36/41] fix: correct insertIndex calculation in RemoveLastPath function --- app/src/components/chat.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/components/chat.tsx b/app/src/components/chat.tsx index ab1cacb2..3a406236 100644 --- a/app/src/components/chat.tsx +++ b/app/src/components/chat.tsx @@ -68,7 +68,7 @@ const RemoveLastPath = (messages: Message[], includeQuery = false): RemoveLastPa const cleaned = [...messages.slice(0, groupStart), ...messages.slice(index + 1)] - return { messages: cleaned, insertIndex: cleaned.length } + return { messages: cleaned, insertIndex: groupStart } } export function Chat({ messages, setMessages, query, setQuery, selectedPath, setSelectedPath, setChatOpen, repo, path, setPath, graph, selectedPathId, isPathResponse, setIsPathResponse, canvasRef, paths, setPaths }: Props) { From 84fcd204660f361006a6a1240d3b034247bea0a2 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jul 2026 15:59:45 +0300 Subject: [PATCH 37/41] fix: address latest review comments --- app/src/App.tsx | 1 + app/src/components/chat.tsx | 8 ++++++-- app/src/components/toolbar.tsx | 18 ++++++++++-------- e2e/logic/POM/codeGraph.ts | 2 +- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/app/src/App.tsx b/app/src/App.tsx index 252e81cd..99ae5b70 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -693,6 +693,7 @@ export default function App() { hideZoom className='w-full justify-center' canvasRef={mobileChartRef} + handleDownloadImage={handleDownloadImage} animation={animation} setAnimation={setAnimation} manualDimmed={manualDimmed} diff --git a/app/src/components/chat.tsx b/app/src/components/chat.tsx index 3a406236..b1060dd1 100644 --- a/app/src/components/chat.tsx +++ b/app/src/components/chat.tsx @@ -143,10 +143,14 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set // Zoom to fit selected path nodes const selectedNodeIds = new Set(selectedPath.nodes.map((n: Node) => n.id)) - setTimeout(() => { + const timeout = setTimeout(() => { canvas.zoomToFit(1, (n: GraphNode) => selectedNodeIds.has(n.id)) }, 100) - }, [selectedPathId, isPathResponse, paths]) + + return () => { + clearTimeout(timeout) + } + }, [selectedPathId, isPathResponse, paths, graph, canvasRef]) const handleSetSelectedPath = (p: PathData) => { const canvas = canvasRef.current diff --git a/app/src/components/toolbar.tsx b/app/src/components/toolbar.tsx index 23d11527..a56c545a 100644 --- a/app/src/components/toolbar.tsx +++ b/app/src/components/toolbar.tsx @@ -80,7 +80,7 @@ interface Props { manualDimmed: boolean setManualDimmed: (dimmed: boolean) => void selectedObjects?: (Node | Link)[] - /** Hide the three zoom buttons and download, show everything else. */ + /** Hide the three zoom buttons, show everything else. */ hideZoom?: boolean } @@ -249,13 +249,15 @@ export function Toolbar({ canvasRef, className, handleDownloadImage, animation,
{!hideZoom && } - + {handleDownloadImage && ( + + )}
) } \ No newline at end of file diff --git a/e2e/logic/POM/codeGraph.ts b/e2e/logic/POM/codeGraph.ts index 24a249ac..3efab4e4 100644 --- a/e2e/logic/POM/codeGraph.ts +++ b/e2e/logic/POM/codeGraph.ts @@ -631,7 +631,7 @@ export default class CodeGraph extends BasePage { nodes = await this.canvasMetricsPanel("1").innerHTML(); edges = await this.canvasMetricsPanel("3").innerHTML(); // innerHTML is e.g. "495 Nodes" / "806 Edges" — parse the number - if (parseInt(nodes, 10) > 0 || parseInt(edges, 10) > 0) break; + if (parseInt(nodes, 10) > 0 && parseInt(edges, 10) > 0) break; await this.page.waitForTimeout(500); } return { nodes, edges } From 1b53c48b3c82c5c44681aee0a948ab09768dcf73 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jul 2026 16:15:28 +0300 Subject: [PATCH 38/41] fix: avoid duplicate graph existence lookup --- api/graph.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/graph.py b/api/graph.py index 153262c4..3c666b10 100644 --- a/api/graph.py +++ b/api/graph.py @@ -856,7 +856,7 @@ def __init__(self, name: str, branch: Optional[str] = None) -> None: self.db = _async_db() self.g = self.db.select_graph(self.name) - async def _resolve_name(self) -> None: + async def _resolve_name(self, graphs: Optional[list[str]] = None) -> None: """Resolve a bare project name to its actual FalkorDB graph key. Prefers an existing legacy graph (stored under the bare project @@ -865,15 +865,16 @@ async def _resolve_name(self) -> None: if not getattr(self, "_needs_resolution", False): return self._needs_resolution = False - graphs = await self.db.list_graphs() + if graphs is None: + graphs = await self.db.list_graphs() if self.name not in graphs: self.name = compose_graph_name(self.project, self.branch) self.g = self.db.select_graph(self.name) async def graph_exists(self) -> bool: """Check if this graph exists, reusing the current connection.""" - await self._resolve_name() graphs = await self.db.list_graphs() + await self._resolve_name(graphs) return self.name in graphs async def _query(self, q: str, params: Optional[dict] = None): From 2fa562f96e4ab3951d2cffaed720f96d1c9c8f4b Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jul 2026 16:19:13 +0300 Subject: [PATCH 39/41] chore: upgrade React and related dependencies; refactor DataPanel component for improved JSON rendering --- app/package-lock.json | 1628 ++++++++++++------------------ app/package.json | 13 +- app/src/components/dataPanel.tsx | 151 +-- 3 files changed, 734 insertions(+), 1058 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 4a8e1a42..b6c184cf 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -27,10 +27,9 @@ "embla-carousel-react": "^8.5.2", "html2canvas": "^1.4.1", "lucide-react": "^0.486.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.2.8", + "react-dom": "^19.2.8", "react-gtm-module": "^2.0.11", - "react-json-tree": "^0.19.0", "react-resizable-panels": "^2.1.7", "react-syntax-highlighter": "^15.6.1", "react-type-animation": "^3.2.0", @@ -40,16 +39,16 @@ }, "devDependencies": { "@types/node": "^20.19.4", - "@types/react": "^18.3.18", - "@types/react-dom": "^18.3.5", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", "@types/react-gtm-module": "^2.0.4", "@types/react-syntax-highlighter": "^15.5.13", - "@vitejs/plugin-react-swc": "^3.10.2", + "@vitejs/plugin-react-swc": "^4.3.2", "autoprefixer": "^10.4.21", "postcss": "^8.5.4", "tailwindcss": "^3.4.17", "typescript": "^5.7.3", - "vite": "^6.4.2" + "vite": "^8.1.5" } }, "node_modules/@alloc/quick-lru": { @@ -73,446 +72,38 @@ "node": ">=6.9.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@falkordb/canvas": { @@ -526,15 +117,6 @@ "react": "^19.2.3" } }, - "node_modules/@falkordb/canvas/node_modules/react": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", - "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@floating-ui/core": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", @@ -608,6 +190,25 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -643,6 +244,16 @@ "node": ">= 8" } }, + "node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@radix-ui/number": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", @@ -2461,31 +2072,10 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", "cpu": [ "arm64" ], @@ -2494,12 +2084,15 @@ "optional": true, "os": [ "android" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", "cpu": [ "arm64" ], @@ -2508,12 +2101,15 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", "cpu": [ "x64" ], @@ -2522,26 +2118,15 @@ "optional": true, "os": [ "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", "cpu": [ "x64" ], @@ -2550,26 +2135,15 @@ "optional": true, "os": [ "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", "cpu": [ "arm" ], @@ -2578,26 +2152,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", "cpu": [ "arm64" ], @@ -2606,54 +2169,32 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", "cpu": [ - "loong64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", "cpu": [ "ppc64" ], @@ -2662,40 +2203,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", "cpu": [ "s390x" ], @@ -2704,12 +2220,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", "cpu": [ "x64" ], @@ -2718,12 +2237,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", "cpu": [ "x64" ], @@ -2732,26 +2254,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", "cpu": [ "arm64" ], @@ -2760,40 +2271,51 @@ "optional": true, "os": [ "openharmony" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", "cpu": [ - "arm64" + "wasm32" ], "dev": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", "cpu": [ - "ia32" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", "cpu": [ "x64" ], @@ -2802,32 +2324,28 @@ "optional": true, "os": [ "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "license": "MIT" }, "node_modules/@swc/core": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.18.tgz", - "integrity": "sha512-z87aF9GphWp//fnkRsqvtY+inMVPgYW3zSlXH1kJFvRT5H/wiAn+G32qW5l3oEk63KSF1x3Ov0BfHCObAmT8RA==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.46.tgz", + "integrity": "sha512-Ri3em2mBpq3h2zSPliCYl63otDGqek8PPEfv2nWgRQEbZ/VBCNyypVTVQ6cEbTCXBhy+WE2T3fQb08moIyuYaw==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.25" + "@swc/types": "^0.1.27" }, "engines": { "node": ">=10" @@ -2837,16 +2355,18 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.18", - "@swc/core-darwin-x64": "1.15.18", - "@swc/core-linux-arm-gnueabihf": "1.15.18", - "@swc/core-linux-arm64-gnu": "1.15.18", - "@swc/core-linux-arm64-musl": "1.15.18", - "@swc/core-linux-x64-gnu": "1.15.18", - "@swc/core-linux-x64-musl": "1.15.18", - "@swc/core-win32-arm64-msvc": "1.15.18", - "@swc/core-win32-ia32-msvc": "1.15.18", - "@swc/core-win32-x64-msvc": "1.15.18" + "@swc/core-darwin-arm64": "1.15.46", + "@swc/core-darwin-x64": "1.15.46", + "@swc/core-linux-arm-gnueabihf": "1.15.46", + "@swc/core-linux-arm64-gnu": "1.15.46", + "@swc/core-linux-arm64-musl": "1.15.46", + "@swc/core-linux-ppc64-gnu": "1.15.46", + "@swc/core-linux-s390x-gnu": "1.15.46", + "@swc/core-linux-x64-gnu": "1.15.46", + "@swc/core-linux-x64-musl": "1.15.46", + "@swc/core-win32-arm64-msvc": "1.15.46", + "@swc/core-win32-ia32-msvc": "1.15.46", + "@swc/core-win32-x64-msvc": "1.15.46" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -2858,9 +2378,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.18.tgz", - "integrity": "sha512-+mIv7uBuSaywN3C9LNuWaX1jJJ3SKfiJuE6Lr3bd+/1Iv8oMU7oLBjYMluX1UrEPzwN2qCdY6Io0yVicABoCwQ==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.46.tgz", + "integrity": "sha512-IsISIT22EfktVJrlvIpnAxG2u/A9aob9l99HMlx80x72WlFmFPk1V3UhkEzx86eJP8hw049KTFv/RISho2cq2Q==", "cpu": [ "arm64" ], @@ -2875,9 +2395,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.18.tgz", - "integrity": "sha512-wZle0eaQhnzxWX5V/2kEOI6Z9vl/lTFEC6V4EWcn+5pDjhemCpQv9e/TDJ0GIoiClX8EDWRvuZwh+Z3dhL1NAg==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.46.tgz", + "integrity": "sha512-4Tj4ppVIPCmUMpmGFiGtyEriwLyJ+yi/US4WfBrP/ok8COGddDZXLEzQETnKyK46mjvr1v0jevrS23zjoff7vA==", "cpu": [ "x64" ], @@ -2892,9 +2412,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.18.tgz", - "integrity": "sha512-ao61HGXVqrJFHAcPtF4/DegmwEkVCo4HApnotLU8ognfmU8x589z7+tcf3hU+qBiU1WOXV5fQX6W9Nzs6hjxDw==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.46.tgz", + "integrity": "sha512-i8tUGnNjyOgMmfmgFSg4aeJLQoFyfpIHK5FjpQAwpRyQIqEUB2w1e8zIDQzY1WhOxx8NoS1S5iUL813Un4Sf5A==", "cpu": [ "arm" ], @@ -2909,9 +2429,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.18.tgz", - "integrity": "sha512-3xnctOBLIq3kj8PxOCgPrGjBLP/kNOddr6f5gukYt/1IZxsITQaU9TDyjeX6jG+FiCIHjCuWuffsyQDL5Ew1bg==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.46.tgz", + "integrity": "sha512-c0OnhqzdhfOvv6qhNCcByepB+sNYOGZyhtr2Qa6ZCHvAWTYhSRw4j/u92Stue9PbZ/6q74b9nHzi76+kVzqQHQ==", "cpu": [ "arm64" ], @@ -2926,9 +2446,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.18.tgz", - "integrity": "sha512-0a+Lix+FSSHBSBOA0XznCcHo5/1nA6oLLjcnocvzXeqtdjnPb+SvchItHI+lfeiuj1sClYPDvPMLSLyXFaiIKw==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.46.tgz", + "integrity": "sha512-imyRpNEcUzFQFV2LE4jL68ErvmKEuZCbvZru77iQREunJ+bR4i658cupTgtG1mLYM3F1Tzy3Sb9xYb02KghWTg==", "cpu": [ "arm64" ], @@ -2942,10 +2462,44 @@ "node": ">=10" } }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.46.tgz", + "integrity": "sha512-ctEfcl/HcUeomK33cbySiHZm98GEDIxTm1EkpBsYCiHxElYBzvTXVeuQT2YwbUXn9XCrjiw4ipyUNk33k26qRg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.46.tgz", + "integrity": "sha512-DxlMdnt84TtRVTv7WL/thWyz9+QU8QZNNoAP9rrk0P68LziuhfePp8MjQ44zIprpTHTsEwyziIuGUUN5iSC1bQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.18.tgz", - "integrity": "sha512-wG9J8vReUlpaHz4KOD/5UE1AUgirimU4UFT9oZmupUDEofxJKYb1mTA/DrMj0s78bkBiNI+7Fo2EgPuvOJfuAA==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.46.tgz", + "integrity": "sha512-SKxI7J6t90XPl8hRUqtJi9NfGdunN/E/vZMc7Bc0figeRdOPDBT+Tm8g7cx9xM0T0mewh2l+8dewa3Am27/P+A==", "cpu": [ "x64" ], @@ -2960,9 +2514,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.18.tgz", - "integrity": "sha512-4nwbVvCphKzicwNWRmvD5iBaZj8JYsRGa4xOxJmOyHlMDpsvvJ2OR2cODlvWyGFH6BYL1MfIAK3qph3hp0Az6g==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.46.tgz", + "integrity": "sha512-qj9T6B7bosI0VEsrWOVXZN1OXxS8Tp63ywyrLxNdOycnUtLdkgYcoBsN5y8ImnDDsnwrEWZOy1e+J4xSe7mA3Q==", "cpu": [ "x64" ], @@ -2977,9 +2531,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.18.tgz", - "integrity": "sha512-zk0RYO+LjiBCat2RTMHzAWaMky0cra9loH4oRrLKLLNuL+jarxKLFDA8xTZWEkCPLjUTwlRN7d28eDLLMgtUcQ==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.46.tgz", + "integrity": "sha512-8p7l4c3LU+eA5g9Et1JPhNeMC1oQwXTGU+uah8DPIBX7YXzqswvaBtyKVmXefVGi/DJU1x3YJsc3mbAp9aWzSQ==", "cpu": [ "arm64" ], @@ -2994,9 +2548,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.18.tgz", - "integrity": "sha512-yVuTrZ0RccD5+PEkpcLOBAuPbYBXS6rslENvIXfvJGXSdX5QGi1ehC4BjAMl5FkKLiam4kJECUI0l7Hq7T1vwg==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.46.tgz", + "integrity": "sha512-tUEnfr3Bn9u6FOjUb3PN9p+09qZC2j+wNDLKHzXXZn22rqGcUqR/ohCRSS+nG9B9+X+U+3FewNEHJkTmdIvMjQ==", "cpu": [ "ia32" ], @@ -3011,9 +2565,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.18.tgz", - "integrity": "sha512-7NRmE4hmUQNCbYU3Hn9Tz57mK9Qq4c97ZS+YlamlK6qG9Fb5g/BB3gPDe0iLlJkns/sYv2VWSkm8c3NmbEGjbg==", + "version": "1.15.46", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.46.tgz", + "integrity": "sha512-Vux7UDzBJYQggSuPfcl2w9iu+IJpgpRCxHzgCaVkELnAXAE4XZMOTX9HNcaNiwfeIDqdu2rkr69RuDm6wY8neA==", "cpu": [ "x64" ], @@ -3035,9 +2589,9 @@ "license": "Apache-2.0" }, "node_modules/@swc/types": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", - "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.27.tgz", + "integrity": "sha512-K6h3iUlqeM946U4sXFYeahefR1YBbXJvko+hv8WS8/0BNJ4OHiHRywMnQUJCqkR7Y9+hqQ1TvEpiKqUhz7NEFg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3050,12 +2604,16 @@ "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", "license": "MIT" }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } }, "node_modules/@types/hast": { "version": "2.3.10", @@ -3066,12 +2624,6 @@ "@types/unist": "^2" } }, - "node_modules/@types/lodash": { - "version": "4.17.24", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", - "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", - "license": "MIT" - }, "node_modules/@types/node": { "version": "20.19.37", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", @@ -3082,30 +2634,24 @@ "undici-types": "~6.21.0" } }, - "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==", - "license": "MIT" - }, "node_modules/@types/react": { - "version": "18.3.31", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", - "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "devOptional": true, "license": "MIT", "dependencies": { - "@types/prop-types": "*", "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", "peerDependencies": { - "@types/react": "^18.0.0" + "@types/react": "^19.2.0" } }, "node_modules/@types/react-gtm-module": { @@ -3132,17 +2678,20 @@ "license": "MIT" }, "node_modules/@vitejs/plugin-react-swc": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.11.0.tgz", - "integrity": "sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-4.3.2.tgz", + "integrity": "sha512-MlSlmSAYbYwPjGa+CjOAqwij09iPYAQDHwx8osVkwoDamCxXlg+avpkC65Ysv+L/uqUWvRTsnJCKPjVhZih/sA==", "dev": true, "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "1.0.0-beta.27", - "@swc/core": "^1.12.11" + "@rolldown/pluginutils": "^1.0.1", + "@swc/core": "^1.15.46" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4 || ^5 || ^6 || ^7" + "vite": "^4 || ^5 || ^6 || ^7 || ^8" } }, "node_modules/accessor-fn": { @@ -3434,64 +2983,23 @@ }, "node_modules/class-variance-authority": { "version": "0.7.1", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", - "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", - "license": "Apache-2.0", - "dependencies": { - "clsx": "^2.1.1" - }, - "funding": { - "url": "https://polar.sh/cva" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", "dependencies": { - "color-name": "~1.1.4" + "clsx": "^2.1.1" }, - "engines": { - "node": ">=7.0.0" + "funding": { + "url": "https://polar.sh/cva" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" + "engines": { + "node": ">=6" } }, "node_modules/comma-separated-tokens": { @@ -3538,6 +3046,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, "license": "MIT" }, "node_modules/d3": { @@ -3978,6 +3487,16 @@ "robust-predicates": "^3.0.2" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -4031,48 +3550,6 @@ "embla-carousel": "8.6.0" } }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -4372,12 +3849,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-arrayish": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", - "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", - "license": "MIT" - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -4468,7 +3939,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/kapsule": { "version": "1.16.3", @@ -4482,6 +3954,267 @@ "node": ">=12" } }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -4511,6 +4244,7 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "license": "MIT", + "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -4575,9 +4309,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "funding": [ { "type": "github", @@ -4687,9 +4421,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.22", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.22.tgz", + "integrity": "sha512-KBDEIpLrvpv16pp3K0Fw+UCoZfopFjjgeB+0tA/aaThfEE74kKDLrgg603YvOWJyg3+WYtyq3xYsQWsIyZlPqQ==", "funding": [ { "type": "opencollective", @@ -4706,7 +4440,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -4915,40 +4649,24 @@ "license": "MIT" }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, - "node_modules/react-base16-styling": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.10.0.tgz", - "integrity": "sha512-H1k2eFB6M45OaiRru3PBXkuCcn2qNmx+gzLb4a9IPMR7tMH8oBRXU5jGbPDYG1Hz+82d88ED0vjR8BmqU3pQdg==", - "license": "MIT", - "dependencies": { - "@types/lodash": "^4.17.0", - "color": "^4.2.3", - "csstype": "^3.1.3", - "lodash-es": "^4.17.21" - } - }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.2.8" } }, "node_modules/react-gtm-module": { @@ -4964,20 +4682,6 @@ "license": "MIT", "peer": true }, - "node_modules/react-json-tree": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.19.0.tgz", - "integrity": "sha512-PqT1WRVcWP+RROsZPQfNEKIC1iM/ZMfY4g5jN6oDnXp5593PPRAYgoHcgYCDjflAHQMtxl8XGdlTwIBdEGUXvw==", - "license": "MIT", - "dependencies": { - "@types/lodash": "^4.17.0", - "react-base16-styling": "^0.10.0" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/react-remove-scroll": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", @@ -5157,49 +4861,38 @@ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", "license": "Unlicense" }, - "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" } }, "node_modules/run-parallel": { @@ -5238,22 +4931,10 @@ "license": "MIT" }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", - "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.3.1" - } + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" }, "node_modules/source-map-js": { "version": "1.2.1", @@ -5410,13 +5091,13 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -5602,24 +5283,23 @@ } }, "node_modules/vite": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", - "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -5628,14 +5308,15 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -5644,13 +5325,16 @@ "@types/node": { "optional": true }, - "jiti": { + "@vitejs/devtools": { "optional": true }, - "less": { + "esbuild": { "optional": true }, - "lightningcss": { + "jiti": { + "optional": true + }, + "less": { "optional": true }, "sass": { @@ -5676,28 +5360,10 @@ } } }, - "node_modules/vite/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { diff --git a/app/package.json b/app/package.json index a3794b69..42c2630c 100644 --- a/app/package.json +++ b/app/package.json @@ -30,10 +30,9 @@ "embla-carousel-react": "^8.5.2", "html2canvas": "^1.4.1", "lucide-react": "^0.486.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.2.8", + "react-dom": "^19.2.8", "react-gtm-module": "^2.0.11", - "react-json-tree": "^0.19.0", "react-resizable-panels": "^2.1.7", "react-syntax-highlighter": "^15.6.1", "react-type-animation": "^3.2.0", @@ -47,15 +46,15 @@ }, "devDependencies": { "@types/node": "^20.19.4", - "@types/react": "^18.3.18", - "@types/react-dom": "^18.3.5", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", "@types/react-gtm-module": "^2.0.4", "@types/react-syntax-highlighter": "^15.5.13", - "@vitejs/plugin-react-swc": "^3.10.2", + "@vitejs/plugin-react-swc": "^4.3.2", "autoprefixer": "^10.4.21", "postcss": "^8.5.4", "tailwindcss": "^3.4.17", "typescript": "^5.7.3", - "vite": "^6.4.2" + "vite": "^8.1.5" } } diff --git a/app/src/components/dataPanel.tsx b/app/src/components/dataPanel.tsx index 4cf67120..441574a4 100644 --- a/app/src/components/dataPanel.tsx +++ b/app/src/components/dataPanel.tsx @@ -1,5 +1,4 @@ -import { Dispatch, SetStateAction } from "react"; -import { JSONTree } from 'react-json-tree'; +import { Dispatch, SetStateAction, type ReactNode } from "react"; import { Link, Node } from "./model"; import { Copy, SquareArrowOutUpRight, X } from "lucide-react"; import SyntaxHighlighter from 'react-syntax-highlighter'; @@ -32,6 +31,77 @@ const excludedProperties = [ "fy", ] +function isPlainObject(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value) +} + +function renderSourceBlock(value: string) { + return ( + + {value} + + ) +} + +function renderJsonValue(value: unknown, path: string[]): ReactNode { + const key = path[path.length - 1] + + if (key === "src" && typeof value === "string") { + return renderSourceBlock(value) + } + + if (Array.isArray(value)) { + return ( +
+ Array({value.length}) +
+ {value.map((item, index) => ( +
+ [{index}] +
+ {renderJsonValue(item, [...path, String(index)])} +
+
+ ))} +
+
+ ) + } + + if (isPlainObject(value)) { + const entries = Object.entries(value).filter(([nestedKey]) => !excludedProperties.includes(nestedKey)) + + return ( +
+ Object({entries.length}) +
+ {entries.map(([nestedKey, nestedValue]) => ( +
+ {nestedKey}: +
+ {renderJsonValue(nestedValue, [...path, nestedKey])} +
+
+ ))} +
+
+ ) + } + + return {String(value)} +} + export default function DataPanel({ obj, setObj, url }: Props) { if (!obj) return null; @@ -50,76 +120,17 @@ export default function DataPanel({ obj, setObj, url }: Props) {
- { - object.map(([key, value]) => ( -
-

{key}:

- { - key === "src" ? - - {value} - - : typeof value === "object" && value !== null ? - !excludedProperties.includes(k)))} - theme={{ - base00: 'transparent', // background - base01: '#000000', - base02: '#CE9178', - base03: '#CE9178', // open values - base04: '#CE9178', - base05: '#CE9178', - base06: '#CE9178', - base07: '#CE9178', - base08: '#CE9178', - base09: '#b5cea8', // numbers - base0A: '#CE9178', - base0B: '#CE9178', // close values - base0C: '#CE9178', - base0D: '#99E4E5', // * keys - base0E: '#ae81ff', - base0F: '#cc6633' - }} - valueRenderer={(_valueAsString, value, keyPath) => { - if (keyPath === "src") { - return - {value as string} - - } - return {value as string} - }} - /> - : {value} - } + {object.map(([key, value]) => ( +
+

{key}:

+
+ {renderJsonValue(value, [key])}
- )) - } +
+ ))}
- { - "category" in obj && + {"category" in obj && ( <>
From 42424e049387ec1089dc6672b4fa303d516b16b1 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jul 2026 16:25:59 +0300 Subject: [PATCH 40/41] fix: address latest review comments --- app/src/components/model.ts | 4 +++- app/src/components/toolbar.tsx | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/components/model.ts b/app/src/components/model.ts index 64957c4d..91380b48 100644 --- a/app/src/components/model.ts +++ b/app/src/components/model.ts @@ -263,8 +263,10 @@ export class Graph { } public removeLinks(ids: number[] = []) { + const idsSet = ids.length !== 0 ? new Set(ids) : null + this.elements.links = this.elements.links.filter(link => { - const isConnectedToIds = ids.length !== 0 && (ids.includes(link.source) || ids.includes(link.target)) + const isConnectedToIds = idsSet !== null && (idsSet.has(link.source) || idsSet.has(link.target)) const hasEndpoints = this.nodesMap.get(link.source) && this.nodesMap.get(link.target) const shouldKeep = Boolean(hasEndpoints) && !(link.collapsed && isConnectedToIds) diff --git a/app/src/components/toolbar.tsx b/app/src/components/toolbar.tsx index a56c545a..93687e78 100644 --- a/app/src/components/toolbar.tsx +++ b/app/src/components/toolbar.tsx @@ -32,7 +32,7 @@ interface ZoomControlsProps { } export function ZoomControls({ canvasRef, className, selectedObjects }: ZoomControlsProps) { - const handleZoomClick = (changefactor: number) => { + const handleZoomClick = (changeFactor: number) => { const canvas = canvasRef.current if (!canvas) return if (selectedObjects && selectedObjects.length > 0) { @@ -53,7 +53,7 @@ export function ZoomControls({ canvasRef, className, selectedObjects }: ZoomCont canvas.centerAt(cx, cy, 300) } } - canvas.zoom(canvas.getZoom() * changefactor) + canvas.zoom(canvas.getZoom() * changeFactor) } return ( From bffa01ba977178c6e6354feed09e1eb0697435e5 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jul 2026 16:47:25 +0300 Subject: [PATCH 41/41] fix: guard element menu repo URL for links --- app/src/components/elementMenu.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/src/components/elementMenu.tsx b/app/src/components/elementMenu.tsx index 4aa1727b..70c14d85 100644 --- a/app/src/components/elementMenu.tsx +++ b/app/src/components/elementMenu.tsx @@ -31,9 +31,11 @@ export default function ElementMenu({ obj, objects, setPath, handleRemove, posit if (!obj || !position) return null - const objURL = "category" in obj && obj.category === "File" - ? `${url}/tree/master/${obj.data.path}/${obj.data.name}` - : `${url}/tree/master/${obj.data.path}#L${obj.data.src_start}-L${obj.data.src_end + 1}` + const nodeURL = "category" in obj + ? obj.category === "File" + ? `${url}/tree/master/${obj.data.path}/${obj.data.name}` + : `${url}/tree/master/${obj.data.path}#L${obj.data.src_start}-L${obj.data.src_end + 1}` + : undefined const parentRect = parentRef?.current?.getBoundingClientRect() const parentW = parentRef?.current?.clientWidth || 0 @@ -152,11 +154,11 @@ export default function ElementMenu({ obj, objects, setPath, handleRemove, posit <> { - window.open(objURL, '_blank'); + if (nodeURL) window.open(nodeURL, '_blank'); }} > @@ -193,7 +195,7 @@ export default function ElementMenu({ obj, objects, setPath, handleRemove, posit )