diff --git a/README.md b/README.md index d0c8bb6..8b38cf5 100644 --- a/README.md +++ b/README.md @@ -676,7 +676,7 @@ uv run --group docs mkdocs build --strict The repo includes a PNPM workspace under `js/` with three packages: - `@cyvest/cyvest-js`: TypeScript types, schema validation, and helpers for Cyvest investigations. -- `@cyvest/cyvest-vis`: React components for graph visualization (Cytoscape + ELK/Dagre, depends on `@cyvest/cyvest-js`). +- `@cyvest/cyvest-vis`: React component for the force-directed observable graph (Cytoscape + `d3-force`). - `@cyvest/cyvest-app`: Vite demo that bundles the JS packages with sample investigations. The JS packages track the generated schema; serialized investigations should include fields like diff --git a/docs/index.md b/docs/index.md index 934884c..b398dca 100644 --- a/docs/index.md +++ b/docs/index.md @@ -103,7 +103,7 @@ Cyvest (facade + fluent proxies) ## JavaScript Packages - `@cyvest/cyvest-js`: TypeScript types, schema validation, and graph helpers for Cyvest investigations. -- `@cyvest/cyvest-vis`: React components (Cytoscape + ELK/Dagre) to visualize investigations. +- `@cyvest/cyvest-vis`: React component (Cytoscape + `d3-force`) for observable relationship graphs. - `@cyvest/cyvest-app`: Vite demo bundling the JS packages with sample investigations. [See JavaScript packages guide](js-packages.md) for install and workspace commands. diff --git a/docs/js-packages.md b/docs/js-packages.md index c04d18b..0a37ea2 100644 --- a/docs/js-packages.md +++ b/docs/js-packages.md @@ -15,12 +15,11 @@ recorded as an `INVESTIGATION_STARTED` event in the `audit_log`. ## @cyvest/cyvest-vis -Interactive graph visualization for Cyvest investigations with a clean v2 API. +Interactive visualization of Cyvest observable relationships. ### Features -- **Observables Graph**: force-directed observable and relationship view centered on the root -- **Investigation Graph**: radial force-directed view of root, tags, Findings, and Evidence +- **Observable Graph**: force-directed observable and relationship view centered on the root - **Restrained visual language**: neutral surfaces, compact SVG nodes, thin edges, and level color used only as a contour - **Interactive focus**: pan/zoom, fit, deterministic layout replay, selection, and neighborhood focus on hover @@ -33,7 +32,6 @@ import "@cyvest/cyvest-vis/styles.css"; console.log(event.nodeId, event.label)} /> ``` @@ -42,11 +40,10 @@ import "@cyvest/cyvest-vis/styles.css"; | Component | Description | |-----------|-------------| -| `CyvestGraph` | Combined view with toggle between Observables and Investigation | +| `CyvestGraph` | Main force-directed graph of observables and relationships | | `CyvestObservablesView` | Force-directed graph of observables and relationships | -| `CyvestInvestigationView` | Force-directed graph of root, tags, Findings, and Evidence | -See `js/packages/cyvest-vis/README.md` for full v2 API and theming details. +See `js/packages/cyvest-vis/README.md` for the full v6 API and theming details. ## Workspace commands diff --git a/js/packages/cyvest-app/README.md b/js/packages/cyvest-app/README.md index 10b97bc..bf7cb92 100644 --- a/js/packages/cyvest-app/README.md +++ b/js/packages/cyvest-app/README.md @@ -1,11 +1,11 @@ # @cyvest/cyvest-app -Vite demo application for the `@cyvest/cyvest-vis` Cytoscape visualization library (ELK for observables, Dagre for investigation view). +Vite demo application for the `@cyvest/cyvest-vis` force-directed observable graph. ## What it does - Loads bundled investigations (`src/investigations/*.json`) and validates them with `@cyvest/cyvest-js` -- Renders both observables and investigation views with `CyvestGraph` +- Renders the observable relationship graph with `CyvestGraph` - Demonstrates node selection events and basic layout customization ## Run locally diff --git a/js/packages/cyvest-app/src/App.tsx b/js/packages/cyvest-app/src/App.tsx index 0f5e842..e9bc074 100644 --- a/js/packages/cyvest-app/src/App.tsx +++ b/js/packages/cyvest-app/src/App.tsx @@ -66,7 +66,7 @@ export const App: React.FC = () => {
CYVEST
-

Investigation graph

+

Observable graph

diff --git a/js/packages/cyvest-vis/README.md b/js/packages/cyvest-vis/README.md index caf3e78..c5f5a76 100644 --- a/js/packages/cyvest-vis/README.md +++ b/js/packages/cyvest-vis/README.md @@ -1,30 +1,12 @@ # @cyvest/cyvest-vis -React components for exploring Cyvest investigations with Cytoscape and a -deterministic `d3-force` layout. +React components for exploring the observable relationship graph of a Cyvest +investigation with Cytoscape and `d3-force`. The default design uses a neutral canvas, compact nodes, thin edges, and a -restrained level palette. Color is limited to security-level contours; node -shape communicates the object category. +restrained level palette. Color is limited to security-level contours. -## Views - -`CyvestGraph` provides two related views: - -- **Observables**: observables and their directed relationships. -- **Investigation**: root, tag hierarchy, Findings, and Evidence links. - -Both views place the root at the center and use radial depth, link attraction, -repulsion, and collision forces. Hovering a node isolates its immediate -neighborhood. Clicking a node or edge emits a typed selection event. - -## Install - -```bash -pnpm add @cyvest/cyvest-vis -``` - -Import the package stylesheet once: +## Usage ```tsx import { CyvestGraph } from "@cyvest/cyvest-vis"; @@ -33,41 +15,40 @@ import "@cyvest/cyvest-vis/styles.css"; { - console.log(event.nodeId, event.label, event.nodeType); + console.log(event.nodeId, event.label); }} /> ``` -## Force layout +`CyvestGraph` and `CyvestObservablesView` render the same observable graph. +`CyvestGraph` is the concise public entry point. -The force simulation runs to completion before Cytoscape renders the graph, so -the same investigation produces stable positions. +Hovering a node isolates its immediate neighborhood. The initial placement is +deterministic; a live force simulation then settles the graph and is reheated +while nodes are dragged. + +## Force layout ```tsx ``` -Available layout settings include `linkDistance`, `linkStrength`, -`chargeStrength`, `collisionPadding`, `radialStep`, `radialStrength`, -`centerStrength`, `iterations`, `padding`, and animation options. +Set `physics={false}` to keep only the deterministic initial placement. -## Theming +Available settings include `linkDistance`, `linkStrength`, `chargeStrength`, +`collisionPadding`, `radialStep`, `radialStrength`, `centerStrength`, +`iterations`, `padding`, and animation options. -Use the `theme` prop to override typed theme tokens: +## Theming ```tsx ``` -The CSS controls use the same tokens through `--cyvest-*` custom properties. +Use `DARK_CYVEST_THEME` for the built-in dark theme. ## Exports - `CyvestGraph` - `CyvestObservablesView` -- `CyvestInvestigationView` - `computeForcePositions` - `createForceLayout` -- `getDefaultForceOptions` -- icon, label, color, event, theme, and graph data types +- `startForceSimulation` +- theme, event, icon, label, color, and observable graph data types ## Development diff --git a/js/packages/cyvest-vis/pnpm-lock.yaml b/js/packages/cyvest-vis/pnpm-lock.yaml deleted file mode 100644 index c10c662..0000000 --- a/js/packages/cyvest-vis/pnpm-lock.yaml +++ /dev/null @@ -1,1692 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@cyvest/cyvest-js': - specifier: workspace:* - version: link:../cyvest-js - cytoscape: - specifier: ^3.34.0 - version: 3.34.0 - cytoscape-dagre: - specifier: ^4.0.0 - version: 4.0.0(cytoscape@3.34.0) - cytoscape-elk: - specifier: ^2.3.0 - version: 2.3.0(cytoscape@3.34.0) - dagre: - specifier: ^0.8.5 - version: 0.8.5 - elkjs: - specifier: ^0.11.1 - version: 0.11.1 - devDependencies: - '@types/react': - specifier: ^19.2.17 - version: 19.2.17 - '@types/react-dom': - specifier: ^19.2.3 - version: 19.2.3(@types/react@19.2.17) - tsup: - specifier: ^8.5.1 - version: 8.5.1(postcss@8.5.15)(typescript@6.0.3) - typescript: - specifier: ^6.0.3 - version: 6.0.3 - vitest: - specifier: ^4.1.8 - version: 4.1.8(vite@8.0.16(esbuild@0.27.7)) - -packages: - - '@emnapi/core@1.10.0': - resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - - '@emnapi/runtime@1.10.0': - resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - - '@emnapi/wasi-threads@1.2.1': - resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - - '@esbuild/aix-ppc64@0.27.7': - resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.27.7': - resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.27.7': - resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.27.7': - resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.27.7': - resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.27.7': - resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.27.7': - resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.27.7': - resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.27.7': - resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.27.7': - resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.27.7': - resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.27.7': - resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.27.7': - resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.27.7': - resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.27.7': - resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.27.7': - resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.27.7': - resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.27.7': - resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.27.7': - resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.27.7': - resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.27.7': - resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.27.7': - resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.27.7': - resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.27.7': - resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.27.7': - resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.27.7': - resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@napi-rs/wasm-runtime@1.1.4': - resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} - peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 - - '@oxc-project/types@0.133.0': - resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} - - '@rolldown/binding-android-arm64@1.0.3': - resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.0.3': - resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.0.3': - resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.0.3': - resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.0.3': - resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.0.3': - resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-arm64-musl@1.0.3': - resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rolldown/binding-linux-ppc64-gnu@1.0.3': - resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-s390x-gnu@1.0.3': - resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-gnu@1.0.3': - resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-musl@1.0.3': - resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rolldown/binding-openharmony-arm64@1.0.3': - resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-wasm32-wasi@1.0.3': - resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.0.3': - resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.0.3': - resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/pluginutils@1.0.1': - resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - - '@rollup/rollup-android-arm-eabi@4.61.1': - resolution: {integrity: sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.61.1': - resolution: {integrity: sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.61.1': - resolution: {integrity: sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.61.1': - resolution: {integrity: sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.61.1': - resolution: {integrity: sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.61.1': - resolution: {integrity: sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.61.1': - resolution: {integrity: sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm-musleabihf@4.61.1': - resolution: {integrity: sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-arm64-gnu@4.61.1': - resolution: {integrity: sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm64-musl@4.61.1': - resolution: {integrity: sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-loong64-gnu@4.61.1': - resolution: {integrity: sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-loong64-musl@4.61.1': - resolution: {integrity: sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-ppc64-gnu@4.61.1': - resolution: {integrity: sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-ppc64-musl@4.61.1': - resolution: {integrity: sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==} - cpu: [ppc64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-gnu@4.61.1': - resolution: {integrity: sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-riscv64-musl@4.61.1': - resolution: {integrity: sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-s390x-gnu@4.61.1': - resolution: {integrity: sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-gnu@4.61.1': - resolution: {integrity: sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-musl@4.61.1': - resolution: {integrity: sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rollup/rollup-openbsd-x64@4.61.1': - resolution: {integrity: sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.61.1': - resolution: {integrity: sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-win32-arm64-msvc@4.61.1': - resolution: {integrity: sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.61.1': - resolution: {integrity: sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.61.1': - resolution: {integrity: sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.61.1': - resolution: {integrity: sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==} - cpu: [x64] - os: [win32] - - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - - '@tybys/wasm-util@0.10.2': - resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} - - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} - peerDependencies: - '@types/react': ^19.2.0 - - '@types/react@19.2.17': - resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} - - '@vitest/expect@4.1.8': - resolution: {integrity: sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==} - - '@vitest/mocker@4.1.8': - resolution: {integrity: sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@4.1.8': - resolution: {integrity: sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==} - - '@vitest/runner@4.1.8': - resolution: {integrity: sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==} - - '@vitest/snapshot@4.1.8': - resolution: {integrity: sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==} - - '@vitest/spy@4.1.8': - resolution: {integrity: sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==} - - '@vitest/utils@4.1.8': - resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==} - - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true - - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - bundle-require@5.1.0: - resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - esbuild: '>=0.18' - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} - engines: {node: '>=18'} - - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} - - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - cytoscape-dagre@4.0.0: - resolution: {integrity: sha512-wsDgi1hHpWfslQxe/Gd/xCCeg2YUDrzwulhHOSI7fMfll92FFVY2AWhSRulLiOVcyDE4zz0rRd/+1TBTw+ew5Q==} - peerDependencies: - cytoscape: ^3.2.22 - - cytoscape-elk@2.3.0: - resolution: {integrity: sha512-1h2ZmPOy5HD2+mrfF3P2ICxfnDyPCWg/xLVs7fIjTOzdQu51ydrMtm6Sb7KnhFwLBzhGIVYI2Gbns0njggBarQ==} - peerDependencies: - cytoscape: ^3.2.0 - - cytoscape@3.34.0: - resolution: {integrity: sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==} - engines: {node: '>=0.10'} - - dagre@0.8.5: - resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - elkjs@0.11.1: - resolution: {integrity: sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==} - - elkjs@0.9.3: - resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==} - - es-module-lexer@2.1.0: - resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} - - esbuild@0.27.7: - resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} - engines: {node: '>=18'} - hasBin: true - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} - engines: {node: '>=12.0.0'} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fix-dts-default-cjs-exports@1.0.1: - resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - graphlib@2.1.8: - resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==} - - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} - engines: {node: '>= 12.0.0'} - - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - load-tsconfig@0.2.5: - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - lodash@4.18.1: - resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - mlly@1.8.2: - resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - obug@2.1.2: - resolution: {integrity: sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==} - engines: {node: '>=12.20.0'} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} - engines: {node: '>=12'} - - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} - - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - - postcss-load-config@6.0.1: - resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} - engines: {node: '>= 18'} - peerDependencies: - jiti: '>=1.21.0' - postcss: '>=8.0.9' - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - jiti: - optional: true - postcss: - optional: true - tsx: - optional: true - yaml: - optional: true - - postcss@8.5.15: - resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} - engines: {node: ^10 || ^12 || >=14} - - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} - - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - rolldown@1.0.3: - resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - rollup@4.61.1: - resolution: {integrity: sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - source-map@0.7.6: - resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} - engines: {node: '>= 12'} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - std-env@4.1.0: - resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} - - sucrase@3.35.1: - resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - - tinyexec@1.2.4: - resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} - engines: {node: '>=18'} - - tinyglobby@0.2.17: - resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} - engines: {node: '>=12.0.0'} - - tinyrainbow@3.1.0: - resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} - engines: {node: '>=14.0.0'} - - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsup@8.5.1: - resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true - - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} - hasBin: true - - ufo@1.6.4: - resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} - - vite@8.0.16: - resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.18 - esbuild: ^0.27.0 || ^0.28.0 - jiti: '>=1.21.0' - less: ^4.0.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vitest@4.1.8: - resolution: {integrity: sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.8 - '@vitest/browser-preview': 4.1.8 - '@vitest/browser-webdriverio': 4.1.8 - '@vitest/coverage-istanbul': 4.1.8 - '@vitest/coverage-v8': 4.1.8 - '@vitest/ui': 4.1.8 - happy-dom: '*' - jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@opentelemetry/api': - optional: true - '@types/node': - optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/coverage-istanbul': - optional: true - '@vitest/coverage-v8': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - -snapshots: - - '@emnapi/core@1.10.0': - dependencies: - '@emnapi/wasi-threads': 1.2.1 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.10.0': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.2.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@esbuild/aix-ppc64@0.27.7': - optional: true - - '@esbuild/android-arm64@0.27.7': - optional: true - - '@esbuild/android-arm@0.27.7': - optional: true - - '@esbuild/android-x64@0.27.7': - optional: true - - '@esbuild/darwin-arm64@0.27.7': - optional: true - - '@esbuild/darwin-x64@0.27.7': - optional: true - - '@esbuild/freebsd-arm64@0.27.7': - optional: true - - '@esbuild/freebsd-x64@0.27.7': - optional: true - - '@esbuild/linux-arm64@0.27.7': - optional: true - - '@esbuild/linux-arm@0.27.7': - optional: true - - '@esbuild/linux-ia32@0.27.7': - optional: true - - '@esbuild/linux-loong64@0.27.7': - optional: true - - '@esbuild/linux-mips64el@0.27.7': - optional: true - - '@esbuild/linux-ppc64@0.27.7': - optional: true - - '@esbuild/linux-riscv64@0.27.7': - optional: true - - '@esbuild/linux-s390x@0.27.7': - optional: true - - '@esbuild/linux-x64@0.27.7': - optional: true - - '@esbuild/netbsd-arm64@0.27.7': - optional: true - - '@esbuild/netbsd-x64@0.27.7': - optional: true - - '@esbuild/openbsd-arm64@0.27.7': - optional: true - - '@esbuild/openbsd-x64@0.27.7': - optional: true - - '@esbuild/openharmony-arm64@0.27.7': - optional: true - - '@esbuild/sunos-x64@0.27.7': - optional: true - - '@esbuild/win32-arm64@0.27.7': - optional: true - - '@esbuild/win32-ia32@0.27.7': - optional: true - - '@esbuild/win32-x64@0.27.7': - optional: true - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.2 - optional: true - - '@oxc-project/types@0.133.0': {} - - '@rolldown/binding-android-arm64@1.0.3': - optional: true - - '@rolldown/binding-darwin-arm64@1.0.3': - optional: true - - '@rolldown/binding-darwin-x64@1.0.3': - optional: true - - '@rolldown/binding-freebsd-x64@1.0.3': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.0.3': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.0.3': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.0.3': - optional: true - - '@rolldown/binding-linux-ppc64-gnu@1.0.3': - optional: true - - '@rolldown/binding-linux-s390x-gnu@1.0.3': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.0.3': - optional: true - - '@rolldown/binding-linux-x64-musl@1.0.3': - optional: true - - '@rolldown/binding-openharmony-arm64@1.0.3': - optional: true - - '@rolldown/binding-wasm32-wasi@1.0.3': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.0.3': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.0.3': - optional: true - - '@rolldown/pluginutils@1.0.1': {} - - '@rollup/rollup-android-arm-eabi@4.61.1': - optional: true - - '@rollup/rollup-android-arm64@4.61.1': - optional: true - - '@rollup/rollup-darwin-arm64@4.61.1': - optional: true - - '@rollup/rollup-darwin-x64@4.61.1': - optional: true - - '@rollup/rollup-freebsd-arm64@4.61.1': - optional: true - - '@rollup/rollup-freebsd-x64@4.61.1': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.61.1': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.61.1': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.61.1': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.61.1': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.61.1': - optional: true - - '@rollup/rollup-linux-loong64-musl@4.61.1': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.61.1': - optional: true - - '@rollup/rollup-linux-ppc64-musl@4.61.1': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.61.1': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.61.1': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.61.1': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.61.1': - optional: true - - '@rollup/rollup-linux-x64-musl@4.61.1': - optional: true - - '@rollup/rollup-openbsd-x64@4.61.1': - optional: true - - '@rollup/rollup-openharmony-arm64@4.61.1': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.61.1': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.61.1': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.61.1': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.61.1': - optional: true - - '@standard-schema/spec@1.1.0': {} - - '@tybys/wasm-util@0.10.2': - dependencies: - tslib: 2.8.1 - optional: true - - '@types/chai@5.2.3': - dependencies: - '@types/deep-eql': 4.0.2 - assertion-error: 2.0.1 - - '@types/deep-eql@4.0.2': {} - - '@types/estree@1.0.9': {} - - '@types/react-dom@19.2.3(@types/react@19.2.17)': - dependencies: - '@types/react': 19.2.17 - - '@types/react@19.2.17': - dependencies: - csstype: 3.2.3 - - '@vitest/expect@4.1.8': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.8 - '@vitest/utils': 4.1.8 - chai: 6.2.2 - tinyrainbow: 3.1.0 - - '@vitest/mocker@4.1.8(vite@8.0.16(esbuild@0.27.7))': - dependencies: - '@vitest/spy': 4.1.8 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 8.0.16(esbuild@0.27.7) - - '@vitest/pretty-format@4.1.8': - dependencies: - tinyrainbow: 3.1.0 - - '@vitest/runner@4.1.8': - dependencies: - '@vitest/utils': 4.1.8 - pathe: 2.0.3 - - '@vitest/snapshot@4.1.8': - dependencies: - '@vitest/pretty-format': 4.1.8 - '@vitest/utils': 4.1.8 - magic-string: 0.30.21 - pathe: 2.0.3 - - '@vitest/spy@4.1.8': {} - - '@vitest/utils@4.1.8': - dependencies: - '@vitest/pretty-format': 4.1.8 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 - - acorn@8.16.0: {} - - any-promise@1.3.0: {} - - assertion-error@2.0.1: {} - - bundle-require@5.1.0(esbuild@0.27.7): - dependencies: - esbuild: 0.27.7 - load-tsconfig: 0.2.5 - - cac@6.7.14: {} - - chai@6.2.2: {} - - chokidar@4.0.3: - dependencies: - readdirp: 4.1.2 - - commander@4.1.1: {} - - confbox@0.1.8: {} - - consola@3.4.2: {} - - convert-source-map@2.0.0: {} - - csstype@3.2.3: {} - - cytoscape-dagre@4.0.0(cytoscape@3.34.0): - dependencies: - cytoscape: 3.34.0 - - cytoscape-elk@2.3.0(cytoscape@3.34.0): - dependencies: - cytoscape: 3.34.0 - elkjs: 0.9.3 - - cytoscape@3.34.0: {} - - dagre@0.8.5: - dependencies: - graphlib: 2.1.8 - lodash: 4.18.1 - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - detect-libc@2.1.2: {} - - elkjs@0.11.1: {} - - elkjs@0.9.3: {} - - es-module-lexer@2.1.0: {} - - esbuild@0.27.7: - optionalDependencies: - '@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 - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.9 - - expect-type@1.3.0: {} - - fdir@6.5.0(picomatch@4.0.4): - optionalDependencies: - picomatch: 4.0.4 - - fix-dts-default-cjs-exports@1.0.1: - dependencies: - magic-string: 0.30.21 - mlly: 1.8.2 - rollup: 4.61.1 - - fsevents@2.3.3: - optional: true - - graphlib@2.1.8: - dependencies: - lodash: 4.18.1 - - joycon@3.1.1: {} - - lightningcss-android-arm64@1.32.0: - optional: true - - lightningcss-darwin-arm64@1.32.0: - optional: true - - lightningcss-darwin-x64@1.32.0: - optional: true - - lightningcss-freebsd-x64@1.32.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.32.0: - optional: true - - lightningcss-linux-arm64-gnu@1.32.0: - optional: true - - lightningcss-linux-arm64-musl@1.32.0: - optional: true - - lightningcss-linux-x64-gnu@1.32.0: - optional: true - - lightningcss-linux-x64-musl@1.32.0: - optional: true - - lightningcss-win32-arm64-msvc@1.32.0: - optional: true - - lightningcss-win32-x64-msvc@1.32.0: - optional: true - - lightningcss@1.32.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 - - lilconfig@3.1.3: {} - - lines-and-columns@1.2.4: {} - - load-tsconfig@0.2.5: {} - - lodash@4.18.1: {} - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - mlly@1.8.2: - dependencies: - acorn: 8.16.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.4 - - ms@2.1.3: {} - - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - - nanoid@3.3.12: {} - - object-assign@4.1.1: {} - - obug@2.1.2: {} - - pathe@2.0.3: {} - - picocolors@1.1.1: {} - - picomatch@4.0.4: {} - - pirates@4.0.7: {} - - pkg-types@1.3.1: - dependencies: - confbox: 0.1.8 - mlly: 1.8.2 - pathe: 2.0.3 - - postcss-load-config@6.0.1(postcss@8.5.15): - dependencies: - lilconfig: 3.1.3 - optionalDependencies: - postcss: 8.5.15 - - postcss@8.5.15: - dependencies: - nanoid: 3.3.12 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - readdirp@4.1.2: {} - - resolve-from@5.0.0: {} - - rolldown@1.0.3: - dependencies: - '@oxc-project/types': 0.133.0 - '@rolldown/pluginutils': 1.0.1 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.3 - '@rolldown/binding-darwin-arm64': 1.0.3 - '@rolldown/binding-darwin-x64': 1.0.3 - '@rolldown/binding-freebsd-x64': 1.0.3 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 - '@rolldown/binding-linux-arm64-gnu': 1.0.3 - '@rolldown/binding-linux-arm64-musl': 1.0.3 - '@rolldown/binding-linux-ppc64-gnu': 1.0.3 - '@rolldown/binding-linux-s390x-gnu': 1.0.3 - '@rolldown/binding-linux-x64-gnu': 1.0.3 - '@rolldown/binding-linux-x64-musl': 1.0.3 - '@rolldown/binding-openharmony-arm64': 1.0.3 - '@rolldown/binding-wasm32-wasi': 1.0.3 - '@rolldown/binding-win32-arm64-msvc': 1.0.3 - '@rolldown/binding-win32-x64-msvc': 1.0.3 - - rollup@4.61.1: - dependencies: - '@types/estree': 1.0.9 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.61.1 - '@rollup/rollup-android-arm64': 4.61.1 - '@rollup/rollup-darwin-arm64': 4.61.1 - '@rollup/rollup-darwin-x64': 4.61.1 - '@rollup/rollup-freebsd-arm64': 4.61.1 - '@rollup/rollup-freebsd-x64': 4.61.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.61.1 - '@rollup/rollup-linux-arm-musleabihf': 4.61.1 - '@rollup/rollup-linux-arm64-gnu': 4.61.1 - '@rollup/rollup-linux-arm64-musl': 4.61.1 - '@rollup/rollup-linux-loong64-gnu': 4.61.1 - '@rollup/rollup-linux-loong64-musl': 4.61.1 - '@rollup/rollup-linux-ppc64-gnu': 4.61.1 - '@rollup/rollup-linux-ppc64-musl': 4.61.1 - '@rollup/rollup-linux-riscv64-gnu': 4.61.1 - '@rollup/rollup-linux-riscv64-musl': 4.61.1 - '@rollup/rollup-linux-s390x-gnu': 4.61.1 - '@rollup/rollup-linux-x64-gnu': 4.61.1 - '@rollup/rollup-linux-x64-musl': 4.61.1 - '@rollup/rollup-openbsd-x64': 4.61.1 - '@rollup/rollup-openharmony-arm64': 4.61.1 - '@rollup/rollup-win32-arm64-msvc': 4.61.1 - '@rollup/rollup-win32-ia32-msvc': 4.61.1 - '@rollup/rollup-win32-x64-gnu': 4.61.1 - '@rollup/rollup-win32-x64-msvc': 4.61.1 - fsevents: 2.3.3 - - siginfo@2.0.0: {} - - source-map-js@1.2.1: {} - - source-map@0.7.6: {} - - stackback@0.0.2: {} - - std-env@4.1.0: {} - - sucrase@3.35.1: - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - commander: 4.1.1 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.7 - tinyglobby: 0.2.17 - ts-interface-checker: 0.1.13 - - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 - - tinybench@2.9.0: {} - - tinyexec@0.3.2: {} - - tinyexec@1.2.4: {} - - tinyglobby@0.2.17: - dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - - tinyrainbow@3.1.0: {} - - tree-kill@1.2.2: {} - - ts-interface-checker@0.1.13: {} - - tslib@2.8.1: - optional: true - - tsup@8.5.1(postcss@8.5.15)(typescript@6.0.3): - dependencies: - bundle-require: 5.1.0(esbuild@0.27.7) - cac: 6.7.14 - chokidar: 4.0.3 - consola: 3.4.2 - debug: 4.4.3 - esbuild: 0.27.7 - fix-dts-default-cjs-exports: 1.0.1 - joycon: 3.1.1 - picocolors: 1.1.1 - postcss-load-config: 6.0.1(postcss@8.5.15) - resolve-from: 5.0.0 - rollup: 4.61.1 - source-map: 0.7.6 - sucrase: 3.35.1 - tinyexec: 0.3.2 - tinyglobby: 0.2.17 - tree-kill: 1.2.2 - optionalDependencies: - postcss: 8.5.15 - typescript: 6.0.3 - transitivePeerDependencies: - - jiti - - supports-color - - tsx - - yaml - - typescript@6.0.3: {} - - ufo@1.6.4: {} - - vite@8.0.16(esbuild@0.27.7): - dependencies: - lightningcss: 1.32.0 - picomatch: 4.0.4 - postcss: 8.5.15 - rolldown: 1.0.3 - tinyglobby: 0.2.17 - optionalDependencies: - esbuild: 0.27.7 - fsevents: 2.3.3 - - vitest@4.1.8(vite@8.0.16(esbuild@0.27.7)): - dependencies: - '@vitest/expect': 4.1.8 - '@vitest/mocker': 4.1.8(vite@8.0.16(esbuild@0.27.7)) - '@vitest/pretty-format': 4.1.8 - '@vitest/runner': 4.1.8 - '@vitest/snapshot': 4.1.8 - '@vitest/spy': 4.1.8 - '@vitest/utils': 4.1.8 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.2 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.1.0 - tinybench: 2.9.0 - tinyexec: 1.2.4 - tinyglobby: 0.2.17 - tinyrainbow: 3.1.0 - vite: 8.0.16(esbuild@0.27.7) - why-is-node-running: 2.3.0 - transitivePeerDependencies: - - msw - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 diff --git a/js/packages/cyvest-vis/src/adapters/investigationElements.ts b/js/packages/cyvest-vis/src/adapters/investigationElements.ts deleted file mode 100644 index 5a2a82d..0000000 --- a/js/packages/cyvest-vis/src/adapters/investigationElements.ts +++ /dev/null @@ -1,347 +0,0 @@ -import { - getRootObservable, - getTagAncestors, - type Finding, - type CyvestInvestigation, - type Level, - type Tag, -} from "@cyvest/cyvest-js"; -import type { ElementDefinition } from "cytoscape"; - -import type { - CyvestThemeTokens, - InvestigationCyEdgeData, - InvestigationCyNodeData, - InvestigationCyNodeType, -} from "../types"; -import { getInvestigationIconSvg } from "../icons/svg"; -import { getLevelColor, resolveTheme } from "../utils/colors"; -import { truncateLabel } from "../utils/labels"; - -export interface InvestigationAdapterOptions { - maxLabelLength?: number; - edgeColor?: string; - theme?: Partial; -} - -const ROOT_NODE_ID = "inv-root"; - -function createNodeData( - nodeId: string, - nodeType: InvestigationCyNodeType, - label: string, - level: Level, - score: number, - maxLabelLength: number, - theme: CyvestThemeTokens -): InvestigationCyNodeData { - const styleByType: Record< - InvestigationCyNodeType, - { - width: number; - height: number; - shape: "ellipse" | "round-rectangle" | "diamond"; - fillColor: string; - borderColor: string; - iconColor: string; - borderWidth: number; - } - > = { - root: { - width: 56, - height: 56, - shape: "ellipse", - fillColor: theme.rootSurface, - borderColor: theme.rootSurface, - iconColor: theme.rootText, - borderWidth: 1, - }, - tag: { - width: 28, - height: 28, - shape: "diamond", - fillColor: theme.nodeSurfaceMuted, - borderColor: "#94a3b8", - iconColor: theme.iconMutedColor, - borderWidth: 1.5, - }, - finding: { - width: 42, - height: 42, - shape: "ellipse", - fillColor: theme.nodeSurface, - borderColor: getLevelColor(level), - iconColor: theme.iconMutedColor, - borderWidth: 2, - }, - evidence: { - width: 36, - height: 30, - shape: "round-rectangle", - fillColor: theme.nodeSurfaceMuted, - borderColor: "#94a3b8", - iconColor: theme.iconMutedColor, - borderWidth: 1.5, - }, - }; - const style = styleByType[nodeType]; - - return { - id: nodeId, - nodeType, - labelShort: truncateLabel( - label, - nodeType === "root" ? maxLabelLength + 4 : maxLabelLength, - true - ), - labelFull: label, - level, - score, - borderColor: style.borderColor, - fillColor: style.fillColor, - icon: getInvestigationIconSvg(nodeType, { color: style.iconColor }), - width: style.width, - height: style.height, - shape: style.shape, - borderWidth: style.borderWidth, - }; -} - -function getRootLabel(investigation: CyvestInvestigation): { - value: string; - level: Level; - score: number; -} { - const rootObservable = getRootObservable(investigation); - if (rootObservable) { - return { - value: rootObservable.value, - level: rootObservable.level, - score: rootObservable.score, - }; - } - - const firstObservable = Object.values(investigation.observables)[0]; - if (firstObservable) { - return { - value: firstObservable.value, - level: firstObservable.level, - score: firstObservable.score, - }; - } - - return { - value: investigation.investigation_name ?? investigation.investigation_id, - level: investigation.level, - score: investigation.score, - }; -} - -function getTagMap(tags: Record): Map { - const map = new Map(); - for (const tag of Object.values(tags)) { - map.set(tag.name, tag); - } - return map; -} - -function createEdge( - id: string, - source: string, - target: string, - relationshipType: string, - edgeColor: string -): ElementDefinition { - const data: InvestigationCyEdgeData = { - id, - relationshipType, - color: edgeColor, - width: 1.05, - sourceArrowShape: "none", - targetArrowShape: "none", - }; - - return { - group: "edges", - data: { - ...data, - source, - target, - }, - }; -} - -export function buildInvestigationElements( - investigation: CyvestInvestigation, - options?: InvestigationAdapterOptions -): ElementDefinition[] { - const maxLabelLength = options?.maxLabelLength ?? 26; - const theme = resolveTheme(options?.theme); - const edgeColor = options?.edgeColor ?? theme.edgeColor; - - const nodes: ElementDefinition[] = []; - const edges: ElementDefinition[] = []; - - const root = getRootLabel(investigation); - nodes.push({ - group: "nodes", - data: createNodeData( - ROOT_NODE_ID, - "root", - root.value, - root.level, - root.score, - maxLabelLength, - theme - ), - }); - - const findings = Object.values(investigation.findings); - const evidences = Object.values(investigation.evidences); - const allTags = Object.values(investigation.tags); - const tagByName = getTagMap(investigation.tags); - - const findingsInTags = new Set(); - for (const tag of allTags) { - for (const findingKey of tag.findings) { - findingsInTags.add(findingKey); - } - } - - for (const finding of findings) { - const findingNodeId = `inv-finding:${finding.key}`; - const nodeData = createNodeData( - findingNodeId, - "finding", - finding.finding_name, - finding.level, - finding.score, - maxLabelLength, - theme - ); - - nodes.push({ group: "nodes", data: nodeData }); - - if (!findingsInTags.has(finding.key)) { - edges.push( - createEdge( - `inv-edge-root-finding:${finding.key}`, - ROOT_NODE_ID, - findingNodeId, - "contains-finding", - edgeColor - ) - ); - } - } - - for (const evidence of evidences) { - nodes.push({ - group: "nodes", - data: createNodeData( - `inv-evidence:${evidence.key}`, - "evidence", - evidence.title, - "INFO", - 0, - maxLabelLength, - theme - ), - }); - } - - for (const finding of findings) { - for (const link of finding.evidence_links) { - if (!investigation.evidences[link.evidence_key]) continue; - edges.push( - createEdge( - `inv-edge-finding-evidence:${finding.key}->${link.evidence_key}`, - `inv-finding:${finding.key}`, - `inv-evidence:${link.evidence_key}`, - "supported-by", - edgeColor - ) - ); - } - } - - const tagNames = new Set(); - for (const tag of allTags) { - tagNames.add(tag.name); - for (const ancestor of getTagAncestors(tag.name)) { - tagNames.add(ancestor); - } - } - - for (const tagName of tagNames) { - const tag = tagByName.get(tagName); - const tagLevel = tag?.direct_level ?? "INFO"; - const tagScore = tag?.direct_score ?? 0; - const shortTagName = tagName.split(":").pop() ?? tagName; - - nodes.push({ - group: "nodes", - data: createNodeData( - `inv-tag:${tagName}`, - "tag", - shortTagName, - tagLevel, - tagScore, - maxLabelLength, - theme - ), - }); - } - - for (const tagName of tagNames) { - const parts = tagName.split(":"); - - if (parts.length === 1) { - edges.push( - createEdge( - `inv-edge-root-tag:${tagName}`, - ROOT_NODE_ID, - `inv-tag:${tagName}`, - "contains-tag", - edgeColor - ) - ); - continue; - } - - const parentName = parts.slice(0, -1).join(":"); - edges.push( - createEdge( - `inv-edge-tag:${parentName}->${tagName}`, - `inv-tag:${parentName}`, - `inv-tag:${tagName}`, - "tag-hierarchy", - edgeColor - ) - ); - } - - const findingsByKey = new Map( - findings.map((finding) => [finding.key, finding]) - ); - - for (const tag of allTags) { - for (const findingKey of tag.findings) { - if (!findingsByKey.has(findingKey)) { - continue; - } - - edges.push( - createEdge( - `inv-edge-tag-finding:${tag.name}->${findingKey}`, - `inv-tag:${tag.name}`, - `inv-finding:${findingKey}`, - "tag-finding", - edgeColor - ) - ); - } - } - - return [...nodes, ...edges]; -} diff --git a/js/packages/cyvest-vis/src/components/CytoscapeCanvas.tsx b/js/packages/cyvest-vis/src/components/CytoscapeCanvas.tsx index cb3ceab..55ca94f 100644 --- a/js/packages/cyvest-vis/src/components/CytoscapeCanvas.tsx +++ b/js/packages/cyvest-vis/src/components/CytoscapeCanvas.tsx @@ -3,18 +3,23 @@ import type { Core, ElementDefinition, EventObjectEdge, EventObjectNode, LayoutO import { createCyInstance } from "../core/createCyInstance"; import { createThemeStyle } from "../core/theme"; +import { + startForceSimulation, + type ForceSimulationController, +} from "../layout/force"; import type { CyEdgeSelectEvent, CyNodeSelectEvent, + CyvestForceOptions, CyvestThemeTokens, - CyvestViewMode, } from "../types"; interface CytoscapeCanvasProps { - view: CyvestViewMode; elements: ElementDefinition[]; stylesheet: Stylesheet[]; layout: LayoutOptions; + forceOptions?: CyvestForceOptions; + physics?: boolean; width: number | string; height: number | string; className?: string; @@ -30,10 +35,11 @@ function joinClassNames(...names: Array): string { } export const CytoscapeCanvas: React.FC = ({ - view, elements, stylesheet, layout, + forceOptions, + physics = true, width, height, className, @@ -46,8 +52,13 @@ export const CytoscapeCanvas: React.FC = ({ const containerRef = useRef(null); const cyRef = useRef(null); const layoutRef = useRef(layout); + const forceOptionsRef = useRef(forceOptions); + const physicsRef = useRef(physics); + const simulationRef = useRef(null); layoutRef.current = layout; + forceOptionsRef.current = forceOptions; + physicsRef.current = physics; useEffect(() => { if (!containerRef.current) { @@ -59,6 +70,8 @@ export const CytoscapeCanvas: React.FC = ({ onCyReady?.(cy); return () => { + simulationRef.current?.stop(); + simulationRef.current = null; cy.destroy(); cyRef.current = null; }; @@ -70,8 +83,21 @@ export const CytoscapeCanvas: React.FC = ({ return; } - const runner = cy.layout(layoutRef.current); + simulationRef.current?.stop(); + simulationRef.current = null; + + const runner = cy.layout({ + ...layoutRef.current, + animate: false, + }); runner.run(); + + if (physicsRef.current) { + simulationRef.current = startForceSimulation( + cy, + forceOptionsRef.current + ); + } }, []); useEffect(() => { @@ -105,7 +131,7 @@ export const CytoscapeCanvas: React.FC = ({ const rawLabel = data.labelFull ?? data.labelShort ?? node.id(); onNodeSelect({ - view, + view: "observables", nodeId: node.id(), nodeType: typeof data.nodeType === "string" ? data.nodeType : "unknown", @@ -124,7 +150,7 @@ export const CytoscapeCanvas: React.FC = ({ const data = edge.data() as Record; onEdgeSelect({ - view, + view: "observables", edgeId: edge.id(), sourceId: edge.source().id(), targetId: edge.target().id(), @@ -169,7 +195,7 @@ export const CytoscapeCanvas: React.FC = ({ cy.removeListener("mouseout", "node", clearFocus); cy.removeListener("tap", handleCanvasTap); }; - }, [onEdgeSelect, onNodeSelect, view]); + }, [onEdgeSelect, onNodeSelect]); const handleFit = useCallback(() => { const cy = cyRef.current; @@ -227,8 +253,8 @@ export const CytoscapeCanvas: React.FC = ({ type="button" className="cyvest-toolbar__button" onClick={runLayout} - title="Re-run layout" - aria-label="Re-run layout" + title="Reheat physics" + aria-label="Reheat physics" >
- {showViewToggle && ( -
- - -
- )} - - {activeView === "observables" ? ( - - ) : ( - - )} -
- ); -}; + layout, + maxLabelLength = 28, +}) => ( + +); diff --git a/js/packages/cyvest-vis/src/components/CyvestInvestigationView.tsx b/js/packages/cyvest-vis/src/components/CyvestInvestigationView.tsx deleted file mode 100644 index 5237383..0000000 --- a/js/packages/cyvest-vis/src/components/CyvestInvestigationView.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useMemo } from "react"; - -import { buildInvestigationElements } from "../adapters/investigationElements"; -import { createInvestigationStylesheet } from "../core/styles"; -import { createForceLayout } from "../layout/force"; -import type { CyvestInvestigationViewProps } from "../types"; -import { CytoscapeCanvas } from "./CytoscapeCanvas"; - -export const CyvestInvestigationView: React.FC = ({ - investigation, - height = 500, - width = "100%", - className, - theme, - onCyReady, - onNodeSelect, - onEdgeSelect, - showToolbar = true, - layout, - maxLabelLength = 26, -}) => { - const elements = useMemo( - () => - buildInvestigationElements(investigation, { - maxLabelLength, - theme, - }), - [investigation, maxLabelLength, theme] - ); - - const stylesheet = useMemo( - () => createInvestigationStylesheet(theme), - [theme] - ); - - const forceLayout = useMemo( - () => createForceLayout(elements, "investigation", layout), - [elements, layout] - ); - - return ( - - ); -}; diff --git a/js/packages/cyvest-vis/src/components/CyvestObservablesView.tsx b/js/packages/cyvest-vis/src/components/CyvestObservablesView.tsx index bbd421a..e32535e 100644 --- a/js/packages/cyvest-vis/src/components/CyvestObservablesView.tsx +++ b/js/packages/cyvest-vis/src/components/CyvestObservablesView.tsx @@ -15,6 +15,7 @@ export const CyvestObservablesView: React.FC = ({ onCyReady, onNodeSelect, onEdgeSelect, + physics = true, showToolbar = true, layout, maxLabelLength = 28, @@ -34,16 +35,17 @@ export const CyvestObservablesView: React.FC = ({ ); const forceLayout = useMemo( - () => createForceLayout(elements, "observables", layout), + () => createForceLayout(elements, layout), [elements, layout] ); return ( -): Stylesheet[] { - return [ - ...createSharedStylesheet(theme), - { - selector: "node[nodeType = 'root']", - style: { - "font-weight": 700, - "font-size": 11, - "text-margin-y": 11, - }, - }, - { - selector: "node[nodeType = 'tag']", - style: { - "font-size": 9.5, - "background-width": "42%", - "background-height": "42%", - }, - }, - { - selector: "node[nodeType = 'evidence']", - style: { - "border-style": "dashed", - "font-size": 9.5, - }, - }, - ]; -} diff --git a/js/packages/cyvest-vis/src/core/theme.ts b/js/packages/cyvest-vis/src/core/theme.ts index 5a7d7bd..a4c7c29 100644 --- a/js/packages/cyvest-vis/src/core/theme.ts +++ b/js/packages/cyvest-vis/src/core/theme.ts @@ -20,7 +20,6 @@ export function createThemeStyle( "--cyvest-panel-text": resolved.panelText, "--cyvest-panel-muted": resolved.panelTextMuted, "--cyvest-accent": resolved.accent, - "--cyvest-toggle-active-bg": resolved.toggleActiveSurface, "--cyvest-font-family": resolved.fontFamily, } as React.CSSProperties; } diff --git a/js/packages/cyvest-vis/src/icons/svg.ts b/js/packages/cyvest-vis/src/icons/svg.ts index 3b0d73a..8daa68d 100644 --- a/js/packages/cyvest-vis/src/icons/svg.ts +++ b/js/packages/cyvest-vis/src/icons/svg.ts @@ -1,5 +1,3 @@ -import type { InvestigationCyNodeType } from "../types"; - export interface IconRenderOptions { color?: string; } @@ -13,10 +11,7 @@ type IconName = | "hash" | "flask" | "question" - | "crosshair" - | "finding" - | "evidence" - | "tag"; + | "finding"; const ICON_PATHS: Record = { globe: @@ -35,14 +30,8 @@ const ICON_PATHS: Record = { '', question: '', - crosshair: - '', finding: '', - evidence: - '', - tag: - '', }; export const OBSERVABLE_ICON_NAME_MAP: Record = { @@ -61,13 +50,6 @@ export const OBSERVABLE_ICON_NAME_MAP: Record = { cloud_resource: "globe", }; -export const INVESTIGATION_ICON_NAME_MAP: Record = { - root: "crosshair", - finding: "finding", - evidence: "evidence", - tag: "tag", -}; - function toSvgDataUri(iconName: IconName, color: string): string { const body = ICON_PATHS[iconName] ?? ICON_PATHS.question; const svg = [ @@ -89,11 +71,3 @@ export function getObservableIconSvg( const icon = OBSERVABLE_ICON_NAME_MAP[normalizedType] ?? "question"; return toSvgDataUri(icon, options?.color ?? "#314264"); } - -export function getInvestigationIconSvg( - nodeType: InvestigationCyNodeType, - options?: IconRenderOptions -): string { - const icon = INVESTIGATION_ICON_NAME_MAP[nodeType] ?? "question"; - return toSvgDataUri(icon, options?.color ?? "#314264"); -} diff --git a/js/packages/cyvest-vis/src/index.ts b/js/packages/cyvest-vis/src/index.ts index a0b99cb..5e598fc 100644 --- a/js/packages/cyvest-vis/src/index.ts +++ b/js/packages/cyvest-vis/src/index.ts @@ -1,12 +1,9 @@ export { CyvestGraph } from "./components/CyvestGraph"; export { CyvestObservablesView } from "./components/CyvestObservablesView"; -export { CyvestInvestigationView } from "./components/CyvestInvestigationView"; export { getObservableIconSvg, - getInvestigationIconSvg, OBSERVABLE_ICON_NAME_MAP, - INVESTIGATION_ICON_NAME_MAP, type IconRenderOptions, } from "./icons/svg"; @@ -22,25 +19,20 @@ export { computeForcePositions, createForceLayout, getDefaultForceOptions, + startForceSimulation, + type ForceSimulationController, } from "./layout/force"; export { DEFAULT_CYVEST_THEME, DARK_CYVEST_THEME, type CyvestThemeTokens, - type CyvestViewMode, - type CyvestElkDirection, - type CyvestElkOptions, type CyvestForceOptions, type CyNodeSelectEvent, type CyEdgeSelectEvent, type CyvestBaseViewProps, type CyvestGraphProps, type CyvestObservablesViewProps, - type CyvestInvestigationViewProps, type ObservableCyNodeData, type ObservableCyEdgeData, - type InvestigationCyNodeType, - type InvestigationCyNodeData, - type InvestigationCyEdgeData, } from "./types"; diff --git a/js/packages/cyvest-vis/src/layout/force.ts b/js/packages/cyvest-vis/src/layout/force.ts index 51fd616..f48e9e8 100644 --- a/js/packages/cyvest-vis/src/layout/force.ts +++ b/js/packages/cyvest-vis/src/layout/force.ts @@ -10,12 +10,15 @@ import { type SimulationLinkDatum, type SimulationNodeDatum, } from "d3-force"; -import type { ElementDefinition, LayoutOptions, Position } from "cytoscape"; - import type { - CyvestForceOptions, - CyvestViewMode, -} from "../types"; + Core, + ElementDefinition, + EventObjectNode, + LayoutOptions, + Position, +} from "cytoscape"; + +import type { CyvestForceOptions } from "../types"; interface ForceNode extends SimulationNodeDatum { id: string; @@ -29,6 +32,11 @@ interface ForceLink extends SimulationLinkDatum { target: string | ForceNode; } +export interface ForceSimulationController { + reheat: () => void; + stop: () => void; +} + const DEFAULT_OBSERVABLES_LAYOUT: Required = { linkDistance: 118, linkStrength: 0.72, @@ -44,39 +52,62 @@ const DEFAULT_OBSERVABLES_LAYOUT: Required = { animationDuration: 420, }; -const DEFAULT_INVESTIGATION_LAYOUT: Required = { - linkDistance: 132, - linkStrength: 0.82, - chargeStrength: -500, - collisionPadding: 34, - radialStep: 144, - radialStrength: 0.5, - centerStrength: 0.045, - iterations: 360, - padding: 78, - fit: true, - animate: true, - animationDuration: 420, -}; - -export function getDefaultForceOptions( - view: CyvestViewMode -): Required { - return view === "observables" - ? { ...DEFAULT_OBSERVABLES_LAYOUT } - : { ...DEFAULT_INVESTIGATION_LAYOUT }; +export function getDefaultForceOptions(): Required { + return { ...DEFAULT_OBSERVABLES_LAYOUT }; } function resolveOptions( - view: CyvestViewMode, overrides?: CyvestForceOptions ): Required { return { - ...getDefaultForceOptions(view), + ...getDefaultForceOptions(), ...overrides, }; } +function configureSimulation( + nodes: ForceNode[], + links: ForceLink[], + options: Required +) { + return forceSimulation(nodes) + .alpha(1) + .alphaDecay(1 - Math.pow(0.001, 1 / options.iterations)) + .velocityDecay(0.34) + .force( + "link", + forceLink(links) + .id((node) => node.id) + .distance((link) => { + const sourceDepth = + typeof link.source === "string" ? 0 : link.source.depth; + const targetDepth = + typeof link.target === "string" ? 0 : link.target.depth; + return options.linkDistance + Math.abs(sourceDepth - targetDepth) * 8; + }) + .strength(options.linkStrength) + ) + .force("charge", forceManyBody().strength(options.chargeStrength)) + .force( + "collision", + forceCollide() + .radius((node) => node.radius + options.collisionPadding) + .strength(0.92) + .iterations(3) + ) + .force( + "radial", + forceRadial( + (node) => node.depth * options.radialStep, + 0, + 0 + ).strength((node) => (node.isRoot ? 1 : options.radialStrength)) + ) + .force("center", forceCenter(0, 0).strength(options.centerStrength)) + .force("x", forceX(0).strength(options.centerStrength)) + .force("y", forceY(0).strength(options.centerStrength)); +} + function getNodeId(element: ElementDefinition): string { return String(element.data.id); } @@ -139,10 +170,9 @@ function calculateDepths( export function computeForcePositions( elements: ElementDefinition[], - view: CyvestViewMode, overrides?: CyvestForceOptions ): Record { - const options = resolveOptions(view, overrides); + const options = resolveOptions(overrides); const nodeElements = elements.filter((element) => element.group === "nodes"); const edgeElements = elements.filter((element) => element.group === "edges"); const nodeIds = nodeElements.map(getNodeId); @@ -165,43 +195,7 @@ export function computeForcePositions( }; }); - const simulation = forceSimulation(nodes) - .alpha(1) - .alphaDecay(1 - Math.pow(0.001, 1 / options.iterations)) - .velocityDecay(0.34) - .force( - "link", - forceLink(links) - .id((node) => node.id) - .distance((link) => { - const sourceDepth = - typeof link.source === "string" ? 0 : link.source.depth; - const targetDepth = - typeof link.target === "string" ? 0 : link.target.depth; - return options.linkDistance + Math.abs(sourceDepth - targetDepth) * 8; - }) - .strength(options.linkStrength) - ) - .force("charge", forceManyBody().strength(options.chargeStrength)) - .force( - "collision", - forceCollide() - .radius((node) => node.radius + options.collisionPadding) - .strength(0.92) - .iterations(3) - ) - .force( - "radial", - forceRadial( - (node) => node.depth * options.radialStep, - 0, - 0 - ).strength((node) => (node.isRoot ? 1 : options.radialStrength)) - ) - .force("center", forceCenter(0, 0).strength(options.centerStrength)) - .force("x", forceX(0).strength(options.centerStrength)) - .force("y", forceY(0).strength(options.centerStrength)) - .stop(); + const simulation = configureSimulation(nodes, links, options).stop(); for (let index = 0; index < options.iterations; index += 1) { simulation.tick(); @@ -218,13 +212,122 @@ export function computeForcePositions( ); } +export function startForceSimulation( + cy: Core, + overrides?: CyvestForceOptions +): ForceSimulationController { + if (cy.nodes().empty()) { + return { + reheat: () => undefined, + stop: () => undefined, + }; + } + + const options = resolveOptions(overrides); + const cyNodes = cy.nodes(); + const nodeIds = cyNodes.map((node) => node.id()); + const rootNode = cyNodes + .filter( + (node) => + node.data("isRoot") === true || node.data("nodeType") === "root" + ) + .first(); + const rootId = (!rootNode.empty() ? rootNode : cyNodes.first()).id(); + const links: ForceLink[] = cy.edges().map((edge) => ({ + source: edge.source().id(), + target: edge.target().id(), + })); + const depths = calculateDepths(nodeIds, links, rootId); + const nodeById = new Map(); + + const nodes: ForceNode[] = cyNodes.map((node) => { + const position = node.position(); + const forceNode: ForceNode = { + id: node.id(), + radius: + Math.max( + Number(node.data("width") ?? 40), + Number(node.data("height") ?? 40) + ) / 2, + depth: depths.get(node.id()) ?? 1, + isRoot: node.id() === rootId, + x: position.x, + y: position.y, + }; + if (forceNode.isRoot) { + forceNode.fx = position.x; + forceNode.fy = position.y; + } + nodeById.set(forceNode.id, forceNode); + return forceNode; + }); + + const simulation = configureSimulation(nodes, links, options) + .alpha(0.42) + .alphaTarget(0) + .on("tick", () => { + cy.batch(() => { + for (const forceNode of nodes) { + const cyNode = cy.getElementById(forceNode.id); + if (cyNode.empty() || cyNode.grabbed()) continue; + cyNode.position({ + x: forceNode.x ?? 0, + y: forceNode.y ?? 0, + }); + } + }); + }); + + const handleGrab = (event: EventObjectNode) => { + const forceNode = nodeById.get(event.target.id()); + if (!forceNode) return; + const position = event.target.position(); + forceNode.fx = position.x; + forceNode.fy = position.y; + simulation.alphaTarget(0.22).restart(); + }; + + const handleDrag = (event: EventObjectNode) => { + const forceNode = nodeById.get(event.target.id()); + if (!forceNode) return; + const position = event.target.position(); + forceNode.fx = position.x; + forceNode.fy = position.y; + }; + + const handleFree = (event: EventObjectNode) => { + const forceNode = nodeById.get(event.target.id()); + if (!forceNode) return; + if (!forceNode.isRoot) { + forceNode.fx = null; + forceNode.fy = null; + } + simulation.alphaTarget(0); + }; + + cy.on("grab", "node", handleGrab); + cy.on("drag", "node", handleDrag); + cy.on("free", "node", handleFree); + + return { + reheat: () => { + simulation.alpha(0.72).alphaTarget(0).restart(); + }, + stop: () => { + simulation.stop(); + cy.removeListener("grab", "node", handleGrab); + cy.removeListener("drag", "node", handleDrag); + cy.removeListener("free", "node", handleFree); + }, + }; +} + export function createForceLayout( elements: ElementDefinition[], - view: CyvestViewMode, overrides?: CyvestForceOptions ): LayoutOptions { - const options = resolveOptions(view, overrides); - const positions = computeForcePositions(elements, view, overrides); + const options = resolveOptions(overrides); + const positions = computeForcePositions(elements, overrides); return { name: "preset", diff --git a/js/packages/cyvest-vis/src/styles.css b/js/packages/cyvest-vis/src/styles.css index 5e5f266..98f84bd 100644 --- a/js/packages/cyvest-vis/src/styles.css +++ b/js/packages/cyvest-vis/src/styles.css @@ -12,8 +12,7 @@ inset: 0; } -.cyvest-toolbar, -.cyvest-view-toggle { +.cyvest-toolbar { position: absolute; z-index: 12; display: flex; @@ -51,8 +50,7 @@ background: rgba(15, 23, 42, 0.055); } -.cyvest-toolbar__button:focus-visible, -.cyvest-view-toggle__button:focus-visible { +.cyvest-toolbar__button:focus-visible { outline: 2px solid var(--cyvest-accent); outline-offset: 1px; } @@ -66,49 +64,11 @@ stroke-linejoin: round; } -.cyvest-view-toggle { - top: 12px; - left: 12px; - gap: 2px; - padding: 3px; - border-radius: 9px; -} - -.cyvest-view-toggle__button { - border: 0; - border-radius: 6px; - padding: 7px 10px; - color: var(--cyvest-panel-muted); - background: transparent; - font-family: var(--cyvest-font-family); - font-size: 11px; - font-weight: 600; - letter-spacing: 0.01em; - cursor: pointer; - transition: background-color 120ms ease, color 120ms ease; -} - -.cyvest-view-toggle__button:hover { - color: var(--cyvest-panel-text); - background: rgba(15, 23, 42, 0.045); -} - -.cyvest-view-toggle__button--active, -.cyvest-view-toggle__button--active:hover { - color: var(--cyvest-panel-text); - background: var(--cyvest-toggle-active-bg); -} - @media (max-width: 640px) { - .cyvest-view-toggle, .cyvest-toolbar { top: 8px; } - .cyvest-view-toggle { - left: 8px; - } - .cyvest-toolbar { right: 8px; } diff --git a/js/packages/cyvest-vis/src/types.ts b/js/packages/cyvest-vis/src/types.ts index d41b576..b1c72c9 100644 --- a/js/packages/cyvest-vis/src/types.ts +++ b/js/packages/cyvest-vis/src/types.ts @@ -1,8 +1,6 @@ import type { CyvestInvestigation, Level, RelationshipDirection } from "@cyvest/cyvest-js"; import type { Core, EdgeSingular, NodeSingular } from "cytoscape"; -export type CyvestViewMode = "observables" | "investigation"; - export interface CyvestThemeTokens { background: string; gridColor: string; @@ -14,10 +12,8 @@ export interface CyvestThemeTokens { edgeColor: string; edgeSelectedColor: string; fontFamily: string; - /** Default node fill (e.g. internal observables, findings). */ + /** Default fill for internal observable nodes. */ nodeSurface: string; - /** Secondary node fill (tags, evidence). */ - nodeSurfaceMuted: string; /** Root node fill. */ rootSurface: string; /** Icon/text color used on the root node. */ @@ -30,8 +26,6 @@ export interface CyvestThemeTokens { levelSurfaceMix: string; /** Mix ratio (0-1) toward {@link levelSurfaceMix}. */ levelSurfaceMixRatio: number; - /** Background of the active view-toggle button. */ - toggleActiveSurface: string; } export const DEFAULT_CYVEST_THEME: CyvestThemeTokens = { @@ -47,14 +41,12 @@ export const DEFAULT_CYVEST_THEME: CyvestThemeTokens = { fontFamily: "IBM Plex Sans, Segoe UI, Helvetica Neue, Arial, sans-serif", nodeSurface: "#ffffff", - nodeSurfaceMuted: "#f1f5f9", rootSurface: "#1e293b", rootText: "#ffffff", iconColor: "#314264", iconMutedColor: "#475569", levelSurfaceMix: "#ffffff", levelSurfaceMixRatio: 0.94, - toggleActiveSurface: "#eef2f6", }; export const DARK_CYVEST_THEME: CyvestThemeTokens = { @@ -70,18 +62,14 @@ export const DARK_CYVEST_THEME: CyvestThemeTokens = { fontFamily: "IBM Plex Sans, Segoe UI, Helvetica Neue, Arial, sans-serif", nodeSurface: "#1e293b", - nodeSurfaceMuted: "#334155", rootSurface: "#020617", rootText: "#f8fafc", iconColor: "#cbd5e1", iconMutedColor: "#cbd5e1", levelSurfaceMix: "#0f172a", levelSurfaceMixRatio: 0.7, - toggleActiveSurface: "#334155", }; -export type CyvestElkDirection = "RIGHT" | "LEFT" | "UP" | "DOWN"; - export interface CyvestForceOptions { linkDistance?: number; linkStrength?: number; @@ -97,11 +85,8 @@ export interface CyvestForceOptions { animationDuration?: number; } -/** @deprecated Use CyvestForceOptions. */ -export type CyvestElkOptions = CyvestForceOptions; - export interface CyNodeSelectEvent { - view: CyvestViewMode; + view: "observables"; nodeId: string; nodeType: string; label: string; @@ -110,7 +95,7 @@ export interface CyNodeSelectEvent { } export interface CyEdgeSelectEvent { - view: CyvestViewMode; + view: "observables"; edgeId: string; sourceId: string; targetId: string; @@ -125,6 +110,8 @@ export interface CyvestBaseViewProps { width?: number | string; className?: string; theme?: Partial; + /** Keep the d3-force simulation active and reheat it while nodes are dragged. */ + physics?: boolean; onCyReady?: (cy: Core) => void; onNodeSelect?: (event: CyNodeSelectEvent) => void; onEdgeSelect?: (event: CyEdgeSelectEvent) => void; @@ -136,21 +123,10 @@ export interface CyvestObservablesViewProps extends CyvestBaseViewProps { maxLabelLength?: number; } -export interface CyvestInvestigationViewProps extends CyvestBaseViewProps { - layout?: CyvestForceOptions; - showToolbar?: boolean; - maxLabelLength?: number; -} - export interface CyvestGraphProps extends CyvestBaseViewProps { - initialView?: CyvestViewMode; - showViewToggle?: boolean; - onViewChange?: (view: CyvestViewMode) => void; showToolbar?: boolean; - observablesLayout?: CyvestForceOptions; - investigationLayout?: CyvestForceOptions; - maxObservableLabelLength?: number; - maxInvestigationLabelLength?: number; + layout?: CyvestForceOptions; + maxLabelLength?: number; } export interface ObservableCyNodeData extends Record { @@ -183,30 +159,3 @@ export interface ObservableCyEdgeData extends Record { sourceArrowShape: "none" | "triangle"; targetArrowShape: "none" | "triangle"; } - -export type InvestigationCyNodeType = "root" | "tag" | "finding" | "evidence"; - -export interface InvestigationCyNodeData extends Record { - id: string; - nodeType: InvestigationCyNodeType; - labelShort: string; - labelFull: string; - level: Level; - score: number; - borderColor: string; - fillColor: string; - icon: string; - width: number; - height: number; - shape: "ellipse" | "round-rectangle" | "diamond"; - borderWidth: number; -} - -export interface InvestigationCyEdgeData extends Record { - id: string; - relationshipType: string; - color: string; - width: number; - sourceArrowShape: "none" | "triangle"; - targetArrowShape: "none" | "triangle"; -} diff --git a/js/packages/cyvest-vis/tests/observables.test.ts b/js/packages/cyvest-vis/tests/observables.test.ts index 4d5cdb2..e60e674 100644 --- a/js/packages/cyvest-vis/tests/observables.test.ts +++ b/js/packages/cyvest-vis/tests/observables.test.ts @@ -1,13 +1,9 @@ import { describe, expect, it } from "vitest"; -import { parseCyvest, type CyvestInvestigation } from "@cyvest/cyvest-js"; +import { parseCyvest } from "@cyvest/cyvest-js"; -import { buildInvestigationElements } from "../src/adapters/investigationElements"; import { buildObservablesElements } from "../src/adapters/observablesElements"; -import { - getInvestigationIconSvg, - getObservableIconSvg, -} from "../src/icons/svg"; +import { getObservableIconSvg } from "../src/icons/svg"; import { computeForcePositions } from "../src/layout/force"; import { getLevelColor } from "../src/utils/colors"; import { truncateLabel } from "../src/utils/labels"; @@ -25,12 +21,10 @@ describe("label utilities", () => { }); describe("icon helpers", () => { - it("creates data URI icons for observables and investigation nodes", () => { + it("creates data URI icons for observables", () => { const observableIcon = getObservableIconSvg("domain"); - const investigationIcon = getInvestigationIconSvg("tag"); expect(observableIcon.startsWith("data:image/svg+xml;utf8,")).toBe(true); - expect(investigationIcon.startsWith("data:image/svg+xml;utf8,")).toBe(true); }); }); @@ -48,68 +42,13 @@ describe("cytoscape adapters", () => { expect(typeof firstNodeData.labelShort).toBe("string"); expect(typeof firstNodeData.icon).toBe("string"); }); - - it("builds investigation hierarchy nodes and edges", () => { - const elements = buildInvestigationElements(investigation); - - const nodes = elements.filter((item) => item.group === "nodes"); - const edges = elements.filter((item) => item.group === "edges"); - - expect(nodes.length).toBeGreaterThan(0); - expect(edges.length).toBeGreaterThan(0); - - const rootNode = nodes.find( - (node) => (node.data as Record).nodeType === "root" - ); - - expect(rootNode).toBeDefined(); - }); - - it("uses compact neutral shapes for investigation entities", () => { - const withEvidence = structuredClone(investigation) as CyvestInvestigation; - const finding = Object.values(withEvidence.findings)[0]; - const evidenceKey = "evd:test:event-1"; - withEvidence.evidences[evidenceKey] = { - type: "event", - title: "Process event", - description: "", - source: "test", - external_id: "event-1", - content: { pid: 42 }, - uri: null, - captured_at: "2026-06-10T20:00:00Z", - extra: {}, - key: evidenceKey, - finding_links: finding ? [finding.key] : [], - }; - if (finding) { - finding.evidence_links.push({ evidence_key: evidenceKey }); - } - - const elements = buildInvestigationElements(withEvidence); - const nodes = elements.filter((item) => item.group === "nodes"); - const root = nodes.find( - (node) => (node.data as Record).nodeType === "root" - ); - const findingNode = nodes.find( - (node) => (node.data as Record).nodeType === "finding" - ); - const evidence = nodes.find( - (node) => (node.data as Record).nodeType === "evidence" - ); - - expect(root?.data.shape).toBe("ellipse"); - expect(root?.data.fillColor).toBe("#1e293b"); - expect(findingNode?.data.width).toBeLessThanOrEqual(42); - expect(evidence?.data.shape).toBe("round-rectangle"); - }); }); describe("force-directed layout", () => { it("computes deterministic finite positions around the root", () => { const elements = buildObservablesElements(investigation); - const first = computeForcePositions(elements, "observables"); - const second = computeForcePositions(elements, "observables"); + const first = computeForcePositions(elements); + const second = computeForcePositions(elements); const root = elements.find( (element) => element.group === "nodes" &&