diff --git a/CHANGELOG.md b/CHANGELOG.md index c1bc150c..4e74952f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- **Feature: Graph Workspace declutter + calmer structural exploration** (PR #483 by @ZohaibHassan16, follow-up by @KaifAhmad1): + - Added a calmer default presentation for dense graphs: reduced label pressure, stronger inactive-state muting, and tuned zoom-tier visibility to improve readability during overview and structure navigation. + - Added display-edge aggregation with raw-edge bundle metadata retention, enabling cleaner visuals while preserving drill-down context for selected edges. + - Added grouped community view and neighborhood collapse/expand controls for high-degree local structures in Graph Workspace and Neighborhood panel flows. + - Extended graph selection/runtime state with display-state metadata (`groupedViewAvailable`, visible/collapsed neighbor counts, aggregated edge descriptors) for plugin and panel introspection. + - Added regression coverage for `resolveDisplayGraph` behavior in `explorer/tests/graphSceneState.display.test.ts`: + - parallel-edge aggregation in full view + - collapse behavior preserving active-path neighbors + - grouped community-node/community-edge projection behavior + - Follow-up merge resolution synced the PR branch with `main` after Explorer path migration (`semantica-explorer` -> `explorer`) and preserved PR #483 behavior in conflicted Graph Workspace files. + - **Fix: DeepSeekProvider now uses OpenAI SDK instead of unmaintained deepseek SDK** (closes #482, PR #482 by @liling, review fixes by @KaifAhmad1): - **Root cause**: The `deepseek` PyPI package has no `deepseek.Client`, causing `AttributeError` on every `DeepSeekProvider` instantiation. The DeepSeek API is OpenAI-compatible, so the `openai` SDK is the correct client. - **`_init_client` rewritten**: Replaced `import deepseek; deepseek.Client(api_key=...)` with `from openai import OpenAI; OpenAI(api_key=..., base_url=self.base_url)`, matching the pattern already used by `NovitaProvider`. diff --git a/explorer/package-lock.json b/explorer/package-lock.json index 52a29c1f..1f057188 100644 --- a/explorer/package-lock.json +++ b/explorer/package-lock.json @@ -40,6 +40,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.4.0", + "tsx": "^4.21.0", "typescript": "~5.9.3", "typescript-eslint": "^8.57.0", "vite": "^5.4.0" @@ -350,9 +351,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "cpu": [ "ppc64" ], @@ -363,13 +364,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "cpu": [ "arm" ], @@ -380,13 +381,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "cpu": [ "arm64" ], @@ -397,13 +398,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", "cpu": [ "x64" ], @@ -414,13 +415,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", "cpu": [ "arm64" ], @@ -431,13 +432,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", "cpu": [ "x64" ], @@ -448,13 +449,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", "cpu": [ "arm64" ], @@ -465,13 +466,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", "cpu": [ "x64" ], @@ -482,13 +483,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", "cpu": [ "arm" ], @@ -499,13 +500,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", "cpu": [ "arm64" ], @@ -516,13 +517,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", "cpu": [ "ia32" ], @@ -533,13 +534,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", "cpu": [ "loong64" ], @@ -550,13 +551,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", "cpu": [ "mips64el" ], @@ -567,13 +568,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", "cpu": [ "ppc64" ], @@ -584,13 +585,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", "cpu": [ "riscv64" ], @@ -601,13 +602,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", "cpu": [ "s390x" ], @@ -618,13 +619,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", "cpu": [ "x64" ], @@ -635,13 +636,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", "cpu": [ "x64" ], @@ -652,13 +670,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", "cpu": [ "x64" ], @@ -669,13 +704,30 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", "cpu": [ "x64" ], @@ -686,13 +738,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", "cpu": [ "arm64" ], @@ -703,13 +755,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", "cpu": [ "ia32" ], @@ -720,13 +772,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", "cpu": [ "x64" ], @@ -737,7 +789,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -898,29 +950,43 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1048,9 +1114,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", - "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", "cpu": [ "arm" ], @@ -1062,9 +1128,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", - "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", "cpu": [ "arm64" ], @@ -1076,9 +1142,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", - "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", "cpu": [ "arm64" ], @@ -1090,9 +1156,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", - "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", "cpu": [ "x64" ], @@ -1104,9 +1170,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", - "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", "cpu": [ "arm64" ], @@ -1118,9 +1184,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", - "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", "cpu": [ "x64" ], @@ -1132,9 +1198,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", - "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", "cpu": [ "arm" ], @@ -1146,9 +1212,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", - "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", "cpu": [ "arm" ], @@ -1160,9 +1226,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", - "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", "cpu": [ "arm64" ], @@ -1174,9 +1240,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", - "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", "cpu": [ "arm64" ], @@ -1188,9 +1254,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", - "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", "cpu": [ "loong64" ], @@ -1202,9 +1268,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", - "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", "cpu": [ "loong64" ], @@ -1216,9 +1282,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", - "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", "cpu": [ "ppc64" ], @@ -1230,9 +1296,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", - "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", "cpu": [ "ppc64" ], @@ -1244,9 +1310,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", - "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", "cpu": [ "riscv64" ], @@ -1258,9 +1324,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", - "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", "cpu": [ "riscv64" ], @@ -1272,9 +1338,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", - "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", "cpu": [ "s390x" ], @@ -1286,9 +1352,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", - "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", "cpu": [ "x64" ], @@ -1300,9 +1366,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", - "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", "cpu": [ "x64" ], @@ -1314,9 +1380,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", - "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", "cpu": [ "x64" ], @@ -1328,9 +1394,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", - "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", "cpu": [ "arm64" ], @@ -1342,9 +1408,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", - "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", "cpu": [ "arm64" ], @@ -1356,9 +1422,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", - "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", "cpu": [ "ia32" ], @@ -1370,9 +1436,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", - "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", "cpu": [ "x64" ], @@ -1384,9 +1450,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", - "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", "cpu": [ "x64" ], @@ -1416,9 +1482,9 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.99.0", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.99.0.tgz", - "integrity": "sha512-3Jv3WQG0BCcH7G+7lf/bP8QyBfJOXeY+T08Rin3GZ1bshvwlbPt7NrDHMEzGdKIOmOzvIQmxjk28YEQX60k7pQ==", + "version": "5.99.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.99.2.tgz", + "integrity": "sha512-1HunU0bXVsR1ZJMZbcOPE6VtaBJxsW809RE9xPe4Gz7MlB0GWwQvuTPhMoEmQ/hIzFKJ/DWAuttIe7BOaWx0tA==", "license": "MIT", "funding": { "type": "github", @@ -1426,12 +1492,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.99.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.99.0.tgz", - "integrity": "sha512-OY2bCqPemT1LlqJ8Y2CUau4KELnIhhG9Ol3ZndPbdnB095pRbPo1cHuXTndg8iIwtoHTgwZjyaDnQ0xD0mYwAw==", + "version": "5.99.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.99.2.tgz", + "integrity": "sha512-vM91UEe45QUS9ED6OklsVL15i8qKcRqNwpWzPTVWvRPRSEgDudDgHpvyTjcdlwHcrKNa80T+xXYcchT2noPnZA==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.99.0" + "@tanstack/query-core": "5.99.2" }, "funding": { "type": "github", @@ -1595,17 +1661,17 @@ "peer": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.1.tgz", - "integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz", + "integrity": "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.58.1", - "@typescript-eslint/type-utils": "8.58.1", - "@typescript-eslint/utils": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/type-utils": "8.58.2", + "@typescript-eslint/utils": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -1618,7 +1684,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.58.1", + "@typescript-eslint/parser": "^8.58.2", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -1634,16 +1700,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.1.tgz", - "integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.2.tgz", + "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.58.1", - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", "debug": "^4.4.3" }, "engines": { @@ -1659,14 +1725,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.1.tgz", - "integrity": "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.2.tgz", + "integrity": "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.58.1", - "@typescript-eslint/types": "^8.58.1", + "@typescript-eslint/tsconfig-utils": "^8.58.2", + "@typescript-eslint/types": "^8.58.2", "debug": "^4.4.3" }, "engines": { @@ -1681,14 +1747,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.1.tgz", - "integrity": "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz", + "integrity": "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1" + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1699,9 +1765,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.1.tgz", - "integrity": "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz", + "integrity": "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==", "dev": true, "license": "MIT", "engines": { @@ -1716,15 +1782,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.1.tgz", - "integrity": "sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.2.tgz", + "integrity": "sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1", - "@typescript-eslint/utils": "8.58.1", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -1741,9 +1807,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.1.tgz", - "integrity": "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.2.tgz", + "integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==", "dev": true, "license": "MIT", "engines": { @@ -1755,16 +1821,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.1.tgz", - "integrity": "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz", + "integrity": "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.58.1", - "@typescript-eslint/tsconfig-utils": "8.58.1", - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1", + "@typescript-eslint/project-service": "8.58.2", + "@typescript-eslint/tsconfig-utils": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1835,16 +1901,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.1.tgz", - "integrity": "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz", + "integrity": "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.58.1", - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1" + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1859,13 +1925,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.1.tgz", - "integrity": "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz", + "integrity": "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.1", + "@typescript-eslint/types": "8.58.2", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -2038,9 +2104,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.18", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.18.tgz", - "integrity": "sha512-VSnGQAOLtP5mib/DPyg2/t+Tlv65NTBz83BJBJvmLVHHuKJVaDOBvJJykiT5TR++em5nfAySPccDZDa4oSrn8A==", + "version": "2.10.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.20.tgz", + "integrity": "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2106,9 +2172,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001787", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz", - "integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==", + "version": "1.0.30001788", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", + "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==", "dev": true, "funding": [ { @@ -2390,16 +2456,16 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.335", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.335.tgz", - "integrity": "sha512-q9n5T4BR4Xwa2cwbrwcsDJtHD/enpQ5S1xF1IAtdqf5AAgqDFmR/aakqH3ChFdqd/QXJhS3rnnXFtexU7rax6Q==", + "version": "1.5.340", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.340.tgz", + "integrity": "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==", "dev": true, "license": "ISC" }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2407,32 +2473,35 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" } }, "node_modules/escalade": { @@ -2519,9 +2588,9 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", - "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dev": true, "license": "MIT", "dependencies": { @@ -2535,7 +2604,7 @@ "node": ">=18" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "node_modules/eslint-plugin-react-refresh": { @@ -2777,6 +2846,19 @@ "node": ">=6.9.0" } }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3386,9 +3468,9 @@ } }, "node_modules/postcss": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", - "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", "dev": true, "funding": [ { @@ -3598,10 +3680,20 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/rollup": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", - "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3615,31 +3707,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.1", - "@rollup/rollup-android-arm64": "4.60.1", - "@rollup/rollup-darwin-arm64": "4.60.1", - "@rollup/rollup-darwin-x64": "4.60.1", - "@rollup/rollup-freebsd-arm64": "4.60.1", - "@rollup/rollup-freebsd-x64": "4.60.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", - "@rollup/rollup-linux-arm-musleabihf": "4.60.1", - "@rollup/rollup-linux-arm64-gnu": "4.60.1", - "@rollup/rollup-linux-arm64-musl": "4.60.1", - "@rollup/rollup-linux-loong64-gnu": "4.60.1", - "@rollup/rollup-linux-loong64-musl": "4.60.1", - "@rollup/rollup-linux-ppc64-gnu": "4.60.1", - "@rollup/rollup-linux-ppc64-musl": "4.60.1", - "@rollup/rollup-linux-riscv64-gnu": "4.60.1", - "@rollup/rollup-linux-riscv64-musl": "4.60.1", - "@rollup/rollup-linux-s390x-gnu": "4.60.1", - "@rollup/rollup-linux-x64-gnu": "4.60.1", - "@rollup/rollup-linux-x64-musl": "4.60.1", - "@rollup/rollup-openbsd-x64": "4.60.1", - "@rollup/rollup-openharmony-arm64": "4.60.1", - "@rollup/rollup-win32-arm64-msvc": "4.60.1", - "@rollup/rollup-win32-ia32-msvc": "4.60.1", - "@rollup/rollup-win32-x64-gnu": "4.60.1", - "@rollup/rollup-win32-x64-msvc": "4.60.1", + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", "fsevents": "~2.3.2" } }, @@ -3770,6 +3862,26 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3798,16 +3910,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.1.tgz", - "integrity": "sha512-gf6/oHChByg9HJvhMO1iBexJh12AqqTfnuxscMDOVqfJW3htsdRJI/GfPpHTTcyeB8cSTUY2JcZmVgoyPqcrDg==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.2.tgz", + "integrity": "sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.58.1", - "@typescript-eslint/parser": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1", - "@typescript-eslint/utils": "8.58.1" + "@typescript-eslint/eslint-plugin": "8.58.2", + "@typescript-eslint/parser": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4005,6 +4117,436 @@ } } }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/explorer/package.json b/explorer/package.json index 7c31fdaf..23df2b07 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -8,7 +8,8 @@ "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview", - "test:graph-store": "node --test tests/graphStore.multi-edge.test.mjs" + "test:graph-store": "node --test tests/graphStore.multi-edge.test.mjs", + "test:graph-workspace": "node --import tsx --test tests/graphSceneState.display.test.ts" }, "dependencies": { "@monaco-editor/react": "^4.7.0", @@ -43,6 +44,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.4.0", + "tsx": "^4.21.0", "typescript": "~5.9.3", "typescript-eslint": "^8.57.0", "vite": "^5.4.0" diff --git a/explorer/src/store/graphStore.ts b/explorer/src/store/graphStore.ts index 7371978e..f2663746 100644 --- a/explorer/src/store/graphStore.ts +++ b/explorer/src/store/graphStore.ts @@ -42,6 +42,10 @@ export interface NodeAttributes { haloColor?: string; labelVisibilityPolicy?: GraphLabelVisibilityPolicy; highlighted?: boolean; + communityId?: string; + isCommunityGroup?: boolean; + memberCount?: number; + anchorNodeId?: string | null; nodeType: string; content: string; @@ -74,6 +78,12 @@ export interface EdgeAttributes { parallelIndex?: number; parallelCount?: number; familySize?: number; + rawEdgeIds?: string[]; + isAggregated?: boolean; + aggregateCount?: number; + dominantEdgeType?: string; + representativeWeight?: number; + bundleKind?: "parallel" | "bidirectional" | "community"; edgeType: string; diff --git a/explorer/src/workspaces/GraphWorkspace/GraphCanvas.tsx b/explorer/src/workspaces/GraphWorkspace/GraphCanvas.tsx index 331f0f47..216b4142 100644 --- a/explorer/src/workspaces/GraphWorkspace/GraphCanvas.tsx +++ b/explorer/src/workspaces/GraphWorkspace/GraphCanvas.tsx @@ -84,6 +84,8 @@ export interface GraphCanvasProps { layoutSource?: string; onLayoutStatusChange?: (status: GraphLayoutStatus) => void; viewMode: GraphViewMode; + aggregationEnabled?: boolean; + collapsedNeighborhoodNodeIds?: string[]; className?: string; showFitViewButton?: boolean; pluginOverlays?: ReactNode[]; @@ -111,17 +113,15 @@ const FA2_SETTINGS = { const SIGMA_SETTINGS = { allowInvalidContainer: true, - labelRenderedSizeThreshold: 2, + labelRenderedSizeThreshold: 6, defaultNodeType: "circle", defaultEdgeType: "line", hideLabelsOnMove: false, hideEdgesOnMove: false, enableEdgeEvents: true, - renderEdgeLabels: true, - edgeLabelSize: 10, - edgeLabelColor: { color: "rgba(180, 210, 255, 0.72)" }, - labelDensity: 1.1, - labelGridCellSize: 80, + renderEdgeLabels: false, + labelDensity: 0.7, + labelGridCellSize: 140, zIndex: true, minCameraRatio: 0.04, maxCameraRatio: 8, @@ -581,6 +581,8 @@ export const GraphCanvas = forwardRef( temporalState, isLayoutRunning, viewMode, + aggregationEnabled = true, + collapsedNeighborhoodNodeIds = [], className, showFitViewButton = true, pluginOverlays = [], @@ -616,8 +618,11 @@ export const GraphCanvas = forwardRef( const isFocusedView = viewMode === "focused" && Boolean(selectedNodeId) && graph.hasNode(selectedNodeId); const displayGraph = useMemo( - () => resolveDisplayGraph(selectedNodeId, activePath, activePathEdgeIds, viewMode), - [activePath, activePathEdgeIds, selectedNodeId, viewMode], + () => resolveDisplayGraph(selectedNodeId, activePath, activePathEdgeIds, viewMode, { + aggregationEnabled, + collapsedNeighborhoodNodeIds, + }).graph, + [activePath, activePathEdgeIds, aggregationEnabled, collapsedNeighborhoodNodeIds, selectedNodeId, viewMode], ); const interactionState = useMemo( diff --git a/explorer/src/workspaces/GraphWorkspace/GraphRuntimeStage.tsx b/explorer/src/workspaces/GraphWorkspace/GraphRuntimeStage.tsx index f1dac367..bbb60115 100644 --- a/explorer/src/workspaces/GraphWorkspace/GraphRuntimeStage.tsx +++ b/explorer/src/workspaces/GraphWorkspace/GraphRuntimeStage.tsx @@ -67,6 +67,10 @@ function buildSelectedNodeState(nodeId: string): GraphSelectedNodeState | null { valid_until: attributes.valid_until ?? null, properties: attributes.properties ?? {}, neighborCount: graph.neighbors(nodeId).length, + visibleNeighborCount: graph.neighbors(nodeId).length, + collapsedNeighborCount: 0, + isNeighborhoodCollapsed: false, + canCollapseNeighborhood: graph.neighbors(nodeId).length > 8, }; } diff --git a/explorer/src/workspaces/GraphWorkspace/GraphWorkspace.tsx b/explorer/src/workspaces/GraphWorkspace/GraphWorkspace.tsx index 0c04357d..48eeef03 100644 --- a/explorer/src/workspaces/GraphWorkspace/GraphWorkspace.tsx +++ b/explorer/src/workspaces/GraphWorkspace/GraphWorkspace.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import type Graph from "graphology"; import { batchMergeEdges, batchMergeNodes, graph } from "../../store/graphStore"; import { logEvent } from "../../store/registryStore"; import type { EdgeAttributes, NodeAttributes } from "../../store/graphStore"; @@ -11,6 +12,7 @@ import { useLoadGraph, useReloadGraph } from "./useLoadGraph"; import { GraphLoadingOverlay } from "./GraphLoadingOverlay"; import { createGraphLoadProgress, getGraphLoadTitle } from "./graphLoading"; import { GRAPH_THEME, withAlpha } from "./graphTheme"; +import { resolveDisplayGraph } from "./graphSceneState"; import { type GraphPlugin, type GraphPluginActionRequest, @@ -23,6 +25,7 @@ import type { LinkPrediction, PathResponse } from "./GraphInspectorPanel"; import type { GraphSceneHandle, GraphSceneRuntime } from "./scene"; import type { GraphAnalyticsSnapshot, + GraphDisplayStateSnapshot, GraphDiagnosticsSnapshot, GraphEffectToggle, GraphEffectsState, @@ -89,7 +92,7 @@ const DEFAULT_EFFECTS_STATE: GraphEffectsState = { pathFlowEnabled: false, lensEnabled: false, temporalEmphasisEnabled: false, - semanticRegionsEnabled: true, + semanticRegionsEnabled: false, contoursEnabled: false, pathfindingEnabled: false, communitiesEnabled: false, @@ -494,7 +497,10 @@ function buildRealtimeEdgeAttributes(payload: { }; } -function buildSelectedNodeState(nodeId: string): GraphSelectedNodeState | null { +function buildSelectedNodeState( + nodeId: string, + displayState: GraphDisplayStateSnapshot, +): GraphSelectedNodeState | null { if (!nodeId || !graph.hasNode(nodeId)) { return null; } @@ -519,31 +525,58 @@ function buildSelectedNodeState(nodeId: string): GraphSelectedNodeState | null { valid_until: attributes.valid_until ?? null, properties: attributes.properties ?? {}, neighborCount: graph.neighbors(nodeId).length, + visibleNeighborCount: displayState.selectedVisibleNeighborIds.length, + collapsedNeighborCount: displayState.selectedCollapsedNeighborIds.length, + isNeighborhoodCollapsed: displayState.selectedCollapsedNeighborIds.length > 0, + canCollapseNeighborhood: graph.neighbors(nodeId).length > 8, }; } -function buildSelectedEdgeState(edgeId: string): GraphSelectedEdgeState | null { - if (!edgeId || !graph.hasEdge(edgeId)) { +function buildSelectedEdgeState( + edgeId: string, + displayGraph: typeof graph | Graph, +): GraphSelectedEdgeState | null { + if (!edgeId || !displayGraph.hasEdge(edgeId)) { return null; } - const [sourceId, targetId] = graph.extremities(edgeId); - const attributes = graph.getEdgeAttributes(edgeId) as { + const [displaySourceId, displayTargetId] = displayGraph.extremities(edgeId); + const attributes = displayGraph.getEdgeAttributes(edgeId) as { edgeType?: string; weight?: number; properties?: Record; familyId?: string; + rawEdgeIds?: string[]; + isAggregated?: boolean; + aggregateCount?: number; + bundleKind?: "parallel" | "bidirectional" | "community"; + dominantEdgeType?: string; + representativeWeight?: number; }; - const sourceAttributes = graph.getNodeAttributes(sourceId) as { label?: string; content?: string }; - const targetAttributes = graph.getNodeAttributes(targetId) as { label?: string; content?: string }; + const rawEdgeIds = attributes.rawEdgeIds?.length ? attributes.rawEdgeIds.map((rawEdgeId) => String(rawEdgeId)) : [edgeId]; + const primaryRawEdgeId = rawEdgeIds.find((rawEdgeId) => graph.hasEdge(rawEdgeId)) ?? rawEdgeIds[0]; + let sourceId = displaySourceId; + let targetId = displayTargetId; + if (primaryRawEdgeId && graph.hasEdge(primaryRawEdgeId)) { + [sourceId, targetId] = graph.extremities(primaryRawEdgeId); + } + const sourceAttributes = graph.hasNode(sourceId) + ? (graph.getNodeAttributes(sourceId) as { label?: string; content?: string }) + : ({ label: displaySourceId } as { label?: string; content?: string }); + const targetAttributes = graph.hasNode(targetId) + ? (graph.getNodeAttributes(targetId) as { label?: string; content?: string }) + : ({ label: displayTargetId } as { label?: string; content?: string }); const properties = attributes.properties ?? {}; const familyId = String(attributes.familyId || edgeId); let familySize = 0; let siblingCount = 0; - graph.forEachEdge((candidateEdgeId, candidateAttrs) => { - const edgeAttrs = candidateAttrs as { familyId?: string }; - const [candidateSource, candidateTarget] = graph.extremities(candidateEdgeId); - if (String(edgeAttrs.familyId || candidateEdgeId) === familyId) { + rawEdgeIds.forEach((rawEdgeId) => { + if (!graph.hasEdge(rawEdgeId)) { + return; + } + const candidateAttrs = graph.getEdgeAttributes(rawEdgeId) as { familyId?: string }; + const [candidateSource, candidateTarget] = graph.extremities(rawEdgeId); + if (String(candidateAttrs.familyId || rawEdgeId) === familyId) { familySize += 1; } if (candidateSource === sourceId && candidateTarget === targetId) { @@ -551,6 +584,11 @@ function buildSelectedEdgeState(edgeId: string): GraphSelectedEdgeState | null { } }); + if (attributes.isAggregated) { + siblingCount = rawEdgeIds.filter((rawEdgeId) => graph.hasEdge(rawEdgeId)).length; + familySize = Math.max(familySize, siblingCount); + } + return { id: edgeId, familyId, @@ -564,6 +602,12 @@ function buildSelectedEdgeState(edgeId: string): GraphSelectedEdgeState | null { provenanceCount: getProvenanceCount(properties), familySize, siblingCount, + isAggregated: Boolean(attributes.isAggregated), + aggregateCount: Number(attributes.aggregateCount ?? rawEdgeIds.length), + rawEdgeIds, + bundleKind: attributes.bundleKind ?? null, + dominantEdgeType: attributes.dominantEdgeType ?? null, + representativeWeight: Number(attributes.representativeWeight ?? attributes.weight ?? 1), }; } @@ -626,7 +670,9 @@ export function GraphWorkspace() { const [selectedNodeId, setSelectedNodeId] = useState(""); const [selectedEdgeId, setSelectedEdgeId] = useState(""); const [isLayoutRunning, setIsLayoutRunning] = useState(false); - const [viewMode, setViewMode] = useState("focused"); + const [viewMode, setViewMode] = useState("full"); + const [aggregationEnabled] = useState(true); + const [collapsedNeighborhoodNodeIds, setCollapsedNeighborhoodNodeIds] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [searchResults, setSearchResults] = useState([]); const [searchError, setSearchError] = useState(""); @@ -663,7 +709,7 @@ export function GraphWorkspace() { focusedNodeId: "", activePath: [], activePathEdgeIds: [], - viewMode: "focused", + viewMode: "full", zoomTier: "overview", isLayoutRunning: false, }); @@ -778,6 +824,28 @@ export function GraphWorkspace() { }, [debouncedTime, isLoading]); const focusNode = useCallback((nodeId: string) => { + const currentDisplayGraph = pluginRuntimeRef.current?.displayGraph ?? graph; + if (viewMode === "grouped" && currentDisplayGraph.hasNode(nodeId)) { + const displayAttrs = currentDisplayGraph.getNodeAttributes(nodeId) as NodeAttributes; + const communityGroup = displayAttrs.properties?.__communityGroup as + | { + anchorNodeId?: string | null; + sampleNodeIds?: string[]; + } + | undefined; + const anchorNodeId = communityGroup?.anchorNodeId || communityGroup?.sampleNodeIds?.[0] || ""; + if (anchorNodeId && graph.hasNode(anchorNodeId)) { + setViewMode("focused"); + setSelectedNodeId(anchorNodeId); + setSelectedEdgeId(""); + setPathResult(null); + setSearchResults([]); + setSearchError(""); + setIsLayoutRunning(false); + return; + } + } + setSelectedNodeId(nodeId); setSelectedEdgeId(""); setPathResult(null); @@ -786,7 +854,7 @@ export function GraphWorkspace() { if (nodeId) { setIsLayoutRunning(false); } - }, []); + }, [viewMode]); const handleEdgeSelect = useCallback((edgeId: string) => { setSelectedEdgeId(edgeId); @@ -930,32 +998,56 @@ export function GraphWorkspace() { }; }, []); - const focusedSummary = useMemo(() => { - if (!selectedNodeId || !graph.hasNode(selectedNodeId)) { - return null; - } - - const localNeighborCount = graph.neighbors(selectedNodeId).length; - if (viewMode === "focused") { - const visibleNeighbors = Math.min(localNeighborCount, 16); - return `${visibleNeighbors + 1} nodes in focused view`; - } - - return `${localNeighborCount} direct neighbors highlighted`; - }, [selectedNodeId, viewMode]); + useEffect(() => { + setCollapsedNeighborhoodNodeIds([]); + }, [summary?.edgeCount, summary?.nodeCount]); const showLoadingOverlay = isLoading || isFetching || loadingProgress?.phase === "stabilizing_layout"; const hasGraphContent = Boolean(summary?.nodeCount); const activePath = pathResult?.path ?? []; const activePathEdgeIds = pathResult?.edge_ids ?? []; + const displayResult = useMemo( + () => resolveDisplayGraph(selectedNodeId, activePath, activePathEdgeIds, viewMode, { + aggregationEnabled, + collapsedNeighborhoodNodeIds, + }), + [activePath, activePathEdgeIds, aggregationEnabled, collapsedNeighborhoodNodeIds, selectedNodeId, viewMode], + ); + const displayState = displayResult.state; + const focusedSummary = useMemo(() => { + if (!selectedNodeId || !graph.hasNode(selectedNodeId)) { + if (viewMode === "grouped") { + return displayState.groupedViewAvailable + ? "Communities compressed into grouped structure view" + : "Grouped view is unavailable for the current graph"; + } + return null; + } + + const localNeighborCount = graph.neighbors(selectedNodeId).length; + if (viewMode === "focused") { + const visibleNeighbors = displayState.selectedVisibleNeighborIds.length || Math.min(localNeighborCount, 16); + return `${visibleNeighbors + 1} nodes in focused view`; + } + + if (viewMode === "grouped") { + return "Grouped structure view with direct community drill-in"; + } + + if (displayState.selectedCollapsedNeighborIds.length > 0) { + return `${displayState.selectedVisibleNeighborIds.length} visible neighbors, ${displayState.selectedCollapsedNeighborIds.length} collapsed`; + } + + return `${localNeighborCount} direct neighbors highlighted`; + }, [displayState, selectedNodeId, viewMode]); const graphSummary = summary as GraphLoadSummary | null; const selectedNodeState = useMemo( - () => buildSelectedNodeState(selectedNodeId), - [selectedNodeId, summary?.nodeCount, summary?.edgeCount], + () => buildSelectedNodeState(selectedNodeId, displayState), + [displayState, selectedNodeId, summary?.nodeCount, summary?.edgeCount], ); const selectedEdgeState = useMemo( - () => buildSelectedEdgeState(selectedEdgeId), - [selectedEdgeId, summary?.nodeCount, summary?.edgeCount], + () => buildSelectedEdgeState(selectedEdgeId, displayResult.graph), + [displayResult.graph, selectedEdgeId, summary?.nodeCount, summary?.edgeCount], ); const temporalState = useMemo( () => ({ @@ -1063,6 +1155,20 @@ export function GraphWorkspace() { case "setViewMode": setViewMode(action.viewMode); return; + case "collapseNeighborhood": + if (!selectedNodeId) { + return; + } + setCollapsedNeighborhoodNodeIds((current) => ( + current.includes(selectedNodeId) ? current : [...current, selectedNodeId] + )); + return; + case "expandNeighborhood": + if (!selectedNodeId) { + return; + } + setCollapsedNeighborhoodNodeIds((current) => current.filter((nodeId) => nodeId !== selectedNodeId)); + return; case "toggleEffect": setEffectToggle(action.effect, (current) => !current); return; @@ -1099,7 +1205,7 @@ export function GraphWorkspace() { setActiveDockPanelId((previous) => (previous === action.panelId ? null : previous)); return; } - }, [focusNode, setEffectToggle]); + }, [focusNode, selectedNodeId, setEffectToggle]); const diagnosticsSnapshot = useMemo(() => { if (!GRAPH_THEME.effects.diagnostics.enabledInDev || !graphDiagnosticsState) { @@ -1139,9 +1245,11 @@ export function GraphWorkspace() { getEffectsState: () => effectsState, getDiagnosticsSnapshot: () => diagnosticsSnapshot, getAnalyticsSnapshot: () => graphAnalyticsState, + getDisplayState: () => displayState, isPanelOpen: (panelId: string) => Boolean(pluginPanelState[panelId]), dispatchAction: handlePluginAction, }), [ + displayState, graphAnalyticsState, diagnosticsSnapshot, effectsState, @@ -1267,17 +1375,10 @@ export function GraphWorkspace() { const coreToolbarGroups = useMemo(() => { const groups: GraphToolbarGroup[] = []; - if (selectedNodeId) { + if (hasGraphContent) { groups.push({ id: "view-mode", items: [ - { - id: "view-focused", - label: "Focused", - title: "Inspect the selected node in a focused local graph", - active: viewMode === "focused", - onClick: () => setViewMode("focused"), - }, { id: "view-full", label: "Full Graph", @@ -1285,6 +1386,46 @@ export function GraphWorkspace() { active: viewMode === "full", onClick: () => setViewMode("full"), }, + { + id: "view-grouped", + label: "Grouped View", + title: displayState.groupedViewAvailable + ? "Compress dense structure into detected communities" + : "Grouped view is unavailable until communities can be detected", + active: viewMode === "grouped", + disabled: !displayState.groupedViewAvailable, + onClick: () => setViewMode("grouped"), + }, + { + id: "view-focused", + label: "Focused", + title: "Inspect the selected node in a focused local graph", + active: viewMode === "focused", + disabled: !selectedNodeId, + onClick: () => setViewMode("focused"), + }, + ], + }); + } + + if (selectedNodeState) { + groups.push({ + id: "local-structure", + items: [ + { + id: "collapse-neighborhood", + label: "Collapse Neighborhood", + title: "Hide lower-priority fanout around the selected node", + disabled: !selectedNodeState.canCollapseNeighborhood || selectedNodeState.isNeighborhoodCollapsed, + onClick: () => handlePluginAction({ type: "collapseNeighborhood" }), + }, + { + id: "expand-neighborhood", + label: "Expand Neighborhood", + title: "Restore the collapsed local neighborhood", + disabled: !selectedNodeState.isNeighborhoodCollapsed, + onClick: () => handlePluginAction({ type: "expandNeighborhood" }), + }, ], }); } @@ -1362,7 +1503,19 @@ export function GraphWorkspace() { } return groups; - }, [isLayoutRunning, pluginToolbarItems, reload, searchQuery, selectedNodeId, showLoadingOverlay, viewMode]); + }, [ + displayState.groupedViewAvailable, + handlePluginAction, + hasGraphContent, + isLayoutRunning, + pluginToolbarItems, + reload, + searchQuery, + selectedNodeId, + selectedNodeState, + showLoadingOverlay, + viewMode, + ]); const sceneAdapterProps = { onNodeSelect: focusNode, @@ -1375,6 +1528,8 @@ export function GraphWorkspace() { temporalState, isLayoutRunning, viewMode, + aggregationEnabled, + collapsedNeighborhoodNodeIds, showFitViewButton: false, pluginOverlays: pluginOverlays.map((overlay) => overlay.element), onRuntimeChange: handleSceneRuntimeChange, @@ -1504,8 +1659,20 @@ export function GraphWorkspace() {
weight {selectedEdgeState.weight.toFixed(2)} - {selectedEdgeState.siblingCount} parallel lane{selectedEdgeState.siblingCount === 1 ? "" : "s"} + {selectedEdgeState.isAggregated ? ( + + {selectedEdgeState.aggregateCount} bundled edge{selectedEdgeState.aggregateCount === 1 ? "" : "s"} + + ) : ( + {selectedEdgeState.siblingCount} parallel lane{selectedEdgeState.siblingCount === 1 ? "" : "s"} + )} {selectedEdgeState.familySize} family member{selectedEdgeState.familySize === 1 ? "" : "s"} + {selectedEdgeState.bundleKind ? ( + {selectedEdgeState.bundleKind} bundle + ) : null} + {selectedEdgeState.dominantEdgeType ? ( + {selectedEdgeState.dominantEdgeType} + ) : null} {selectedEdgeState.provenanceCount > 0 ? ( {selectedEdgeState.provenanceCount} provenance fields ) : null} diff --git a/explorer/src/workspaces/GraphWorkspace/GraphWorkspaceShell.tsx b/explorer/src/workspaces/GraphWorkspace/GraphWorkspaceShell.tsx index 14003ca9..0af7d5f1 100644 --- a/explorer/src/workspaces/GraphWorkspace/GraphWorkspaceShell.tsx +++ b/explorer/src/workspaces/GraphWorkspace/GraphWorkspaceShell.tsx @@ -139,6 +139,10 @@ function toSelectedNodeState(node: ApiNode, neighborCount: number, fallbackColor valid_until: node.valid_until ?? null, properties: node.properties ?? {}, neighborCount, + visibleNeighborCount: neighborCount, + collapsedNeighborCount: 0, + isNeighborhoodCollapsed: false, + canCollapseNeighborhood: neighborCount > 8, }; } @@ -427,6 +431,10 @@ export function GraphWorkspaceShell() { valid_until: null, properties: searchNode.properties ?? {}, neighborCount: 0, + visibleNeighborCount: 0, + collapsedNeighborCount: 0, + isNeighborhoodCollapsed: false, + canCollapseNeighborhood: false, } : null; }, [neighborCountMap, searchResults, selectedNodeId, selectedNodeState, snapshot]); diff --git a/explorer/src/workspaces/GraphWorkspace/behaviors/viewModeSwitchBehavior.ts b/explorer/src/workspaces/GraphWorkspace/behaviors/viewModeSwitchBehavior.ts index b6fd493c..c7d30335 100644 --- a/explorer/src/workspaces/GraphWorkspace/behaviors/viewModeSwitchBehavior.ts +++ b/explorer/src/workspaces/GraphWorkspace/behaviors/viewModeSwitchBehavior.ts @@ -1,7 +1,8 @@ import type { GraphBehavior } from "./types"; +import type { GraphViewMode } from "../types"; export function createViewModeSwitchBehavior(): GraphBehavior { - let lastViewMode: "focused" | "full" | null = null; + let lastViewMode: GraphViewMode | null = null; return { id: "view-mode-switch", diff --git a/explorer/src/workspaces/GraphWorkspace/graphSceneState.ts b/explorer/src/workspaces/GraphWorkspace/graphSceneState.ts index a4d066a0..8a7bc15f 100644 --- a/explorer/src/workspaces/GraphWorkspace/graphSceneState.ts +++ b/explorer/src/workspaces/GraphWorkspace/graphSceneState.ts @@ -17,12 +17,24 @@ import { withAlpha, zoomTierAtLeast, } from "./graphTheme"; -import type { GraphInteractionState, GraphViewMode } from "./types"; +import { computeGraphAnalyticsBase } from "./graphAnalytics"; +import type { GraphDisplayStateSnapshot, GraphInteractionState, GraphViewMode } from "./types"; const MAX_FOCUS_NEIGHBORS = GRAPH_THEME.focus.maxNeighbors; const FOCUS_RING_CAPACITY = GRAPH_THEME.focus.ringCapacity; const FOCUS_RING_GAP = GRAPH_THEME.focus.ringGap; const FOCUS_PRIMARY_LABELS = GRAPH_THEME.focus.primaryLabels; +const COLLAPSE_VISIBLE_NEIGHBORS = 8; +const GROUP_SAMPLE_MEMBERS = 8; +const AGGREGATED_EDGE_PREFIX = "__agg__:"; +const COMMUNITY_NODE_PREFIX = "__community__:"; + +type GraphRef = typeof graph | Graph; + +export type GraphDisplayResult = { + graph: GraphRef; + state: GraphDisplayStateSnapshot; +}; function getOverviewPresenceBoost(cameraRatio: number) { return clamp(0, Math.log2(Math.max(cameraRatio, 1)) / 1.85, 1); @@ -65,7 +77,7 @@ export type ResolvedEdgeStyle = { }; function forEachDirectedEdgeBetween( - graphRef: typeof graph | Graph, + graphRef: GraphRef, source: string, target: string, callback: (edgeId: string, attrs: EdgeAttributes) => void, @@ -76,7 +88,7 @@ function forEachDirectedEdgeBetween( } function collectDirectedEdgeIdsBetween( - graphRef: typeof graph | Graph, + graphRef: GraphRef, source: string, target: string, ): string[] { @@ -87,13 +99,52 @@ function collectDirectedEdgeIdsBetween( return edgeIds; } +function isAggregatedEdgeAttributes(attrs: EdgeAttributes | undefined): boolean { + return Boolean(attrs?.isAggregated || (attrs?.rawEdgeIds?.length ?? 0) > 1); +} + +function collectRawEdgeIds(attrs: EdgeAttributes | undefined, fallbackEdgeId?: string): string[] { + const rawEdgeIds = attrs?.rawEdgeIds?.map((edgeId) => String(edgeId)).filter(Boolean) ?? []; + if (rawEdgeIds.length > 0) { + return rawEdgeIds; + } + return fallbackEdgeId ? [String(fallbackEdgeId)] : []; +} + +function createEmptyDisplayState( + selectedNodeId: string, + aggregationEnabled: boolean, +): GraphDisplayStateSnapshot { + return { + aggregationEnabled, + groupedViewAvailable: false, + selectedRootNodeId: selectedNodeId || null, + selectedVisibleNeighborIds: [], + selectedCollapsedNeighborIds: [], + }; +} + export function buildPathEdgeSet( - graphRef: typeof graph | Graph, + graphRef: GraphRef, path: string[], pathEdgeIds: string[] = [], ): Set { if (pathEdgeIds.length > 0) { - return new Set(pathEdgeIds.filter((edgeId) => graphRef.hasEdge(edgeId))); + const requested = new Set(pathEdgeIds.map((edgeId) => String(edgeId))); + const matched = new Set(); + graphRef.forEachEdge((edgeId, attrs) => { + const stableEdgeId = String(edgeId); + if (requested.has(stableEdgeId)) { + matched.add(stableEdgeId); + return; + } + + const rawEdgeIds = collectRawEdgeIds(attrs as EdgeAttributes, stableEdgeId); + if (rawEdgeIds.some((rawEdgeId) => requested.has(rawEdgeId))) { + matched.add(stableEdgeId); + } + }); + return matched; } const edgeIds = new Set(); @@ -104,7 +155,7 @@ export function buildPathEdgeSet( } export function buildEdgeEndpointSet( - graphRef: typeof graph | Graph, + graphRef: GraphRef, ...edgeIds: Array ): Set { const nodeIds = new Set(); @@ -123,7 +174,7 @@ export function buildEdgeEndpointSet( } function collectFocusEdgeIds( - graphRef: typeof graph | Graph, + graphRef: GraphRef, nodeIds: Set, ): Set { const edgeIds = new Set(); @@ -187,10 +238,24 @@ function collectImpactedEdgeKeys( } function resolveDisplayEdgeIds( - graphRef: typeof graph | Graph, + graphRef: GraphRef, stableEdgeIds: Set, ): string[] { - return Array.from(stableEdgeIds).filter((edgeId) => graphRef.hasEdge(edgeId)); + const displayEdgeIds = new Set(); + graphRef.forEachEdge((edgeId, attrs) => { + const stableEdgeId = String(edgeId); + if (stableEdgeIds.has(stableEdgeId)) { + displayEdgeIds.add(stableEdgeId); + return; + } + + const rawEdgeIds = collectRawEdgeIds(attrs as EdgeAttributes, stableEdgeId); + if (rawEdgeIds.some((rawEdgeId) => stableEdgeIds.has(rawEdgeId))) { + displayEdgeIds.add(stableEdgeId); + } + }); + + return Array.from(displayEdgeIds); } export function collectInteractionRefreshTargets( @@ -252,7 +317,7 @@ export function buildFocusSet(nodeId: string): Set { } export function isEdgeInteractable( - graphRef: typeof graph | Graph, + graphRef: GraphRef, interactionState: GraphInteractionState, edgeId: string, source: string, @@ -264,6 +329,10 @@ export function isEdgeInteractable( return true; } + if (attrs.isAggregated && (interactionState.viewMode === "grouped" || interactionState.zoomTier === "inspection")) { + return true; + } + const primaryNodeId = interactionState.selectedNodeId; if (primaryNodeId && graphRef.hasNode(primaryNodeId)) { if (source === primaryNodeId || target === primaryNodeId) { @@ -507,7 +576,7 @@ export function resolveNodeVisualState( return "neighbor"; } if (hoveredNodeId || selectedNodeId || selectedEdgeId || pathNodeIds.size > 0) { - if (zoomTier === "overview") { + if (zoomTier !== "inspection") { return "default"; } return "muted"; @@ -839,10 +908,315 @@ export function resolveEdgeElementStyle( }; } +function addNodeIfMissing(targetGraph: GraphRef, nodeId: string, attrs: NodeAttributes) { + if (!targetGraph.hasNode(nodeId)) { + targetGraph.addNode(nodeId, attrs); + } +} + +function buildCollapsedNeighborhoodState( + nodeId: string, + activePath: string[], +): Pick { + if (!nodeId || !graph.hasNode(nodeId)) { + return { + selectedVisibleNeighborIds: [], + selectedCollapsedNeighborIds: [], + }; + } + + const rankedNeighbors = rankNeighbors(nodeId); + const forcedVisible = new Set(activePath.filter((candidateId) => candidateId !== nodeId && graph.hasNode(candidateId))); + const visible = new Set(); + rankedNeighbors.forEach((neighborId, index) => { + if (index < COLLAPSE_VISIBLE_NEIGHBORS || forcedVisible.has(neighborId)) { + visible.add(neighborId); + } + }); + + return { + selectedVisibleNeighborIds: Array.from(visible), + selectedCollapsedNeighborIds: rankedNeighbors.filter((neighborId) => !visible.has(neighborId)), + }; +} + +function createCollapsedNeighborhoodGraph( + nodeId: string, + activePath: string[], +): Graph { + const collapsedState = buildCollapsedNeighborhoodState(nodeId, activePath); + const hiddenNeighbors = new Set(collapsedState.selectedCollapsedNeighborIds); + if (hiddenNeighbors.size === 0) { + return graph.copy() as Graph; + } + + const collapsedGraph = graph.copy() as Graph; + hiddenNeighbors.forEach((neighborId) => { + if (!collapsedGraph.hasNode(neighborId)) { + return; + } + + const incidentEdges = collapsedGraph.edges(neighborId).map((edgeId) => String(edgeId)); + incidentEdges.forEach((edgeId) => { + if (!collapsedGraph.hasEdge(edgeId)) { + return; + } + const [sourceId, targetId] = collapsedGraph.extremities(edgeId); + const touchesSelectedPair = + (sourceId === nodeId && targetId === neighborId) + || (sourceId === neighborId && targetId === nodeId); + if (touchesSelectedPair) { + collapsedGraph.dropEdge(edgeId); + } + }); + + if (collapsedGraph.hasNode(neighborId) && collapsedGraph.degree(neighborId) === 0) { + collapsedGraph.dropNode(neighborId); + } + }); + + return collapsedGraph; +} + +function aggregateDisplayGraph(graphRef: GraphRef): Graph { + const aggregated = new Graph({ + type: "directed", + multi: true, + allowSelfLoops: false, + }); + + graphRef.forEachNode((nodeId, attrs) => { + aggregated.addNode(nodeId, { ...(attrs as NodeAttributes) }); + }); + + const groupedEdges = new Map>(); + graphRef.forEachEdge((edgeId, attrs, sourceId, targetId) => { + const key = `${sourceId}→${targetId}`; + const bucket = groupedEdges.get(key) ?? []; + bucket.push({ edgeId: String(edgeId), attrs: attrs as EdgeAttributes }); + groupedEdges.set(key, bucket); + }); + + groupedEdges.forEach((entries, key) => { + const [sourceId, targetId] = key.split("→"); + if (entries.length === 1) { + const [{ edgeId, attrs }] = entries; + aggregated.mergeDirectedEdgeWithKey(edgeId, sourceId, targetId, { + ...attrs, + rawEdgeIds: collectRawEdgeIds(attrs, edgeId), + isAggregated: isAggregatedEdgeAttributes(attrs), + aggregateCount: attrs.aggregateCount ?? collectRawEdgeIds(attrs, edgeId).length, + dominantEdgeType: attrs.dominantEdgeType ?? attrs.edgeType, + representativeWeight: attrs.representativeWeight ?? Number(attrs.weight ?? 1), + }); + return; + } + + const sorted = entries + .slice() + .sort((left, right) => { + const weightDelta = Number(right.attrs.weight ?? 0) - Number(left.attrs.weight ?? 0); + if (weightDelta !== 0) { + return weightDelta; + } + const priorityDelta = Number(right.attrs.visualPriority ?? 0) - Number(left.attrs.visualPriority ?? 0); + if (priorityDelta !== 0) { + return priorityDelta; + } + return left.edgeId.localeCompare(right.edgeId); + }); + const representative = sorted[0]; + const rawEdgeIds = entries.flatMap(({ edgeId, attrs }) => collectRawEdgeIds(attrs, edgeId)); + const typeCounts = new Map(); + entries.forEach(({ attrs }) => { + const edgeType = String(attrs.edgeType ?? "related_to"); + typeCounts.set(edgeType, (typeCounts.get(edgeType) ?? 0) + 1); + }); + const dominantEdgeType = [...typeCounts.entries()].sort((left, right) => right[1] - left[1])[0]?.[0] ?? representative.attrs.edgeType ?? "related_to"; + const reverseKey = `${targetId}→${sourceId}`; + const isBidirectionalBundle = groupedEdges.has(reverseKey); + const syntheticEdgeId = `${AGGREGATED_EDGE_PREFIX}${sourceId}::${targetId}`; + + aggregated.mergeDirectedEdgeWithKey(syntheticEdgeId, sourceId, targetId, { + ...representative.attrs, + edgeId: syntheticEdgeId, + familyId: syntheticEdgeId, + sourceId, + targetId, + rawEdgeIds, + isAggregated: true, + aggregateCount: rawEdgeIds.length, + dominantEdgeType: String(dominantEdgeType), + representativeWeight: Number(representative.attrs.weight ?? 1), + weight: Number(representative.attrs.weight ?? 1), + edgeType: String(representative.attrs.edgeType ?? dominantEdgeType ?? "related_to"), + parallelCount: rawEdgeIds.length, + familySize: rawEdgeIds.length, + bundleKind: isBidirectionalBundle ? "bidirectional" : "parallel", + isBidirectional: isBidirectionalBundle, + }); + }); + + return aggregated; +} + +function buildCommunityGroupedGraph(): GraphDisplayResult { + const base = computeGraphAnalyticsBase(graph, { + computeCommunities: true, + computeCentrality: true, + }); + const state = createEmptyDisplayState("", true); + state.groupedViewAvailable = base.communitiesByNode.size > 0; + if (base.communitiesByNode.size === 0) { + return { graph: aggregateDisplayGraph(graph), state }; + } + + const grouped = new Graph({ + type: "directed", + multi: true, + allowSelfLoops: false, + }); + const communityMembers = new Map(); + graph.forEachNode((nodeId) => { + const communityId = base.communitiesByNode.get(nodeId); + if (communityId === undefined) { + return; + } + const bucket = communityMembers.get(communityId) ?? []; + bucket.push(nodeId); + communityMembers.set(communityId, bucket); + }); + + communityMembers.forEach((memberIds, communityId) => { + const rankedMembers = memberIds + .slice() + .sort((left, right) => { + const leftScore = base.centralityByNode.get(left)?.score ?? 0; + const rightScore = base.centralityByNode.get(right)?.score ?? 0; + if (rightScore !== leftScore) { + return rightScore - leftScore; + } + return left.localeCompare(right); + }); + const anchorNodeId = rankedMembers[0] ?? null; + const anchorAttrs = anchorNodeId ? (graph.getNodeAttributes(anchorNodeId) as NodeAttributes) : null; + const semanticCounts = new Map(); + memberIds.forEach((memberId) => { + const memberAttrs = graph.getNodeAttributes(memberId) as NodeAttributes; + const semanticGroup = String(memberAttrs.semanticGroup || memberAttrs.nodeType || "entity"); + semanticCounts.set(semanticGroup, (semanticCounts.get(semanticGroup) ?? 0) + 1); + }); + const dominantSemanticGroup = [...semanticCounts.entries()].sort((left, right) => right[1] - left[1])[0]?.[0] ?? "entity"; + const color = anchorAttrs?.baseColor || anchorAttrs?.color || GRAPH_THEME.palette.semantic[communityId % GRAPH_THEME.palette.semantic.length]; + const communityNodeId = `${COMMUNITY_NODE_PREFIX}${communityId}`; + addNodeIfMissing(grouped, communityNodeId, { + label: anchorAttrs?.label || `Community ${communityId}`, + content: anchorAttrs?.content || anchorAttrs?.label || `Community ${communityId}`, + x: anchorAttrs?.x ?? 0, + y: anchorAttrs?.y ?? 0, + size: Math.max(14, 8 + Math.log2(memberIds.length + 1) * 5), + baseSize: Math.max(14, 8 + Math.log2(memberIds.length + 1) * 5), + color, + baseColor: color, + mutedColor: withAlpha(color, 0.32), + glowColor: withAlpha(color, 0.22), + nodeType: "community", + semanticGroup: dominantSemanticGroup, + properties: { + __communityGroup: { + communityId: String(communityId), + memberCount: memberIds.length, + memberNodeIds: memberIds, + sampleNodeIds: rankedMembers.slice(0, GROUP_SAMPLE_MEMBERS), + anchorNodeId, + anchorLabel: anchorAttrs?.label || anchorNodeId || `Community ${communityId}`, + dominantSemanticGroup, + color, + }, + }, + isCommunityGroup: true, + communityId: String(communityId), + memberCount: memberIds.length, + anchorNodeId, + labelPriority: Math.max(2, Math.log2(memberIds.length + 1)), + visualPriority: Math.max(1, Math.log2(memberIds.length + 1)), + }); + }); + + const groupedEdges = new Map; + }>(); + + graph.forEachEdge((edgeId, attrs, sourceId, targetId) => { + const sourceCommunity = base.communitiesByNode.get(sourceId); + const targetCommunity = base.communitiesByNode.get(targetId); + if (sourceCommunity === undefined || targetCommunity === undefined || sourceCommunity === targetCommunity) { + return; + } + + const groupedSourceId = `${COMMUNITY_NODE_PREFIX}${sourceCommunity}`; + const groupedTargetId = `${COMMUNITY_NODE_PREFIX}${targetCommunity}`; + const key = `${groupedSourceId}→${groupedTargetId}`; + const bucket = groupedEdges.get(key) ?? { + sourceId: groupedSourceId, + targetId: groupedTargetId, + rawEdgeIds: [], + weight: 0, + typeCounts: new Map(), + }; + bucket.rawEdgeIds.push(String(edgeId)); + bucket.weight = Math.max(bucket.weight, Number((attrs as EdgeAttributes).weight ?? 1)); + const edgeType = String((attrs as EdgeAttributes).edgeType ?? "related_to"); + bucket.typeCounts.set(edgeType, (bucket.typeCounts.get(edgeType) ?? 0) + 1); + groupedEdges.set(key, bucket); + }); + + groupedEdges.forEach((bundle, key) => { + const dominantEdgeType = [...bundle.typeCounts.entries()].sort((left, right) => right[1] - left[1])[0]?.[0] ?? "related_to"; + const reverseKey = `${bundle.targetId}→${bundle.sourceId}`; + const syntheticEdgeId = `${AGGREGATED_EDGE_PREFIX}${key}`; + grouped.mergeDirectedEdgeWithKey(syntheticEdgeId, bundle.sourceId, bundle.targetId, { + edgeId: syntheticEdgeId, + familyId: syntheticEdgeId, + sourceId: bundle.sourceId, + targetId: bundle.targetId, + edgeType: dominantEdgeType, + dominantEdgeType, + weight: bundle.weight, + representativeWeight: bundle.weight, + properties: {}, + rawEdgeIds: bundle.rawEdgeIds, + isAggregated: true, + aggregateCount: bundle.rawEdgeIds.length, + familySize: bundle.rawEdgeIds.length, + parallelCount: bundle.rawEdgeIds.length, + isBidirectional: groupedEdges.has(reverseKey), + bundleKind: "community", + visualPriority: 2, + }); + }); + + return { + graph: grouped, + state: { + aggregationEnabled: true, + groupedViewAvailable: true, + selectedRootNodeId: null, + selectedVisibleNeighborIds: [], + selectedCollapsedNeighborIds: [], + }, + }; +} + export function createFocusedGraph( nodeId: string, activePath: string[], activePathEdgeIds: string[] = [], + collapseNeighborhood = false, ): Graph { const focused = new Graph({ type: "directed", @@ -851,8 +1225,15 @@ export function createFocusedGraph( }); const rankedNeighbors = rankNeighbors(nodeId).slice(0, MAX_FOCUS_NEIGHBORS); - const focusIds = new Set([nodeId, ...rankedNeighbors]); - const labelledNeighborIds = new Set(rankedNeighbors.slice(0, FOCUS_PRIMARY_LABELS)); + const collapsedState = collapseNeighborhood + ? buildCollapsedNeighborhoodState(nodeId, activePath) + : { + selectedVisibleNeighborIds: rankedNeighbors, + selectedCollapsedNeighborIds: [], + }; + const visibleNeighborIds = rankedNeighbors.filter((neighborId) => collapsedState.selectedVisibleNeighborIds.includes(neighborId)); + const focusIds = new Set([nodeId, ...visibleNeighborIds]); + const labelledNeighborIds = new Set(visibleNeighborIds.slice(0, FOCUS_PRIMARY_LABELS)); const pathNodeIds = new Set(activePath); const pathEdgeIds = buildPathEdgeSet(graph, activePath, activePathEdgeIds); @@ -875,7 +1256,7 @@ export function createFocusedGraph( label: selectedState.label, }); - rankedNeighbors.forEach((neighborId, index) => { + visibleNeighborIds.forEach((neighborId, index) => { const baseAttrs = graph.getNodeAttributes(neighborId) as NodeAttributes; const ring = Math.floor(index / FOCUS_RING_CAPACITY); const ringIndex = index % FOCUS_RING_CAPACITY; @@ -940,7 +1321,7 @@ export function createFocusedGraph( } } - return focused; + return aggregateDisplayGraph(focused); } export function resolveDisplayGraph( @@ -948,9 +1329,64 @@ export function resolveDisplayGraph( activePath: string[], activePathEdgeIds: string[], viewMode: GraphViewMode, -) { + options?: { + aggregationEnabled?: boolean; + collapsedNeighborhoodNodeIds?: Iterable; + }, +): GraphDisplayResult { + const aggregationEnabled = options?.aggregationEnabled ?? true; + const collapsedNeighborhoodNodeIds = new Set( + Array.from(options?.collapsedNeighborhoodNodeIds ?? []).filter((nodeId) => typeof nodeId === "string"), + ); + const displayState = createEmptyDisplayState(selectedNodeId, aggregationEnabled); + displayState.groupedViewAvailable = computeGraphAnalyticsBase(graph, { + computeCommunities: true, + computeCentrality: false, + }).communitiesByNode.size > 0; const isFocusedView = viewMode === "focused" && Boolean(selectedNodeId) && graph.hasNode(selectedNodeId); - return isFocusedView && selectedNodeId ? createFocusedGraph(selectedNodeId, activePath, activePathEdgeIds) : graph; + const isGroupedView = viewMode === "grouped"; + const shouldCollapseNeighborhood = Boolean(selectedNodeId && collapsedNeighborhoodNodeIds.has(selectedNodeId)); + + if (selectedNodeId && graph.hasNode(selectedNodeId)) { + const collapsedState = shouldCollapseNeighborhood + ? buildCollapsedNeighborhoodState(selectedNodeId, activePath) + : { + selectedVisibleNeighborIds: rankNeighbors(selectedNodeId), + selectedCollapsedNeighborIds: [], + }; + displayState.selectedRootNodeId = selectedNodeId; + displayState.selectedVisibleNeighborIds = collapsedState.selectedVisibleNeighborIds; + displayState.selectedCollapsedNeighborIds = collapsedState.selectedCollapsedNeighborIds; + } + + if (isGroupedView) { + const grouped = buildCommunityGroupedGraph(); + return { + graph: grouped.graph, + state: { + ...grouped.state, + selectedRootNodeId: displayState.selectedRootNodeId, + selectedVisibleNeighborIds: displayState.selectedVisibleNeighborIds, + selectedCollapsedNeighborIds: displayState.selectedCollapsedNeighborIds, + }, + }; + } + + if (isFocusedView && selectedNodeId) { + return { + graph: createFocusedGraph(selectedNodeId, activePath, activePathEdgeIds, shouldCollapseNeighborhood), + state: displayState, + }; + } + + const baseGraph = shouldCollapseNeighborhood && selectedNodeId + ? createCollapsedNeighborhoodGraph(selectedNodeId, activePath) + : graph; + + return { + graph: aggregationEnabled ? aggregateDisplayGraph(baseGraph) : baseGraph, + state: displayState, + }; } export function createInteractionState( diff --git a/explorer/src/workspaces/GraphWorkspace/graphTheme.ts b/explorer/src/workspaces/GraphWorkspace/graphTheme.ts index ea033613..6d6e8968 100644 --- a/explorer/src/workspaces/GraphWorkspace/graphTheme.ts +++ b/explorer/src/workspaces/GraphWorkspace/graphTheme.ts @@ -9,6 +9,7 @@ export type GraphBadgeKind = "inferred" | "temporal" | "provenance"; type GraphNodeColorMode = "base" | "selected" | "hovered" | "path" | "muted"; type GraphEdgeColorMode = "overview" | "backbone" | "structure" | "inspection" | "hover" | "path" | "focus" | "muted"; +const IS_DEV = Boolean((import.meta as { env?: { DEV?: boolean } }).env?.DEV); export interface GraphTheme { palette: { @@ -305,10 +306,10 @@ export const GRAPH_THEME: GraphTheme = { zoomTiers: { overview: { maxRatio: Number.POSITIVE_INFINITY, - nodeScale: 0.88, - labelThreshold: 0.92, - labelBudget: 28, - edgePriorityThreshold: 0.55, + nodeScale: 0.72, + labelThreshold: 0.995, + labelBudget: 4, + edgePriorityThreshold: 0.72, arrowPriorityThreshold: Number.POSITIVE_INFINITY, edgeSizeScale: 0.62, showBadges: false, @@ -317,21 +318,21 @@ export const GRAPH_THEME: GraphTheme = { }, structure: { maxRatio: 1.2, - nodeScale: 1.02, - labelThreshold: 0.82, - labelBudget: 60, - edgePriorityThreshold: 0.3, - arrowPriorityThreshold: 0.65, - edgeSizeScale: 1.05, - showBadges: true, - showCurves: true, - showContextualArrows: true, + nodeScale: 0.94, + labelThreshold: 0.93, + labelBudget: 18, + edgePriorityThreshold: 0.4, + arrowPriorityThreshold: 0.75, + edgeSizeScale: 0.92, + showBadges: false, + showCurves: false, + showContextualArrows: false, }, inspection: { maxRatio: 0.5, - nodeScale: 1.08, - labelThreshold: 0.6, - labelBudget: 120, + nodeScale: 1, + labelThreshold: 0.8, + labelBudget: 40, edgePriorityThreshold: 0, arrowPriorityThreshold: 0.45, edgeSizeScale: 1.18, @@ -341,7 +342,7 @@ export const GRAPH_THEME: GraphTheme = { }, }, labels: { - forceVisibleStates: ["hovered", "selected", "neighbor", "path"], + forceVisibleStates: ["hovered", "selected", "path"], policies: { none: { minZoomTier: "inspection" }, priority: { minZoomTier: "overview" }, @@ -391,26 +392,26 @@ export const GRAPH_THEME: GraphTheme = { }, nodes: { backgroundScale: 0.52, - mutedAlpha: 0.08, + mutedAlpha: 0.16, strokeHierarchy: { overview: { base: 0.05, emphasis: 0.34, muted: 0.02 }, structure: { base: 1.05, emphasis: 1.45, muted: 0.55 }, inspection: { base: 1.2, emphasis: 1.7, muted: 0.6 }, }, states: { - default: { color: "base", sizeMultiplier: 0.92, minSize: 3.5, forceLabel: false, zIndex: 0, borderBoost: -0.18 }, - hovered: { color: "hovered", sizeMultiplier: 1.28, minSize: 13.5, forceLabel: true, zIndex: 4, borderBoost: 0.28 }, - selected: { color: "selected", sizeMultiplier: 1.14, minSize: 11.5, forceLabel: true, zIndex: 3, borderBoost: 0.24 }, - neighbor: { color: "base", sizeMultiplier: 0.96, minSize: 5.5, forceLabel: true, zIndex: 2, borderBoost: 0.04 }, - path: { color: "path", sizeMultiplier: 1.08, minSize: 7.0, forceLabel: true, zIndex: 2, borderBoost: 0.12 }, - inactive: { color: "muted", sizeMultiplier: 0.48, minSize: 1.8, forceLabel: false, zIndex: 0, borderBoost: -0.28 }, - muted: { color: "muted", sizeMultiplier: 0.48, minSize: 1.8, forceLabel: false, zIndex: 0, borderBoost: -0.28 }, + default: { color: "base", sizeMultiplier: 0.72, minSize: 0.68, forceLabel: false, zIndex: 0, borderBoost: -0.42 }, + hovered: { color: "hovered", sizeMultiplier: 1.1, minSize: 10.8, forceLabel: true, zIndex: 4, borderBoost: 0.18 }, + selected: { color: "selected", sizeMultiplier: 1.02, minSize: 9.4, forceLabel: true, zIndex: 3, borderBoost: 0.18 }, + neighbor: { color: "base", sizeMultiplier: 0.78, minSize: 4.2, forceLabel: false, zIndex: 2, borderBoost: -0.12 }, + path: { color: "path", sizeMultiplier: 0.97, minSize: 5.8, forceLabel: true, zIndex: 2, borderBoost: 0.06 }, + inactive: { color: "muted", sizeMultiplier: 0.52, minSize: 0.58, forceLabel: false, zIndex: 0, borderBoost: -0.42 }, + muted: { color: "muted", sizeMultiplier: 0.52, minSize: 0.58, forceLabel: false, zIndex: 0, borderBoost: -0.42 }, }, variants: { default: { sizeMultiplier: 1, borderBoost: 0, haloBoost: 0, badgeVisibleFrom: "inspection" }, - temporal: { sizeMultiplier: 1.02, borderBoost: 0.12, haloBoost: 0.1, badgeKind: "temporal", badgeVisibleFrom: "structure" }, - inferred: { sizeMultiplier: 1.05, borderBoost: 0.16, haloBoost: 0.14, badgeKind: "inferred", badgeVisibleFrom: "structure" }, - provenance: { sizeMultiplier: 1.03, borderBoost: 0.14, haloBoost: 0.12, badgeKind: "provenance", badgeVisibleFrom: "structure" }, + temporal: { sizeMultiplier: 1.02, borderBoost: 0.12, haloBoost: 0.1, badgeKind: "temporal", badgeVisibleFrom: "inspection" }, + inferred: { sizeMultiplier: 1.05, borderBoost: 0.16, haloBoost: 0.14, badgeKind: "inferred", badgeVisibleFrom: "inspection" }, + provenance: { sizeMultiplier: 1.03, borderBoost: 0.14, haloBoost: 0.12, badgeKind: "provenance", badgeVisibleFrom: "inspection" }, selected: { sizeMultiplier: 1.06, borderBoost: 0.22, haloBoost: 0.16, badgeVisibleFrom: "overview" }, }, selectedRing: { @@ -530,7 +531,7 @@ export const GRAPH_THEME: GraphTheme = { maxGroups: 8, }, diagnostics: { - enabledInDev: import.meta.env.DEV, + enabledInDev: IS_DEV, }, }, }; diff --git a/explorer/src/workspaces/GraphWorkspace/plugins/neighborhoodPanelPlugin.tsx b/explorer/src/workspaces/GraphWorkspace/plugins/neighborhoodPanelPlugin.tsx index 2fb45de8..9cc2d323 100644 --- a/explorer/src/workspaces/GraphWorkspace/plugins/neighborhoodPanelPlugin.tsx +++ b/explorer/src/workspaces/GraphWorkspace/plugins/neighborhoodPanelPlugin.tsx @@ -42,6 +42,7 @@ export const neighborhoodPanelPlugin: GraphPlugin = { } const selected = context.getSelectedNodeState(); + const displayState = context.getDisplayState(); if (!selected) { return { id: NEIGHBORHOOD_PANEL_ID, @@ -82,6 +83,11 @@ export const neighborhoodPanelPlugin: GraphPlugin = { return left.label.localeCompare(right.label); }) .slice(0, MAX_NEIGHBORS); + const hiddenNeighborCount = displayState.selectedCollapsedNeighborIds.length; + const aggregatedEdgeCount = context.displayGraph + .edges() + .map((edgeId) => context.displayGraph.getEdgeAttributes(edgeId) as { isAggregated?: boolean }) + .filter((attrs) => attrs.isAggregated).length; return { id: NEIGHBORHOOD_PANEL_ID, @@ -97,6 +103,34 @@ export const neighborhoodPanelPlugin: GraphPlugin = {
{selected.neighborCount.toLocaleString()} direct neighbors in the full graph
+
+ + +
+ {hiddenNeighborCount > 0 ? ( +
+ {hiddenNeighborCount.toLocaleString()} lower-priority neighbors are collapsed in the current view. +
+ ) : null} + {aggregatedEdgeCount > 0 ? ( +
+ {aggregatedEdgeCount.toLocaleString()} aggregated structural bundle{aggregatedEdgeCount === 1 ? "" : "s"} visible. +
+ ) : null} {neighbors.length ? (
{neighbors.map((neighbor) => ( @@ -159,6 +193,16 @@ const neighborButtonStyle: CSSProperties = { cursor: "pointer", }; +const controlButtonStyle: CSSProperties = { + padding: "7px 10px", + background: "rgba(255,255,255,0.03)", + border: "1px solid rgba(255,255,255,0.08)", + borderRadius: 10, + color: "#dce7f4", + cursor: "pointer", + fontSize: 12, +}; + const swatchStyle: CSSProperties = { width: 10, height: 10, diff --git a/explorer/src/workspaces/GraphWorkspace/plugins/types.ts b/explorer/src/workspaces/GraphWorkspace/plugins/types.ts index 8814512f..1292ee69 100644 --- a/explorer/src/workspaces/GraphWorkspace/plugins/types.ts +++ b/explorer/src/workspaces/GraphWorkspace/plugins/types.ts @@ -6,6 +6,7 @@ import type { GraphTheme } from "../graphTheme"; import type { GraphSceneRuntime } from "../scene"; import type { GraphAnalyticsSnapshot, + GraphDisplayStateSnapshot, GraphDiagnosticsSnapshot, GraphEffectsState, GraphEffectToggle, @@ -31,6 +32,8 @@ export type GraphPluginActionRequest = | { type: "focusNode"; nodeId: string } | { type: "selectNode"; nodeId: string } | { type: "setViewMode"; viewMode: GraphViewMode } + | { type: "collapseNeighborhood" } + | { type: "expandNeighborhood" } | { type: "toggleEffect"; effect: GraphEffectToggle } | { type: "setEffect"; effect: GraphEffectToggle; enabled: boolean } | { type: "togglePanel"; panelId: string } @@ -77,6 +80,7 @@ export interface GraphPluginContext { getEffectsState: () => GraphEffectsState; getDiagnosticsSnapshot: () => GraphDiagnosticsSnapshot | null; getAnalyticsSnapshot: () => GraphAnalyticsSnapshot | null; + getDisplayState: () => GraphDisplayStateSnapshot; isPanelOpen: (panelId: string) => boolean; dispatchAction: (action: GraphPluginActionRequest) => void; } diff --git a/explorer/src/workspaces/GraphWorkspace/scene.ts b/explorer/src/workspaces/GraphWorkspace/scene.ts index 6a099c48..010e24a7 100644 --- a/explorer/src/workspaces/GraphWorkspace/scene.ts +++ b/explorer/src/workspaces/GraphWorkspace/scene.ts @@ -48,6 +48,8 @@ export interface GraphSceneProps extends GraphSceneEventMap { layoutSource?: GraphLayoutSource; onLayoutStatusChange?: (status: GraphLayoutStatus) => void; viewMode: GraphViewMode; + aggregationEnabled?: boolean; + collapsedNeighborhoodNodeIds?: string[]; className?: string; showFitViewButton?: boolean; pluginOverlays?: ReactNode[]; diff --git a/explorer/src/workspaces/GraphWorkspace/types.ts b/explorer/src/workspaces/GraphWorkspace/types.ts index 69b73c12..b679581e 100644 --- a/explorer/src/workspaces/GraphWorkspace/types.ts +++ b/explorer/src/workspaces/GraphWorkspace/types.ts @@ -1,4 +1,4 @@ -export type GraphViewMode = "focused" | "full"; +export type GraphViewMode = "focused" | "full" | "grouped"; export type GraphLayoutSource = "provided" | "carried" | "runtime"; export type GraphLayoutState = "idle" | "bootstrapping" | "running" | "stabilized" | "interactive" | "failed"; export type GraphLoadPhase = @@ -31,6 +31,14 @@ export interface GraphInteractionState { isLayoutRunning: boolean; } +export interface GraphDisplayStateSnapshot { + aggregationEnabled: boolean; + groupedViewAvailable: boolean; + selectedRootNodeId: string | null; + selectedVisibleNeighborIds: string[]; + selectedCollapsedNeighborIds: string[]; +} + export type GraphEffectToggle = | "pathPulseEnabled" | "pathFlowEnabled" @@ -245,6 +253,10 @@ export interface GraphSelectedNodeState { valid_until?: string | null; properties: Record; neighborCount: number; + visibleNeighborCount: number; + collapsedNeighborCount: number; + isNeighborhoodCollapsed: boolean; + canCollapseNeighborhood: boolean; } export interface GraphSelectedEdgeState { @@ -260,6 +272,12 @@ export interface GraphSelectedEdgeState { provenanceCount: number; familySize: number; siblingCount: number; + isAggregated: boolean; + aggregateCount: number; + rawEdgeIds: string[]; + bundleKind: "parallel" | "bidirectional" | "community" | null; + dominantEdgeType: string | null; + representativeWeight: number; } export interface GraphStageHandle { diff --git a/explorer/tests/graphSceneState.display.test.ts b/explorer/tests/graphSceneState.display.test.ts new file mode 100644 index 00000000..b4865026 --- /dev/null +++ b/explorer/tests/graphSceneState.display.test.ts @@ -0,0 +1,137 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { + batchMergeEdges, + batchMergeNodes, + clearGraph, +} from "../src/store/graphStore.ts"; +import { resolveDisplayGraph } from "../src/workspaces/GraphWorkspace/graphSceneState.ts"; + +function addNode(id: string, semanticGroup = "entity") { + batchMergeNodes([ + { + id, + attributes: { + label: id, + content: id, + x: 0, + y: 0, + size: 8, + color: "#63E6FF", + baseColor: "#63E6FF", + nodeType: semanticGroup, + semanticGroup, + properties: {}, + }, + }, + ]); +} + +function addEdge(id: string, source: string, target: string, weight = 1) { + batchMergeEdges([ + { + id, + source, + target, + attributes: { + edgeType: "related_to", + weight, + properties: {}, + }, + }, + ]); +} + +test.beforeEach(() => { + clearGraph(); +}); + +test.after(() => { + clearGraph(); +}); + +test("resolveDisplayGraph bundles parallel edges in full view", () => { + addNode("a"); + addNode("b"); + addEdge("e1", "a", "b", 1); + addEdge("e2", "a", "b", 2); + + const { graph } = resolveDisplayGraph("", [], [], "full", { aggregationEnabled: true }); + assert.equal(graph.size, 1); + + const edgeId = graph.edges()[0]; + const attrs = graph.getEdgeAttributes(edgeId) as { + isAggregated?: boolean; + aggregateCount?: number; + rawEdgeIds?: string[]; + bundleKind?: string; + }; + + assert.equal(attrs.isAggregated, true); + assert.equal(attrs.aggregateCount, 2); + assert.deepEqual(new Set(attrs.rawEdgeIds ?? []), new Set(["e1", "e2"])); + assert.equal(attrs.bundleKind, "parallel"); +}); + +test("resolveDisplayGraph collapse keeps path neighbor visible", () => { + addNode("center"); + for (let index = 0; index < 10; index += 1) { + const neighbor = `n${index}`; + addNode(neighbor); + addEdge(`edge-${index}`, "center", neighbor, 1); + } + + const { state } = resolveDisplayGraph("center", ["center", "n9"], [], "full", { + aggregationEnabled: false, + collapsedNeighborhoodNodeIds: ["center"], + }); + + assert.equal(state.selectedRootNodeId, "center"); + assert.equal(state.selectedVisibleNeighborIds.includes("n9"), true); + assert.equal(state.selectedVisibleNeighborIds.length, 9); + assert.equal(state.selectedCollapsedNeighborIds.length, 1); +}); + +test("resolveDisplayGraph grouped view emits community nodes and edges", () => { + const left = ["a1", "a2", "a3", "a4"]; + const right = ["b1", "b2", "b3", "b4"]; + + [...left, ...right].forEach((nodeId, index) => { + addNode(nodeId, index < left.length ? "left" : "right"); + }); + + let edgeIndex = 0; + for (let i = 0; i < left.length; i += 1) { + for (let j = 0; j < left.length; j += 1) { + if (i !== j) { + addEdge(`l-${edgeIndex++}`, left[i], left[j], 3); + } + } + } + + for (let i = 0; i < right.length; i += 1) { + for (let j = 0; j < right.length; j += 1) { + if (i !== j) { + addEdge(`r-${edgeIndex++}`, right[i], right[j], 3); + } + } + } + + addEdge("bridge-1", "a1", "b1", 0.1); + addEdge("bridge-2", "a2", "b2", 0.1); + + const { graph, state } = resolveDisplayGraph("", [], [], "grouped", { aggregationEnabled: true }); + + assert.equal(state.groupedViewAvailable, true); + + const communityNodes = graph.nodes().filter((nodeId) => nodeId.startsWith("__community__")); + assert.ok(communityNodes.length >= 2); + + const hasCommunityEdge = graph + .edges() + .map((edgeId) => graph.getEdgeAttributes(edgeId) as { bundleKind?: string; isAggregated?: boolean; aggregateCount?: number }) + .some((attrs) => attrs.bundleKind === "community" && attrs.isAggregated === true && Number(attrs.aggregateCount ?? 0) > 0); + + assert.equal(hasCommunityEdge, true); +});