diff --git a/package-lock.json b/package-lock.json index 234c55a..e1e6ded 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ioai/rosview", - "version": "1.7.9", + "version": "1.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ioai/rosview", - "version": "1.7.9", + "version": "1.8.0", "license": "MIT", "devDependencies": { "@eslint/js": "^10.0.1", @@ -24,66 +24,66 @@ "@mcap/browser": "^1.1.0", "@mcap/core": "^2.0.2", "@microsoft/api-extractor": "^7.58.12", - "@playwright/test": "^1.59.1", - "@radix-ui/react-collapsible": "^1.1.12", - "@radix-ui/react-dialog": "^1.1.5", - "@radix-ui/react-dropdown-menu": "^2.1.5", - "@radix-ui/react-menubar": "^1.1.16", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-separator": "^1.1.1", - "@radix-ui/react-slider": "^1.2.2", - "@radix-ui/react-slot": "^1.1.1", - "@radix-ui/react-tabs": "^1.1.2", - "@react-three/drei": "^10.0.0", - "@react-three/fiber": "^9.0.0", + "@playwright/test": "^1.62.1", + "@radix-ui/react-collapsible": "^1.1.20", + "@radix-ui/react-dialog": "^1.1.23", + "@radix-ui/react-dropdown-menu": "^2.1.24", + "@radix-ui/react-menubar": "^1.1.24", + "@radix-ui/react-popover": "^1.1.23", + "@radix-ui/react-select": "^2.3.7", + "@radix-ui/react-separator": "^1.1.15", + "@radix-ui/react-slider": "^1.4.7", + "@radix-ui/react-slot": "^1.3.3", + "@radix-ui/react-tabs": "^1.1.21", + "@react-three/drei": "^10.7.8", + "@react-three/fiber": "^9.7.0", "@types/cytoscape": "^3.21.9", - "@types/node": "^25.6.0", - "@types/react": "^19.2.15", - "@types/react-dom": "^19.2.3", + "@types/node": "^26.2.0", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", "@types/sql.js": "^1.4.11", - "@types/three": "^0.173.0", - "@vitejs/plugin-react": "^6.0.1", - "@vitest/coverage-v8": "^4.1.10", - "autoprefixer": "^10.4.23", + "@types/three": "^0.185.4", + "@vitejs/plugin-react": "^6.0.5", + "@vitest/coverage-v8": "^4.1.11", + "autoprefixer": "^10.5.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "comlink": "^4.4.1", - "cytoscape": "^3.33.2", + "cytoscape": "^3.34.1", "dockview": "^6.0.7", "dockview-core": "^6.0.7", - "eslint": "^10.8.0", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.5.2", + "eslint": "^10.8.1", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.4", "eventemitter3": "^5.0.1", "fflate": "^0.8.2", "flatbuffers": "^25.9.23", - "globals": "^17.4.0", - "happy-dom": "^20.9.0", + "globals": "^17.11.0", + "happy-dom": "^20.11.2", "intervals-fn": "^3.0.3", - "lucide-react": "^0.474.0", + "lucide-react": "^1.32.0", "lz4js": "^0.2.0", - "postcss": "^8.5.6", - "protobufjs": "^8.0.1", - "react": "^19.2.6", - "react-dom": "^19.2.6", - "react-intl": "^10.1.2", - "react-resizable-panels": "^4.10.0", - "sonner": "^2.0.7", - "sql.js": "^1.14.1", + "postcss": "^8.5.26", + "protobufjs": "^8.7.2", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "react-intl": "^10.1.22", + "react-resizable-panels": "^4.12.3", + "sonner": "^2.0.8", + "sql.js": "^1.14.2", "tailwind-merge": "^3.5.0", "tailwindcss": "^3.4.17", - "three": "^0.173.0", + "three": "^0.185.1", "typescript": "6.0.3", - "typescript-eslint": "^8.58.0", + "typescript-eslint": "^8.67.0", "uplot": "^1.6.31", - "vite": "^8.0.14", - "vitest": "^4.1.4", - "zustand": "^5.0.3" + "vite": "^8.2.1", + "vitest": "^4.1.11", + "zustand": "^5.0.15" }, "engines": { - "node": ">=22.0.0" + "node": ">=22.12.0" }, "peerDependencies": { "@react-three/drei": "^10.0.0", @@ -377,39 +377,12 @@ "node": ">=18" } }, - "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "node_modules/@dimforge/rapier3d-compat": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", + "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } + "license": "Apache-2.0" }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", @@ -589,9 +562,9 @@ "license": "MIT" }, "node_modules/@formatjs/icu-messageformat-parser": { - "version": "3.5.14", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.14.tgz", - "integrity": "sha512-jDvgtoLqe3U6yzoBlToMTkWBe38qSi7LN7kFlnXzd5ig8nn+4tSlED0xtEtdYakZVZGJJY2rW1D5xS3BFZh6kA==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.17.tgz", + "integrity": "sha512-cN9jhVqT7u0K9tix43fhjoUwL0nazyW6zsNIXs2QdPADr+nurPfYyssUiMqcSCGlPcCiqnYVxgSn7zBSuI+5Bg==", "dev": true, "license": "MIT", "dependencies": { @@ -606,15 +579,15 @@ "license": "MIT" }, "node_modules/@formatjs/intl": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-4.1.16.tgz", - "integrity": "sha512-EJe0wdORNZyzzqOmZs+sqFTL27iTcgNR6aiNT5+OscakcKvA673MyJ4SO9p5L+RQpPDw3v+jWidpo/Jaz14khg==", + "version": "4.1.19", + "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-4.1.19.tgz", + "integrity": "sha512-bZMROATl+rzfL8wsDZ53i8XWHx5e6rVr8QlGT5GDvHImEUro4WE9pxUd2dobzmI3CtD+zkt/wkEdqcb+3CK8Fg==", "dev": true, "license": "MIT", "dependencies": { "@formatjs/fast-memoize": "3.1.7", - "@formatjs/icu-messageformat-parser": "3.5.14", - "intl-messageformat": "11.2.11" + "@formatjs/icu-messageformat-parser": "3.5.17", + "intl-messageformat": "11.2.14" } }, "node_modules/@foxglove/cdr": { @@ -1069,25 +1042,6 @@ "three": ">= 0.159.0" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.3" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1127,9 +1081,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.139.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", - "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "version": "0.144.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz", + "integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==", "dev": true, "license": "MIT", "funding": { @@ -1137,43 +1091,43 @@ } }, "node_modules/@playwright/test": { - "version": "1.61.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", - "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz", + "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.61.1" + "playwright": "1.62.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@radix-ui/number": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz", - "integrity": "sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz", + "integrity": "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==", "dev": true, "license": "MIT" }, "node_modules/@radix-ui/primitive": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.5.tgz", - "integrity": "sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", "dev": true, "license": "MIT" }, "node_modules/@radix-ui/react-arrow": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.11.tgz", - "integrity": "sha512-Kdil9BB1rIFC/khmf4hC35bn8701AJcizTU7G7cUbEbk5XqqbjDuHW60uUfKqO5WojjZcbAW51Q7P0hRmMLw8A==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz", + "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.7" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -1191,20 +1145,20 @@ } }, "node_modules/@radix-ui/react-collapsible": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.16.tgz", - "integrity": "sha512-opfXRe6nnzyGmCDPx+l1Aqo/RbqWtQal2FnsBqF9hhePp6j0LsRoBaRxcMOlTv+uYTJVtWYZKg9t9wTe+BA/ZA==", + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.20.tgz", + "integrity": "sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -1222,16 +1176,16 @@ } }, "node_modules/@radix-ui/react-collection": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.12.tgz", - "integrity": "sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.15.tgz", + "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0" + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", @@ -1249,9 +1203,9 @@ } }, "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz", - "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1265,9 +1219,9 @@ } }, "node_modules/@radix-ui/react-context": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz", - "integrity": "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1281,24 +1235,25 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.19.tgz", - "integrity": "sha512-+HhbN2+YtkRgVirjZ2afMeutQRuGOrdkWR5+EFC58SJojGmtyNQwYzgi6tHBpOxvFHefMtPeHdgtjz0BOGxFQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-dismissable-layer": "1.1.15", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.12", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-controllable-state": "1.2.3", + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", + "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, @@ -1318,9 +1273,9 @@ } }, "node_modules/@radix-ui/react-direction": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz", - "integrity": "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz", + "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1334,17 +1289,17 @@ } }, "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.15.tgz", - "integrity": "sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-effect-event": "0.0.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-effect-event": "0.0.5" }, "peerDependencies": { "@types/react": "*", @@ -1362,19 +1317,19 @@ } }, "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.20", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.20.tgz", - "integrity": "sha512-slfm+rRaZRuQBvHq60lXvSVUPhid0IPtjSZzIuUlWZMUs01iYZNlGS3mJgRD3ChLQVBAYlKiL/tFyWGX+dz8Xw==", + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.24.tgz", + "integrity": "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-menu": "2.1.20", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -1392,9 +1347,9 @@ } }, "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz", - "integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", + "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1408,15 +1363,15 @@ } }, "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.12.tgz", - "integrity": "sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", + "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2" + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -1434,13 +1389,13 @@ } }, "node_modules/@radix-ui/react-id": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz", - "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", + "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -1453,28 +1408,28 @@ } }, "node_modules/@radix-ui/react-menu": { - "version": "2.1.20", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.20.tgz", - "integrity": "sha512-VsUrXxFe9d2ScbZF0fR/oPR1+qjyeLs5p0jzG8h90puMoA9bq4SirYlXbE+USRg9Q2qTeJSFNqjw2nts8jJe4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-collection": "1.1.12", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.15", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.12", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.3", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.15", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-callback-ref": "1.1.2", + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.24.tgz", + "integrity": "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, @@ -1494,22 +1449,22 @@ } }, "node_modules/@radix-ui/react-menubar": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.20.tgz", - "integrity": "sha512-gzFZvybgmwYsFBWDqanycIoEYnhyk8MMnuLamdFVHUZYGp4COM+sqXiwbnn0VMWqGLeeU7GV7jm+dXRa+Wufag==", + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.24.tgz", + "integrity": "sha512-eeVs0vf7cuqXaM0qLQCPcufImiJNVBXdJDLu7ZGYl2732UH23Qat/foNGrr6vYV3/DdTsBqASoggUFgH14OcZA==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-collection": "1.1.12", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-menu": "2.1.20", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.15", - "@radix-ui/react-use-controllable-state": "1.2.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -1527,25 +1482,25 @@ } }, "node_modules/@radix-ui/react-popover": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.19.tgz", - "integrity": "sha512-jkrTdQVxnIB8fpn0NyyxW9CTB5aCXZZelVz5z+Xmii6g5WxMqS3fInNslZ63puP39+Puu4jYohUK31y3dT87gQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-dismissable-layer": "1.1.15", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.12", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.3", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-controllable-state": "1.2.3", + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.23.tgz", + "integrity": "sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, @@ -1565,22 +1520,22 @@ } }, "node_modules/@radix-ui/react-popper": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.3.tgz", - "integrity": "sha512-mS7dGpyjv6b+gsDjLF7e0ia1W4Im1B1hSCy2yuXlHuvnZxHKagfDaobt/KAKt27EpZMit2pss8eJBVyVjEWM+g==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz", + "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==", "dev": true, "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.11", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-rect": "1.1.2", - "@radix-ui/react-use-size": "1.1.2", - "@radix-ui/rect": "1.1.2" + "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-rect": "1.1.4", + "@radix-ui/react-use-size": "1.1.4", + "@radix-ui/rect": "1.1.3" }, "peerDependencies": { "@types/react": "*", @@ -1598,14 +1553,14 @@ } }, "node_modules/@radix-ui/react-portal": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.13.tgz", - "integrity": "sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==", + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", + "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -1623,13 +1578,13 @@ } }, "node_modules/@radix-ui/react-presence": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.7.tgz", - "integrity": "sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -1647,13 +1602,13 @@ } }, "node_modules/@radix-ui/react-primitive": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.7.tgz", - "integrity": "sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==", + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.3.0" + "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", @@ -1671,23 +1626,23 @@ } }, "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.15.tgz", - "integrity": "sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz", + "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-collection": "1.1.12", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-is-hydrated": "0.1.1", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -1705,32 +1660,32 @@ } }, "node_modules/@radix-ui/react-select": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.3.tgz", - "integrity": "sha512-L5RQTXz6Anxsf9CCv+pTgiAsUpyVj7rJxsGtmhFaEOJ++cVfXucv4qWfsIO0AIB4NAhi3yovWGVMKKS1Xf1Wrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.2", - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-collection": "1.1.12", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.15", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.12", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.3", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-previous": "1.1.2", - "@radix-ui/react-visually-hidden": "1.2.7", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.7.tgz", + "integrity": "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, @@ -1750,13 +1705,13 @@ } }, "node_modules/@radix-ui/react-separator": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.11.tgz", - "integrity": "sha512-jRhe86+8PF7VZ1u14eOWVOuh2BuAhALg/FT1VcMC4OHedMTRUazDnDlKTt+yxo5cRNKHMfmvZ4sSQtWDeMV4CQ==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.15.tgz", + "integrity": "sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.7" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -1774,23 +1729,23 @@ } }, "node_modules/@radix-ui/react-slider": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.3.tgz", - "integrity": "sha512-CWVVj+XaTom0SKCqw1EUgb0NuiLwS+N3OFG73mVEezKEjgNIvZiu0EevMelSSU+CbX3owbqJweG2gPU31WGC5A==", + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.7.tgz", + "integrity": "sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/number": "1.1.2", - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-collection": "1.1.12", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-previous": "1.1.2", - "@radix-ui/react-use-size": "1.1.2" + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -1808,13 +1763,13 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz", - "integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3" + "@radix-ui/react-compose-refs": "1.1.5" }, "peerDependencies": { "@types/react": "*", @@ -1827,20 +1782,20 @@ } }, "node_modules/@radix-ui/react-tabs": { - "version": "1.1.17", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.17.tgz", - "integrity": "sha512-nRyXnrAVCwjeXcHbvEbLS6ndbTeKHG1RqCP4A8Gw5L4cemDzPXdD8rAmr6wet0v57R69wGvuIIsFjHSVkZiMzQ==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.21.tgz", + "integrity": "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.15", - "@radix-ui/react-use-controllable-state": "1.2.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -1858,9 +1813,9 @@ } }, "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz", - "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1874,14 +1829,15 @@ } }, "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz", - "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", + "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.3", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -1894,13 +1850,13 @@ } }, "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz", - "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==", + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", + "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -1913,9 +1869,9 @@ } }, "node_modules/@radix-ui/react-use-is-hydrated": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.1.tgz", - "integrity": "sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz", + "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1929,9 +1885,9 @@ } }, "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz", - "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1945,9 +1901,9 @@ } }, "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.2.tgz", - "integrity": "sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.4.tgz", + "integrity": "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1961,13 +1917,13 @@ } }, "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.2.tgz", - "integrity": "sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz", + "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/rect": "1.1.2" + "@radix-ui/rect": "1.1.3" }, "peerDependencies": { "@types/react": "*", @@ -1980,13 +1936,13 @@ } }, "node_modules/@radix-ui/react-use-size": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.2.tgz", - "integrity": "sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz", + "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -1999,13 +1955,13 @@ } }, "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.7.tgz", - "integrity": "sha512-1wNZBggTDK3GRuuQ6nP4k2yi7a6l7I5qbMPbZcRsrGsGVead/f/d5FhEzUvqFs0bcrDLx7n1zKQ3JvLR6whaaw==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz", + "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==", "dev": true, "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.7" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -2023,16 +1979,16 @@ } }, "node_modules/@radix-ui/rect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.2.tgz", - "integrity": "sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz", + "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==", "dev": true, "license": "MIT" }, "node_modules/@react-three/drei": { - "version": "10.7.7", - "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.7.tgz", - "integrity": "sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==", + "version": "10.7.8", + "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.8.tgz", + "integrity": "sha512-rJXyuzLm2Xq0kafHuR47ajDGbOe/pEhzIr4m8E8zwzQs0iNjloFDqBwRhrXmP/w+onLeYyN3EYPFW/cwWK/4yA==", "dev": true, "license": "MIT", "dependencies": { @@ -2071,9 +2027,9 @@ } }, "node_modules/@react-three/fiber": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.6.1.tgz", - "integrity": "sha512-zF0rsKcVYpcJwbFEnv2HkHX9cvOEgsfQo/X8lwmR2dn13S4qEQJXir9fxf5js2LQFoXqxOY7MDkOkYx2uZ4gSg==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.7.0.tgz", + "integrity": "sha512-EWm9FwcaOZQu/ExFW5rggoCMM1NJet5YbxVxKaOE+KSncrjU0Wx7017qSyGFvupviK89nMYGCWU3BIK4dI1clw==", "dev": true, "license": "MIT", "dependencies": { @@ -2120,9 +2076,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", - "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz", + "integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==", "cpu": [ "arm64" ], @@ -2137,9 +2093,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", - "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==", "cpu": [ "arm64" ], @@ -2154,9 +2110,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", - "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz", + "integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==", "cpu": [ "x64" ], @@ -2171,9 +2127,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", - "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz", + "integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==", "cpu": [ "x64" ], @@ -2188,9 +2144,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", - "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz", + "integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==", "cpu": [ "arm" ], @@ -2205,9 +2161,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", - "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz", + "integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==", "cpu": [ "arm64" ], @@ -2222,9 +2178,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", - "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz", + "integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==", "cpu": [ "arm64" ], @@ -2239,9 +2195,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", - "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz", + "integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==", "cpu": [ "ppc64" ], @@ -2256,9 +2212,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", - "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz", + "integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==", "cpu": [ "s390x" ], @@ -2273,9 +2229,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", - "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz", + "integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==", "cpu": [ "x64" ], @@ -2290,9 +2246,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", - "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz", + "integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==", "cpu": [ "x64" ], @@ -2307,9 +2263,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", - "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz", + "integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==", "cpu": [ "arm64" ], @@ -2323,29 +2279,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", - "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.11.1", - "@emnapi/runtime": "1.11.1", - "@napi-rs/wasm-runtime": "^1.1.6" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", - "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz", + "integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==", "cpu": [ "arm64" ], @@ -2360,9 +2297,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", - "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz", + "integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==", "cpu": [ "x64" ], @@ -2521,17 +2458,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/argparse": { "version": "1.0.38", "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", @@ -2600,13 +2526,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.5.tgz", - "integrity": "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==", + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", + "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" + "undici-types": "~8.3.0" } }, "node_modules/@types/offscreencanvas": { @@ -2617,9 +2543,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", - "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", "dev": true, "license": "MIT", "dependencies": { @@ -2627,9 +2553,9 @@ } }, "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -2665,18 +2591,18 @@ "license": "MIT" }, "node_modules/@types/three": { - "version": "0.173.0", - "resolved": "https://registry.npmjs.org/@types/three/-/three-0.173.0.tgz", - "integrity": "sha512-KtNjfI/CRB6JVKIVeZM1R3GYDX2wkoV2itNcQu2j4d7qkhjGOuB+s2oF6jl9mztycDLGMtrAnJQYxInC8Bb20A==", + "version": "0.185.4", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.185.4.tgz", + "integrity": "sha512-gAsBIC07NIFrxjbf7tH2t71c38uulFfk/RFoC7FNBSjMRAQ8J1x/RBvusX0N5PJouaYFJawXQqfCQ0RKUx/1nA==", "dev": true, "license": "MIT", "dependencies": { + "@dimforge/rapier3d-compat": "~0.12.0", "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", - "@types/webxr": "*", - "@webgpu/types": "*", + "@types/webxr": ">=0.5.17", "fflate": "~0.8.2", - "meshoptimizer": "~0.18.1" + "meshoptimizer": "~1.1.1" } }, "node_modules/@types/webxr": { @@ -2704,17 +2630,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", - "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", + "integrity": "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.64.0", - "@typescript-eslint/type-utils": "8.64.0", - "@typescript-eslint/utils": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/type-utils": "8.67.0", + "@typescript-eslint/utils": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -2727,7 +2653,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.64.0", + "@typescript-eslint/parser": "^8.67.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -2743,16 +2669,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", - "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz", + "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.64.0", - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/typescript-estree": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", "debug": "^4.4.3" }, "engines": { @@ -2768,14 +2694,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", - "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", + "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.64.0", - "@typescript-eslint/types": "^8.64.0", + "@typescript-eslint/tsconfig-utils": "^8.67.0", + "@typescript-eslint/types": "^8.67.0", "debug": "^4.4.3" }, "engines": { @@ -2790,14 +2716,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", - "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", + "integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0" + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2808,9 +2734,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", - "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", + "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", "dev": true, "license": "MIT", "engines": { @@ -2825,15 +2751,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz", - "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", + "integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/typescript-estree": "8.64.0", - "@typescript-eslint/utils": "8.64.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/utils": "8.67.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -2850,9 +2776,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", - "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz", + "integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==", "dev": true, "license": "MIT", "engines": { @@ -2864,16 +2790,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", - "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", + "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.64.0", - "@typescript-eslint/tsconfig-utils": "8.64.0", - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0", + "@typescript-eslint/project-service": "8.67.0", + "@typescript-eslint/tsconfig-utils": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -2905,16 +2831,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz", - "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", + "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.64.0", - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/typescript-estree": "8.64.0" + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2929,13 +2855,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", - "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", + "integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/types": "8.67.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -2967,9 +2893,9 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", - "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", + "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", "dev": true, "license": "MIT", "dependencies": { @@ -2993,14 +2919,14 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", - "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.11.tgz", + "integrity": "sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.10", + "@vitest/utils": "4.1.11", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -3014,8 +2940,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.10", - "vitest": "4.1.10" + "@vitest/browser": "4.1.11", + "vitest": "4.1.11" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -3024,16 +2950,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", - "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.10", - "@vitest/utils": "4.1.10", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -3042,13 +2968,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", - "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.10", + "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -3069,9 +2995,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", - "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", "dev": true, "license": "MIT", "dependencies": { @@ -3082,13 +3008,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", - "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.10", + "@vitest/utils": "4.1.11", "pathe": "^2.0.3" }, "funding": { @@ -3096,14 +3022,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", - "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.10", - "@vitest/utils": "4.1.10", + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -3112,9 +3038,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", - "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", "dev": true, "license": "MIT", "funding": { @@ -3122,13 +3048,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", - "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.10", + "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -3136,13 +3062,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@webgpu/types": { - "version": "0.1.71", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.71.tgz", - "integrity": "sha512-mMy8/ODcKhab808co15eW+yN+HgXoQxRQHTiBV9Mrvl1r0ufnid7YOcI+gi4eUWSWl9ezD6TW2KXccrL8HCh2A==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/acorn": { "version": "8.17.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", @@ -3294,9 +3213,9 @@ "license": "MIT" }, "node_modules/autoprefixer": { - "version": "10.5.2", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.2.tgz", - "integrity": "sha512-rD5t5DwOjJdmSORcTq64j8MawTC+tbQ+HHqjR4NDumamy/ambn1UJrlKL+KdwujWxMkFjPM3pPHOEA9tl4767Q==", + "version": "10.5.4", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz", + "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==", "dev": true, "funding": [ { @@ -3314,8 +3233,8 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.28.4", - "caniuse-lite": "^1.0.30001799", + "browserslist": "^4.28.6", + "caniuse-lite": "^1.0.30001806", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -3388,9 +3307,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", - "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "dependencies": { @@ -3520,9 +3439,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001805", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", - "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", "dev": true, "funding": [ { @@ -3707,9 +3626,9 @@ "license": "MIT" }, "node_modules/cytoscape": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.0.tgz", - "integrity": "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==", + "version": "3.34.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.1.tgz", + "integrity": "sha512-Lr0RvH9H75y9ar8h9Toy6u4lxRSCcxUq+hHcQ26sVWo6BnaQp1gwEZOYqwuYTZhyW7npyKnNLP8oJ2p1/3OZ7g==", "dev": true, "license": "MIT", "engines": { @@ -3850,9 +3769,9 @@ } }, "node_modules/es-module-lexer": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", - "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", "dev": true, "license": "MIT" }, @@ -3880,9 +3799,9 @@ } }, "node_modules/eslint": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", - "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", + "version": "10.8.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.1.tgz", + "integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==", "dev": true, "license": "MIT", "workspaces": [ @@ -3959,9 +3878,9 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.3.tgz", - "integrity": "sha512-5EMmLCV98Pi4o/f/3DP/v/tNqLHMIc9I8LKClNDWhZ9JTho89/kQcitCXQBMG7sAfVRK0Ie3T2EDOzp1YXYiVA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.4.tgz", + "integrity": "sha512-7bqTKz7T0r+HKWFarNXByDE9/5+73wI2ru+M3zuqGbR7s/b/5/pQJXZoufWlrngqGqoZto73ZkGumCdLxk+4rw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -4167,9 +4086,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", - "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", "dev": true, "funding": [ { @@ -4359,9 +4278,9 @@ } }, "node_modules/globals": { - "version": "17.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", - "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.11.0.tgz", + "integrity": "sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==", "dev": true, "license": "MIT", "engines": { @@ -4386,9 +4305,9 @@ "license": "ISC" }, "node_modules/happy-dom": { - "version": "20.10.6", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.10.6.tgz", - "integrity": "sha512-6QD0ilzDDt93tX44y8tbmZdAcdTRYDhUP+Asgi6pC8Pp5IA3cvaZGyoVN/EGtlq9ziT65iPuBBn3ASLr6hCgVw==", + "version": "20.11.2", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.11.2.tgz", + "integrity": "sha512-7MB+bJLkxu3SowAfBJbjW+c55kNz5tkR45gu2qzrxznezhLeN5YIlJbwUgSzlGc+qWoZ8Ykg71H5ezz69xixrw==", "dev": true, "license": "MIT", "dependencies": { @@ -4544,14 +4463,14 @@ } }, "node_modules/intl-messageformat": { - "version": "11.2.11", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.11.tgz", - "integrity": "sha512-aDG5bvFRbQvRoT2Bh9FV6yV8t7o0MjEGknZ6pnin5Wt52PJwaBOHDfvz+oPEe78Pl3InQYKugBgCdXijLj6viQ==", + "version": "11.2.14", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.14.tgz", + "integrity": "sha512-9f2VD1HFuxUvMw0RxsaP8WmMns6JRTnsNB/zghTFrp11ZktiXWwVDeZBPQchBKEmo+Gx/ZhxI7Qht7YglFD4PA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@formatjs/fast-memoize": "3.1.7", - "@formatjs/icu-messageformat-parser": "3.5.14" + "@formatjs/icu-messageformat-parser": "3.5.17" } }, "node_modules/is-binary-path": { @@ -4720,9 +4639,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", - "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "dev": true, "funding": [ { @@ -4837,9 +4756,9 @@ } }, "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -4853,23 +4772,23 @@ "url": "https://opencollective.com/parcel" }, "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" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -4888,9 +4807,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], @@ -4909,9 +4828,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], @@ -4930,9 +4849,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], @@ -4951,9 +4870,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], @@ -4972,9 +4891,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], @@ -4993,9 +4912,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], @@ -5014,9 +4933,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], @@ -5035,9 +4954,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], @@ -5056,9 +4975,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -5077,9 +4996,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -5151,9 +5070,9 @@ } }, "node_modules/lucide-react": { - "version": "0.474.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.474.0.tgz", - "integrity": "sha512-CmghgHkh0OJNmxGKWc0qfPJCYHASPMVSyGY8fj3xgk4v84ItqDg64JNKFZn5hC6E0vHi6gxnbCgwhyVB09wQtA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.32.0.tgz", + "integrity": "sha512-txX56hMFnRxPi1f9/nH69YN8uvAO6a7Y1KSWKjCDAtdD9+soEgmWuCt6iRm1pkxUZo2+YntSdsE1L6bIuKoY8Q==", "dev": true, "license": "ISC", "peerDependencies": { @@ -5257,9 +5176,9 @@ } }, "node_modules/meshoptimizer": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", - "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.1.1.tgz", + "integrity": "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==", "dev": true, "license": "MIT" }, @@ -5313,9 +5232,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", - "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "dev": true, "funding": [ { @@ -5379,9 +5298,9 @@ } }, "node_modules/obug": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", - "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -5517,41 +5436,41 @@ } }, "node_modules/playwright": { - "version": "1.61.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", - "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", + "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.61.1" + "playwright-core": "1.62.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" }, "optionalDependencies": { "fsevents": "2.3.2" } }, "node_modules/playwright-core": { - "version": "1.61.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", - "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", "dev": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/postcss": { - "version": "8.5.19", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", - "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "dev": true, "funding": [ { @@ -5569,7 +5488,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -5740,9 +5659,9 @@ } }, "node_modules/protobufjs": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.7.1.tgz", - "integrity": "sha512-agdGHrXNTv0IrYscJPDou/PlEJk1c/hBZ9o/B5NH2i/nSPtPqacNxzgwf1CebXxFMjMrZH5sqv9uQuw96aGt/A==", + "version": "8.7.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.7.2.tgz", + "integrity": "sha512-oTVHV+oelUBtiu5iTuTNNZ0eLYsXSMxry4cgr30mayNkgIZL6qZ0IOQVPuSWGcyAaXKl/XgqwWHIC3a0khYVBA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5791,9 +5710,9 @@ "license": "MIT" }, "node_modules/react": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", - "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "dev": true, "license": "MIT", "engines": { @@ -5801,28 +5720,28 @@ } }, "node_modules/react-dom": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", - "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "dev": true, "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.7" + "react": "^19.2.8" } }, "node_modules/react-intl": { - "version": "10.1.16", - "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-10.1.16.tgz", - "integrity": "sha512-wt9lV390x/Fbqx9A1X4F+daxOzBb2LRybc2vNVS6FpI81TnV/LBlN29W3BBeh2b8cP3aWrofyrAhdViE1AD5pQ==", + "version": "10.1.22", + "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-10.1.22.tgz", + "integrity": "sha512-6PxlTKapYpv/Kl+yYEH06J3T5T7gUVx6STjrWJedeITOAkGEn6sBvb6r0qtjg2pK7PZsom7gXKUFKwNykLoLSQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@formatjs/icu-messageformat-parser": "3.5.14", - "@formatjs/intl": "4.1.16", - "intl-messageformat": "11.2.11" + "@formatjs/icu-messageformat-parser": "3.5.17", + "@formatjs/intl": "4.1.19", + "intl-messageformat": "11.2.14" }, "peerDependencies": { "@types/react": ">=18.0.0", @@ -5879,9 +5798,9 @@ } }, "node_modules/react-resizable-panels": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.12.2.tgz", - "integrity": "sha512-NwY5LCo4WrxVvDh0xoMML6EMLPONP/8ckKcIdpnojxexoatZdjLiRqLJQjQK5CPkd4SYiB/2M5BVrjZBQtOO7Q==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.12.3.tgz", + "integrity": "sha512-GHMJWnDXui/3RX4bT+cgBP+N3N2nkwcoZATr/2xLFpqQQe7TlBrE0cGM0dUa9ceT7A90tUrSRsiqgRG+g0/2AA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -5995,13 +5914,13 @@ } }, "node_modules/rolldown": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", - "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz", + "integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.139.0", + "@oxc-project/types": "=0.144.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -6011,21 +5930,20 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.5", - "@rolldown/binding-darwin-arm64": "1.1.5", - "@rolldown/binding-darwin-x64": "1.1.5", - "@rolldown/binding-freebsd-x64": "1.1.5", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", - "@rolldown/binding-linux-arm64-gnu": "1.1.5", - "@rolldown/binding-linux-arm64-musl": "1.1.5", - "@rolldown/binding-linux-ppc64-gnu": "1.1.5", - "@rolldown/binding-linux-s390x-gnu": "1.1.5", - "@rolldown/binding-linux-x64-gnu": "1.1.5", - "@rolldown/binding-linux-x64-musl": "1.1.5", - "@rolldown/binding-openharmony-arm64": "1.1.5", - "@rolldown/binding-wasm32-wasi": "1.1.5", - "@rolldown/binding-win32-arm64-msvc": "1.1.5", - "@rolldown/binding-win32-x64-msvc": "1.1.5" + "@rolldown/binding-android-arm64": "1.2.4", + "@rolldown/binding-darwin-arm64": "1.2.4", + "@rolldown/binding-darwin-x64": "1.2.4", + "@rolldown/binding-freebsd-x64": "1.2.4", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.4", + "@rolldown/binding-linux-arm64-gnu": "1.2.4", + "@rolldown/binding-linux-arm64-musl": "1.2.4", + "@rolldown/binding-linux-ppc64-gnu": "1.2.4", + "@rolldown/binding-linux-s390x-gnu": "1.2.4", + "@rolldown/binding-linux-x64-gnu": "1.2.4", + "@rolldown/binding-linux-x64-musl": "1.2.4", + "@rolldown/binding-openharmony-arm64": "1.2.4", + "@rolldown/binding-win32-arm64-msvc": "1.2.4", + "@rolldown/binding-win32-x64-msvc": "1.2.4" } }, "node_modules/run-parallel": { @@ -6100,14 +6018,20 @@ "license": "ISC" }, "node_modules/sonner": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", - "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.8.tgz", + "integrity": "sha512-UM/ByIoFra8yzV75n1o0Puu0bw5U/9UNnDacrJNspekBewIfsQ3D6ez1nvlWpt7aTsO6rujQtifBpycwIivqlg==", "dev": true, "license": "MIT", "peerDependencies": { + "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/source-map": { @@ -6138,9 +6062,9 @@ "license": "BSD-3-Clause" }, "node_modules/sql.js": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.14.1.tgz", - "integrity": "sha512-gcj8zBWU5cFsi9WUP+4bFNXAyF1iRpA3LLyS/DP5xlrNzGmPIizUeBggKa8DbDwdqaKwUcTEnChtd2grWo/x/A==", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.14.2.tgz", + "integrity": "sha512-3ZGPovObMFrdw79zrUHbfdE/DLIsy8jdNdssmMSQuRAymedU6q84asPt0kgiqrdMYlPegDItiIMfmIXzZnYFcw==", "dev": true, "license": "MIT" }, @@ -6332,9 +6256,9 @@ } }, "node_modules/three": { - "version": "0.173.0", - "resolved": "https://registry.npmjs.org/three/-/three-0.173.0.tgz", - "integrity": "sha512-AUwVmViIEUgBwxJJ7stnF0NkPpZxx1aZ6WiAbQ/Qq61h6I9UR4grXtZDmO8mnlaNORhHnIBlXJ1uBxILEKuVyw==", + "version": "0.185.1", + "resolved": "https://registry.npmjs.org/three/-/three-0.185.1.tgz", + "integrity": "sha512-5aojFCXKwnjBRZvUnt3WFfEcvUJgkN5LlijRFN95hMy8WVkG4I0QNcJE+OuWvuJ0bOdStrbfXn0pkd6/QyiAlg==", "dev": true, "license": "MIT" }, @@ -6381,9 +6305,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", - "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", "dev": true, "license": "MIT", "engines": { @@ -6439,9 +6363,9 @@ } }, "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", "dev": true, "license": "MIT", "engines": { @@ -6588,16 +6512,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.64.0.tgz", - "integrity": "sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.67.0.tgz", + "integrity": "sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.64.0", - "@typescript-eslint/parser": "8.64.0", - "@typescript-eslint/typescript-estree": "8.64.0", - "@typescript-eslint/utils": "8.64.0" + "@typescript-eslint/eslint-plugin": "8.67.0", + "@typescript-eslint/parser": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/utils": "8.67.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6612,9 +6536,9 @@ } }, "node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", "dev": true, "license": "MIT" }, @@ -6749,16 +6673,16 @@ } }, "node_modules/vite": { - "version": "8.1.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz", - "integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz", + "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", + "lightningcss": "^1.33.0", "picomatch": "^4.0.5", - "postcss": "^8.5.16", - "rolldown": "~1.1.4", + "postcss": "^8.5.25", + "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "bin": { @@ -6775,7 +6699,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.3.0", + "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -6855,19 +6779,19 @@ } }, "node_modules/vitest": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", - "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.10", - "@vitest/mocker": "4.1.10", - "@vitest/pretty-format": "4.1.10", - "@vitest/runner": "4.1.10", - "@vitest/snapshot": "4.1.10", - "@vitest/spy": "4.1.10", - "@vitest/utils": "4.1.10", + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -6895,12 +6819,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.10", - "@vitest/browser-preview": "4.1.10", - "@vitest/browser-webdriverio": "4.1.10", - "@vitest/coverage-istanbul": "4.1.10", - "@vitest/coverage-v8": "4.1.10", - "@vitest/ui": "4.1.10", + "@vitest/browser-playwright": "4.1.11", + "@vitest/browser-preview": "4.1.11", + "@vitest/browser-webdriverio": "4.1.11", + "@vitest/coverage-istanbul": "4.1.11", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -7024,9 +6948,9 @@ } }, "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", "dev": true, "license": "MIT", "engines": { @@ -7089,9 +7013,9 @@ } }, "node_modules/zustand": { - "version": "5.0.14", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", - "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.15.tgz", + "integrity": "sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index b758ab5..c1092dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ioai/rosview", - "version": "1.7.9", + "version": "1.8.0", "description": "High-performance robotics data visualization for MCAP, ROS bag, ROS2 db3, HDF5 and BVH — embeddable React component and standalone SPA", "keywords": [ "ros", @@ -104,62 +104,62 @@ "@mcap/browser": "^1.1.0", "@mcap/core": "^2.0.2", "@microsoft/api-extractor": "^7.58.12", - "@playwright/test": "^1.59.1", - "@radix-ui/react-collapsible": "^1.1.12", - "@radix-ui/react-dialog": "^1.1.5", - "@radix-ui/react-dropdown-menu": "^2.1.5", - "@radix-ui/react-menubar": "^1.1.16", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-separator": "^1.1.1", - "@radix-ui/react-slider": "^1.2.2", - "@radix-ui/react-slot": "^1.1.1", - "@radix-ui/react-tabs": "^1.1.2", - "@react-three/drei": "^10.0.0", - "@react-three/fiber": "^9.0.0", + "@playwright/test": "^1.62.1", + "@radix-ui/react-collapsible": "^1.1.20", + "@radix-ui/react-dialog": "^1.1.23", + "@radix-ui/react-dropdown-menu": "^2.1.24", + "@radix-ui/react-menubar": "^1.1.24", + "@radix-ui/react-popover": "^1.1.23", + "@radix-ui/react-select": "^2.3.7", + "@radix-ui/react-separator": "^1.1.15", + "@radix-ui/react-slider": "^1.4.7", + "@radix-ui/react-slot": "^1.3.3", + "@radix-ui/react-tabs": "^1.1.21", + "@react-three/drei": "^10.7.8", + "@react-three/fiber": "^9.7.0", "@types/cytoscape": "^3.21.9", - "@types/node": "^25.6.0", - "@types/react": "^19.2.15", - "@types/react-dom": "^19.2.3", + "@types/node": "^26.2.0", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", "@types/sql.js": "^1.4.11", - "@types/three": "^0.173.0", - "@vitejs/plugin-react": "^6.0.1", - "@vitest/coverage-v8": "^4.1.10", - "autoprefixer": "^10.4.23", + "@types/three": "^0.185.4", + "@vitejs/plugin-react": "^6.0.5", + "@vitest/coverage-v8": "^4.1.11", + "autoprefixer": "^10.5.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "comlink": "^4.4.1", - "cytoscape": "^3.33.2", + "cytoscape": "^3.34.1", "dockview": "^6.0.7", "dockview-core": "^6.0.7", - "eslint": "^10.8.0", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.5.2", + "eslint": "^10.8.1", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.4", "eventemitter3": "^5.0.1", "fflate": "^0.8.2", "flatbuffers": "^25.9.23", - "globals": "^17.4.0", - "happy-dom": "^20.9.0", + "globals": "^17.11.0", + "happy-dom": "^20.11.2", "intervals-fn": "^3.0.3", - "lucide-react": "^0.474.0", + "lucide-react": "^1.32.0", "lz4js": "^0.2.0", - "postcss": "^8.5.6", - "protobufjs": "^8.0.1", - "react": "^19.2.6", - "react-dom": "^19.2.6", - "react-intl": "^10.1.2", - "react-resizable-panels": "^4.10.0", - "sonner": "^2.0.7", - "sql.js": "^1.14.1", + "postcss": "^8.5.26", + "protobufjs": "^8.7.2", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "react-intl": "^10.1.22", + "react-resizable-panels": "^4.12.3", + "sonner": "^2.0.8", + "sql.js": "^1.14.2", "tailwind-merge": "^3.5.0", "tailwindcss": "^3.4.17", - "three": "^0.173.0", + "three": "^0.185.1", "typescript": "6.0.3", - "typescript-eslint": "^8.58.0", + "typescript-eslint": "^8.67.0", "uplot": "^1.6.31", - "vite": "^8.0.14", - "vitest": "^4.1.4", - "zustand": "^5.0.3" + "vite": "^8.2.1", + "vitest": "^4.1.11", + "zustand": "^5.0.15" } } diff --git a/src/core/players/IterablePlayer.test.ts b/src/core/players/IterablePlayer.test.ts index c7874e5..15a4f73 100644 --- a/src/core/players/IterablePlayer.test.ts +++ b/src/core/players/IterablePlayer.test.ts @@ -5,6 +5,7 @@ import { useMessagePipelineStore } from '@/core/pipeline/store'; import type { Initialization, MessageEvent } from '@/core/types/ros'; import type { PlayerState } from '@/core/types/player'; import type { WorkerSerializedSource } from '@/infra/workers/WorkerSerializedSource'; +import type { SourceInitProgressCallback } from '@/infra/workers/types'; const TOPIC = '/camera/front/image/compressed'; @@ -1216,3 +1217,34 @@ describe('IterablePlayer topic metadata', () => { player.close(); }); }); + +describe('IterablePlayer initialize progress', () => { + it('emits loadedBytes while presence is still initializing', async () => { + const gate = deferred(); + const source = makeSource([]); + source.initialize = vi.fn(async (_args: Record, onProgress?: SourceInitProgressCallback) => { + onProgress?.({ phase: 'downloading', loadedBytes: 12, totalBytes: 100, transferredBytes: 12 }); + await gate.promise; + return makeInitialization(); + }); + + const player = new IterablePlayer(source); + let latestState: PlayerState | undefined; + player.setListener((state) => { + latestState = state; + }); + + const pending = player.initialize({}); + await flushAsyncWork(); + + expect(latestState?.presence).toBe('initializing'); + expect(latestState?.progress.loadedBytes).toBe(12); + expect(latestState?.progress.totalBytes).toBe(100); + expect(latestState?.progress.initPhase).toBe('downloading'); + expect(useMessagePipelineStore.getState().playerState.progress.loadedBytes).toBe(12); + + gate.resolve(makeInitialization()); + await pending; + player.close(); + }); +}); diff --git a/src/core/players/IterablePlayer.ts b/src/core/players/IterablePlayer.ts index 5711ddb..be9b590 100644 --- a/src/core/players/IterablePlayer.ts +++ b/src/core/players/IterablePlayer.ts @@ -9,7 +9,7 @@ import type { import { MAX_PLAYBACK_SPEED } from '@/core/types/player'; import type { DataQualityReport, Time, Initialization, MessageEvent, TimeRange } from '@/core/types/ros'; import type { ISourceHandle } from '@/infra/workers/ISourceHandle'; -import type { IMessageCursor } from '@/infra/workers/types'; +import type { IMessageCursor, SourceInitProgress } from '@/infra/workers/types'; import { addMs, toNano } from '@/shared/utils/time'; import { useMessagePipelineStore } from '@/core/pipeline/store'; import { messageBus } from '@/core/pipeline/messageBus'; @@ -387,7 +387,9 @@ export class IterablePlayer implements Player { try { this._advancePlaybackEpoch(); - this._initialization = await this._source.initialize(args); + this._initialization = await this._source.initialize(args, (progress) => { + this._applyInitProgress(progress); + }); this._topicLastMessageNs.clear(); this._currentTime = this._initialization.start; this._state.presence = "ready"; @@ -1194,6 +1196,25 @@ export class IterablePlayer implements Player { return time; } + private _applyInitProgress(progress: SourceInitProgress): void { + if (this._state.presence !== "initializing") { + return; + } + const percent = + progress.totalBytes > 0 + ? Math.min(100, (progress.loadedBytes / progress.totalBytes) * 100) + : undefined; + this._state.progress = { + ...this._state.progress, + initPhase: progress.phase, + loadedBytes: progress.loadedBytes, + totalBytes: progress.totalBytes, + transferredBytes: progress.transferredBytes, + percent, + }; + this._emitState(); + } + private _startLoadProgressPolling(): void { this._stopLoadProgressPolling(); this._loadProgressPollId = globalThis.setInterval(() => { diff --git a/src/core/types/player.ts b/src/core/types/player.ts index fb57653..304e097 100644 --- a/src/core/types/player.ts +++ b/src/core/types/player.ts @@ -24,6 +24,9 @@ export interface Readable { export type PlayerPresence = 'preinit' | 'initializing' | 'ready' | 'closed'; +/** Worker-reported phase while a remote (or large local) source is opening. */ +export type SourceInitPhase = 'connecting' | 'downloading' | 'opening'; + export interface PlayerState { presence: PlayerPresence; progress: { @@ -33,6 +36,12 @@ export interface PlayerState { downloadedByteRanges?: Range[]; /** Total source bytes for byte-range diagnostics. */ totalBytes?: number; + /** Bytes received in the current HTTP range (or full-file download). */ + loadedBytes?: number; + /** Session-cumulative HTTP bytes transferred while opening. */ + transferredBytes?: number; + /** Coarse initialize phase for the loading overlay. */ + initPhase?: SourceInitPhase; /** Parsed/playable time ranges rendered on the playback track. */ parsedMessageRanges?: TimeRange[]; /** Current worker transport mode. */ diff --git a/src/features/layout/PanelTabHeader.tsx b/src/features/layout/PanelTabHeader.tsx index 2041f18..55234fd 100644 --- a/src/features/layout/PanelTabHeader.tsx +++ b/src/features/layout/PanelTabHeader.tsx @@ -4,7 +4,14 @@ import { useIntl } from 'react-intl'; import { Separator } from '@/shared/ui/separator'; import { useElementWidth } from '@/shared/hooks/useElementWidth'; import type { PanelType } from '../panels/framework'; -import { getPanelTypeFromId, listPanelStates, usePanelActions } from '../panels/framework'; +import { + extractPrimaryTopicFromConfig, + getPanelConfig, + getPanelState, + getPanelTypeFromId, + listPanelStates, + usePanelActions, +} from '../panels/framework'; import { PANEL_TYPE_MESSAGE_SLUG } from '../panels/framework/panelMessageSlug'; import { PanelTypeIcon } from '../panels/framework/panelIcons'; import { @@ -98,9 +105,15 @@ export const PanelTabHeader: React.FC = ({ api, conta const addPanelWithPlacement = useCallback( (type: PanelType, placement: 'replace' | 'right' | 'below' | 'within') => { + const sourceConfig = getPanelConfig(api.id) ?? getPanelState(api.id)?.config; + const inheritedTopic = + type === 'RawMessages' ? extractPrimaryTopicFromConfig(sourceConfig) : undefined; + const config = inheritedTopic ? { topic: inheritedTopic } : undefined; + if (placement === 'replace') { openDockviewPanel({ type, + config, position: { referencePanel: api.id, direction: 'within' }, }); window.setTimeout(() => { @@ -110,6 +123,7 @@ export const PanelTabHeader: React.FC = ({ api, conta } openDockviewPanel({ type, + config, position: { referencePanel: api.id, direction: placement }, }); }, diff --git a/src/features/panels/RawMessages/RawMessagesPanel.tsx b/src/features/panels/RawMessages/RawMessagesPanel.tsx index c624cec..54942c0 100644 --- a/src/features/panels/RawMessages/RawMessagesPanel.tsx +++ b/src/features/panels/RawMessages/RawMessagesPanel.tsx @@ -14,7 +14,7 @@ import { scheduleFrame } from '@/shared/utils/rafScheduler'; import { TopicQuickPicker } from '../framework/TopicQuickPicker'; import { PanelTopicBar } from '../framework/PanelTopicBar'; import type { RawMessagesConfig } from './defaults'; -import { buildRowsForMessageEvent, type FlatRow } from './shapeTree'; +import { buildRowsForMessageEvent, isExpandableArray, type FlatRow } from './shapeTree'; interface RawMessagesPanelProps { player: Player; @@ -110,7 +110,7 @@ function readValueAtPath(event: MessageEvent | null | undefined, path: string): const parts = pathToParts(path.replace(/^message\./, '')); let current: unknown = event.message; for (const part of parts) { - if (Array.isArray(current)) { + if (isExpandableArray(current)) { const idx = Number(part); if (!Number.isInteger(idx)) return undefined; current = current[idx]; @@ -158,7 +158,7 @@ function previewPrimitive(value: unknown): string { if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (value instanceof Uint8Array) return `Uint8Array(${value.byteLength})`; if (value instanceof ArrayBuffer) return `ArrayBuffer(${value.byteLength})`; - if (Array.isArray(value)) return `Array(${value.length})`; + if (isExpandableArray(value)) return `Array(${value.length})`; if (isPlainObject(value)) return `{${Object.keys(value).length} keys}`; if (typeof value === 'bigint') return `${value.toString()}n`; if (typeof value === 'function') return '[Function]'; @@ -206,7 +206,7 @@ export function describeValue( if (value instanceof ArrayBuffer) { return describeValue(new Uint8Array(value), maxBinaryPreviewBytes, options); } - if (Array.isArray(value)) return { text: `Array(${value.length})`, kind: 'array' }; + if (isExpandableArray(value)) return { text: `Array(${value.length})`, kind: 'array' }; if (isPlainObject(value)) { return { text: previewObject(value) ?? `{${Object.keys(value).length} keys}`, kind: 'object' }; } @@ -257,7 +257,9 @@ function serializeForCopy(value: unknown, binaryFormat: BinaryCopyFormat): unkno return { __type: 'Uint8Array', data: Array.from(value) }; } if (value instanceof ArrayBuffer) return serializeForCopy(new Uint8Array(value), binaryFormat); - if (Array.isArray(value)) return value.map((entry) => serializeForCopy(entry, binaryFormat)); + if (isExpandableArray(value)) { + return Array.from(value, (entry) => serializeForCopy(entry, binaryFormat)); + } if (isPlainObject(value)) { const out: Record = {}; for (const [key, entry] of Object.entries(value)) out[key] = serializeForCopy(entry, binaryFormat); diff --git a/src/features/panels/RawMessages/describeValue.test.ts b/src/features/panels/RawMessages/describeValue.test.ts index 6c474ca..56d74ab 100644 --- a/src/features/panels/RawMessages/describeValue.test.ts +++ b/src/features/panels/RawMessages/describeValue.test.ts @@ -35,4 +35,16 @@ describe('describeValue', () => { expect(visual.text).toContain('100.500000000'); expect(visual.kind).toBe('number'); }); + + it('describes Float64Array as an expandable Array(n) preview', () => { + const visual = describeValue(new Float64Array(16), 256); + expect(visual.text).toBe('Array(16)'); + expect(visual.kind).toBe('array'); + }); + + it('still treats Uint8Array as binary', () => { + const visual = describeValue(new Uint8Array([0x00, 0x01]), 256); + expect(visual.kind).toBe('binary'); + expect(visual.text).toBe('Uint8Array(2) 0x0001'); + }); }); diff --git a/src/features/panels/RawMessages/shapeTree.test.ts b/src/features/panels/RawMessages/shapeTree.test.ts index cb3c529..0f27a34 100644 --- a/src/features/panels/RawMessages/shapeTree.test.ts +++ b/src/features/panels/RawMessages/shapeTree.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import type { MessageEvent } from '@/core/types/ros'; -import { buildRowsForMessageEvent } from './shapeTree'; +import { buildRowsForMessageEvent, buildRowsForShape } from './shapeTree'; describe('buildRowsForMessageEvent', () => { it('includes log_time and publish_time rows before message', () => { @@ -17,3 +17,35 @@ describe('buildRowsForMessageEvent', () => { expect(shape.signature.startsWith('log_time:time|publish_time:time|')).toBe(true); }); }); + +describe('buildRowsForShape typed arrays', () => { + it('expands Float64Array fields the same way as regular arrays', () => { + const { rows } = buildRowsForShape( + { + name: ['abad_L_Joint', 'hip_L_Joint'], + position: new Float64Array([-1.6e-10, 0.25]), + }, + 4, + 2000, + ); + const byPath = new Map(rows.map((row) => [row.path, row])); + + expect(byPath.get('message.position')).toMatchObject({ + expandable: true, + parentIsArray: false, + }); + expect(byPath.get('message.position.0')).toMatchObject({ + key: '0', + expandable: false, + parentIsArray: true, + }); + expect(byPath.has('message.position.1')).toBe(true); + }); + + it('keeps Uint8Array payloads as a non-expandable binary leaf', () => { + const { rows } = buildRowsForShape({ data: new Uint8Array([1, 2, 3, 4]) }, 4, 2000); + const dataRow = rows.find((row) => row.path === 'message.data'); + expect(dataRow).toMatchObject({ expandable: false }); + expect(rows.some((row) => row.path === 'message.data.0')).toBe(false); + }); +}); diff --git a/src/features/panels/RawMessages/shapeTree.ts b/src/features/panels/RawMessages/shapeTree.ts index 65c32c5..afd4669 100644 --- a/src/features/panels/RawMessages/shapeTree.ts +++ b/src/features/panels/RawMessages/shapeTree.ts @@ -25,6 +25,12 @@ function isPlainObject(value: unknown): value is Record { return proto === Object.prototype || proto === null; } +/** Arrays and numeric TypedArrays. Uint8Array stays binary so image payloads do not explode. */ +export function isExpandableArray(value: unknown): value is ArrayLike { + if (Array.isArray(value)) return true; + return ArrayBuffer.isView(value) && !(value instanceof DataView) && !(value instanceof Uint8Array); +} + export function buildRowsForShape( root: unknown, maxExpandedDepth: number, @@ -46,7 +52,7 @@ export function buildRowsForShape( ? 'u8' : value instanceof ArrayBuffer ? 'ab' - : Array.isArray(value) + : isExpandableArray(value) ? 'arr' : isPlainObject(value) ? 'obj' @@ -61,7 +67,7 @@ export function buildRowsForShape( walk(new Uint8Array(value), path, key, depth, parentIsArray); return; } - if (Array.isArray(value)) { + if (isExpandableArray(value)) { const expandable = value.length > 0; pushRow({ id: path, path, key, depth, expandable, parentIsArray }); if (depth >= maxExpandedDepth) return; diff --git a/src/features/panels/ThreeD/core/renderables.ts b/src/features/panels/ThreeD/core/renderables.ts index 20c1c3e..d873bb8 100644 --- a/src/features/panels/ThreeD/core/renderables.ts +++ b/src/features/panels/ThreeD/core/renderables.ts @@ -556,6 +556,9 @@ function loadCollada(text: string, meshUrl: string, meshUpAxis: MeshUpAxis): THR const xml = new DOMParser().parseFromString(text, 'application/xml'); const upAxis = (xml.querySelector('up_axis')?.textContent ?? 'Y_UP').trim().toUpperCase(); const collada = new ColladaLoader().parse(xml.documentElement.outerHTML, meshUrl); + if (!collada) { + throw new Error(`Failed to parse Collada mesh: ${meshUrl}`); + } if (meshUpAxis === 'y_up' && upAxis === 'Y_UP') { collada.scene.rotateX(Math.PI / 2); } diff --git a/src/features/panels/framework/extractPrimaryTopicFromConfig.test.ts b/src/features/panels/framework/extractPrimaryTopicFromConfig.test.ts new file mode 100644 index 0000000..0f807ee --- /dev/null +++ b/src/features/panels/framework/extractPrimaryTopicFromConfig.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, it } from 'vitest'; +import { extractPrimaryTopicFromConfig } from './extractPrimaryTopicFromConfig'; + +describe('extractPrimaryTopicFromConfig', () => { + it('reads a top-level topic used by Image/Audio/Raw/JointStatePlot', () => { + expect(extractPrimaryTopicFromConfig({ topic: '/joint_command' })).toBe('/joint_command'); + }); + + it('ignores blank top-level topics', () => { + expect(extractPrimaryTopicFromConfig({ topic: ' ' })).toBeUndefined(); + expect(extractPrimaryTopicFromConfig({ topic: '' })).toBeUndefined(); + expect(extractPrimaryTopicFromConfig(undefined)).toBeUndefined(); + expect(extractPrimaryTopicFromConfig(null)).toBeUndefined(); + }); + + it('reads the first non-empty Plot series topic', () => { + expect( + extractPrimaryTopicFromConfig({ + series: [{ topic: '' }, { topic: '/imu' }, { topic: '/later' }], + }), + ).toBe('/imu'); + }); + + it('reads the first enabled 3D topicSetting and ignores urdf.topic', () => { + expect( + extractPrimaryTopicFromConfig({ + urdf: { topic: '/robot_description' }, + topicSettings: [ + { topic: '/disabled_scan', enabled: false }, + { topic: '/points', enabled: true }, + ], + }), + ).toBe('/points'); + }); + + it('does not fall back to urdf.topic when 3D has no enabled topicSettings', () => { + expect( + extractPrimaryTopicFromConfig({ + urdf: { topic: '/robot_description' }, + topicSettings: [{ topic: '/cloud', enabled: false }], + }), + ).toBeUndefined(); + }); + + it('reads Pose topics[].topic', () => { + expect( + extractPrimaryTopicFromConfig({ + topics: [{ topic: '', enabled: true }, { topic: '/odom' }], + }), + ).toBe('/odom'); + }); + + it('reads Align topics[] strings', () => { + expect(extractPrimaryTopicFromConfig({ topics: ['/cam_left', '/cam_right'] })).toBe('/cam_left'); + }); +}); diff --git a/src/features/panels/framework/extractPrimaryTopicFromConfig.ts b/src/features/panels/framework/extractPrimaryTopicFromConfig.ts new file mode 100644 index 0000000..624c6d9 --- /dev/null +++ b/src/features/panels/framework/extractPrimaryTopicFromConfig.ts @@ -0,0 +1,49 @@ +function nonEmptyTopic(value: unknown): string | undefined { + if (typeof value !== 'string') return undefined; + const topic = value.trim(); + return topic.length > 0 ? topic : undefined; +} + +function topicFromRecord(value: unknown): string | undefined { + if (!value || typeof value !== 'object') return undefined; + return nonEmptyTopic((value as { topic?: unknown }).topic); +} + +/** + * Best-effort primary topic from a panel config. + * Looks at the fields used by Image/Audio/Raw/JointStatePlot, Plot series, + * 3D topicSettings (not urdf.topic), and Align/Pose topics lists. + */ +export function extractPrimaryTopicFromConfig(config: unknown): string | undefined { + if (!config || typeof config !== 'object') return undefined; + const record = config as Record; + + const direct = nonEmptyTopic(record.topic); + if (direct) return direct; + + if (Array.isArray(record.series)) { + for (const series of record.series) { + const topic = topicFromRecord(series); + if (topic) return topic; + } + } + + if (Array.isArray(record.topicSettings)) { + for (const setting of record.topicSettings) { + if (!setting || typeof setting !== 'object') continue; + const entry = setting as { topic?: unknown; enabled?: unknown }; + if (entry.enabled === false) continue; + const topic = nonEmptyTopic(entry.topic); + if (topic) return topic; + } + } + + if (Array.isArray(record.topics)) { + for (const entry of record.topics) { + const topic = nonEmptyTopic(entry) ?? topicFromRecord(entry); + if (topic) return topic; + } + } + + return undefined; +} diff --git a/src/features/panels/framework/index.ts b/src/features/panels/framework/index.ts index 703b67a..aebe965 100644 --- a/src/features/panels/framework/index.ts +++ b/src/features/panels/framework/index.ts @@ -1,4 +1,5 @@ export { createPanelInstanceId, getPanelTypeFromId, markPanelInstanceId } from './ids'; +export { extractPrimaryTopicFromConfig } from './extractPrimaryTopicFromConfig'; export { collectExtras, FOXGLOVE_PANEL_TITLE_KEY, diff --git a/src/features/workspace/common/LoadingOverlay.test.tsx b/src/features/workspace/common/LoadingOverlay.test.tsx new file mode 100644 index 0000000..babed08 --- /dev/null +++ b/src/features/workspace/common/LoadingOverlay.test.tsx @@ -0,0 +1,74 @@ +/** + * @vitest-environment happy-dom + */ +import { act } from 'react'; +import { createRoot, type Root } from 'react-dom/client'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { IntlProvider } from 'react-intl'; +import { useMessagePipelineStore } from '@/core/pipeline/store'; +import { getRosViewMessages } from '@/shared/intl/loadRosViewMessages'; +import { LoadingOverlay } from './LoadingOverlay'; + +(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true; + +const enMessages = getRosViewMessages('en'); + +describe('LoadingOverlay', () => { + let container: HTMLDivElement; + let root: Root; + + beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); + root = createRoot(container); + }); + + afterEach(() => { + act(() => root.unmount()); + container.remove(); + useMessagePipelineStore.setState({ + playerState: { presence: 'preinit', progress: {} }, + }); + }); + + function renderOverlay(sourceName?: string): void { + act(() => { + root.render( + + + , + ); + }); + } + + it('shows the file basename and determinate progress, not Preparing', () => { + useMessagePipelineStore.getState().setPlayerState({ + presence: 'initializing', + progress: { + loadedBytes: 12_582_912, + totalBytes: 52_428_800, + initPhase: 'downloading', + }, + }); + + renderOverlay( + 'https://rosview-samples-1328702871.cos.accelerate.myqcloud.com/RealMan_PicknPlace.mcap', + ); + + expect(container.textContent).toContain('Loading'); + expect(container.textContent).toContain('RealMan_PicknPlace.mcap'); + expect(container.textContent).not.toContain('Preparing'); + expect(container.textContent).not.toContain('https://rosview-samples'); + expect(container.querySelector('[data-testid="rosview-loading-progress-bar"]')).not.toBeNull(); + expect(container.querySelector('[data-testid="rosview-loading-stats"]')?.textContent).toContain( + '12.0 MB / 50.0 MB', + ); + }); + + it('uses an indeterminate bar before byte totals are known', () => { + renderOverlay(); + expect(container.querySelector('[data-testid="rosview-loading-progress-indeterminate"]')).not.toBeNull(); + expect(container.querySelector('[data-testid="rosview-loading-progress-bar"]')).toBeNull(); + expect(container.textContent).not.toContain('Preparing'); + }); +}); diff --git a/src/features/workspace/common/LoadingOverlay.tsx b/src/features/workspace/common/LoadingOverlay.tsx index d6a581c..2967a49 100644 --- a/src/features/workspace/common/LoadingOverlay.tsx +++ b/src/features/workspace/common/LoadingOverlay.tsx @@ -1,8 +1,13 @@ import React from 'react'; import { useIntl } from 'react-intl'; +import { useMessagePipeline } from '@/core/pipeline/useMessagePipeline'; import { Button } from '@/shared/ui/button'; import { Card, CardFooter, CardHeader, CardTitle } from '@/shared/ui/card'; import { Spinner } from '@/shared/ui/spinner'; +import { formatBytes } from '@/shared/utils/formatBytes'; +import { estimateEtaSeconds, formatEtaParts } from '@/shared/utils/formatEta'; +import { sourceDisplayName } from '@/shared/utils/sourceDisplayName'; +import { useTransferRate } from './useTransferRate'; interface LoadingOverlayProps { sourceName?: string; @@ -11,6 +16,40 @@ interface LoadingOverlayProps { export const LoadingOverlay: React.FC = ({ sourceName, onCancel }) => { const { formatMessage } = useIntl(); + const loadedBytes = useMessagePipeline((state) => state.playerState.progress.loadedBytes); + const totalBytes = useMessagePipeline((state) => state.playerState.progress.totalBytes); + const bytesPerSecond = useTransferRate(loadedBytes); + const displayName = sourceDisplayName(sourceName); + const hasTotal = typeof totalBytes === 'number' && totalBytes > 0; + const hasLoaded = typeof loadedBytes === 'number' && loadedBytes >= 0; + const percent = hasTotal && hasLoaded ? Math.min(100, (loadedBytes / totalBytes) * 100) : undefined; + const loadedLabel = hasLoaded ? formatBytes(loadedBytes) : undefined; + const totalLabel = hasTotal ? formatBytes(totalBytes) : undefined; + const speedLabel = + bytesPerSecond != null && bytesPerSecond > 0 + ? formatMessage({ id: 'welcome.loadingSpeed' }, { speed: formatBytes(bytesPerSecond) ?? '' }) + : undefined; + const etaSeconds = + hasTotal && hasLoaded && bytesPerSecond != null + ? estimateEtaSeconds(totalBytes - loadedBytes, bytesPerSecond) + : undefined; + const etaParts = etaSeconds != null ? formatEtaParts(etaSeconds) : undefined; + const etaLabel = etaParts + ? formatMessage( + { + id: + etaParts.unit === 'hours' + ? 'welcome.loadingEtaHours' + : etaParts.unit === 'minutes' + ? 'welcome.loadingEtaMinutes' + : 'welcome.loadingEtaSeconds', + }, + { n: etaParts.n }, + ) + : undefined; + const stats = [loadedLabel && totalLabel ? `${loadedLabel} / ${totalLabel}` : loadedLabel, speedLabel, etaLabel] + .filter((part): part is string => Boolean(part)) + .join(' · '); return (
= ({ sourceName, onCa {formatMessage({ id: 'welcome.loadingTitle' })} - {sourceName ? ( + {displayName ? (

- {sourceName} + {displayName} +

+ ) : null} + {percent != null ? ( +
+
+
+ ) : ( +
+
+
+ )} + {stats ? ( +

+ {stats}

) : null} -

- {formatMessage({ id: 'welcome.loadingPhase.preparing' })} -

{onCancel ? ( diff --git a/src/features/workspace/common/useTransferRate.ts b/src/features/workspace/common/useTransferRate.ts new file mode 100644 index 0000000..666c8b4 --- /dev/null +++ b/src/features/workspace/common/useTransferRate.ts @@ -0,0 +1,37 @@ +import { useEffect, useRef, useState } from 'react'; + +const WINDOW_MS = 3000; +const MIN_SAMPLE_MS = 200; + +/** Sliding-window bytes/sec from a monotonically increasing (or resetting) byte counter. */ +export function useTransferRate(loadedBytes: number | undefined): number | undefined { + const samplesRef = useRef>([]); + const [bps, setBps] = useState(); + + useEffect(() => { + if (loadedBytes == null || !Number.isFinite(loadedBytes) || loadedBytes < 0) { + return; + } + const now = performance.now(); + const samples = samplesRef.current; + const last = samples[samples.length - 1]; + if (last && loadedBytes < last.bytes) { + samples.length = 0; + } + samples.push({ t: now, bytes: loadedBytes }); + const cutoff = now - WINDOW_MS; + while (samples.length > 1 && samples[0].t < cutoff) { + samples.shift(); + } + if (samples.length >= 2) { + const first = samples[0]; + const latest = samples[samples.length - 1]; + const dt = (latest.t - first.t) / 1000; + if (dt * 1000 >= MIN_SAMPLE_MS) { + setBps((latest.bytes - first.bytes) / dt); + } + } + }, [loadedBytes]); + + return bps; +} diff --git a/src/features/workspace/navbar/Navbar.tsx b/src/features/workspace/navbar/Navbar.tsx index d491612..414b0ac 100644 --- a/src/features/workspace/navbar/Navbar.tsx +++ b/src/features/workspace/navbar/Navbar.tsx @@ -19,6 +19,7 @@ import { Upload, } from 'lucide-react'; import { useIntl } from 'react-intl'; +import { sourceDisplayName } from '@/shared/utils/sourceDisplayName'; import { Menubar, MenubarContent, @@ -168,8 +169,10 @@ export const Navbar: React.FC = ({ const hasOpenMenu = onOpenFilePick || onOpenDirectory || onOpenTarPick || onOpenRemotePrompt || onOpenSampleDialog; - const showCenter = Boolean(sourceLoading || (sourceName && sourceName.trim().length > 0)); - const centerLabel = sourceLoading ? formatMessage({ id: 'navbar.sourceLoading' }) : (sourceName ?? ''); + const displayName = sourceDisplayName(sourceName); + const showCenter = Boolean(sourceLoading || displayName); + const centerLabel = sourceLoading ? formatMessage({ id: 'navbar.sourceLoading' }) : (displayName ?? ''); + const centerTitle = sourceLoading ? centerLabel : (sourceName?.trim() || centerLabel); const brandText = brandLabel ?? formatMessage({ id: 'common.productName' }); const brandAccessibleName = brandLabel ?? formatMessage({ id: 'navbar.goHome' }); @@ -352,7 +355,7 @@ export const Navbar: React.FC = ({ className={`block min-w-0 flex-1 truncate text-center text-sm font-medium tracking-tight ${ sourceLoading ? 'text-muted-foreground' : 'text-foreground' }`} - title={centerLabel} + title={centerTitle} > {centerLabel} diff --git a/src/infra/services/BrowserHttpReader.test.ts b/src/infra/services/BrowserHttpReader.test.ts new file mode 100644 index 0000000..5f528ce --- /dev/null +++ b/src/infra/services/BrowserHttpReader.test.ts @@ -0,0 +1,45 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { BrowserHttpReader } from './BrowserHttpReader'; + +describe('BrowserHttpReader streaming progress', () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it('reports incremental progress while streaming a range', async () => { + const part1 = new Uint8Array([1, 2, 3, 4]); + const part2 = new Uint8Array([5, 6, 7, 8]); + vi.stubGlobal( + 'fetch', + vi.fn(async () => { + return new Response( + new ReadableStream({ + start(controller) { + controller.enqueue(part1); + controller.enqueue(part2); + controller.close(); + }, + }), + { + status: 206, + headers: { + 'Content-Range': 'bytes 0-7/100', + 'Content-Length': '8', + }, + }, + ); + }), + ); + + const reader = new BrowserHttpReader('https://example.com/file.mcap'); + const updates: Array<[number, number]> = []; + const data = await reader.read(0, 8, undefined, (received, expected) => { + updates.push([received, expected]); + }); + + expect(Array.from(data)).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); + expect(updates.length).toBeGreaterThanOrEqual(2); + expect(updates[updates.length - 1]).toEqual([8, 8]); + expect(updates[0][0]).toBeLessThan(updates[updates.length - 1][0]); + }); +}); diff --git a/src/infra/services/BrowserHttpReader.ts b/src/infra/services/BrowserHttpReader.ts index 93f99ab..c751756 100644 --- a/src/infra/services/BrowserHttpReader.ts +++ b/src/infra/services/BrowserHttpReader.ts @@ -1,6 +1,8 @@ +export type HttpReadProgress = (received: number, expected: number) => void; + export interface HttpReader { size(): number; - read(offset: number, length: number, signal?: AbortSignal): Promise; + read(offset: number, length: number, signal?: AbortSignal, onProgress?: HttpReadProgress): Promise; } export type BrowserHttpReaderOptions = { @@ -99,7 +101,12 @@ export class BrowserHttpReader implements HttpReader { return this._size; } - async read(offset: number, length: number, signal?: AbortSignal): Promise { + async read( + offset: number, + length: number, + signal?: AbortSignal, + onProgress?: HttpReadProgress, + ): Promise { if (length === 0) { return new Uint8Array(); } @@ -108,8 +115,15 @@ export class BrowserHttpReader implements HttpReader { for (let pos = offset; pos < offset + length; pos += RANGE_PART_BYTES) { parts.push({ start: pos, len: Math.min(RANGE_PART_BYTES, offset + length - pos) }); } + const receivedByPart = new Array(parts.length).fill(0); const chunks = await Promise.all( - parts.map(({ start, len }) => this.#readOneRange(start, len, signal)), + parts.map(({ start, len }, index) => + this.#readOneRange(start, len, signal, (received) => { + receivedByPart[index] = received; + const sum = receivedByPart.reduce((acc, value) => acc + value, 0); + onProgress?.(sum, length); + }), + ), ); const out = new Uint8Array(length); let at = 0; @@ -119,7 +133,7 @@ export class BrowserHttpReader implements HttpReader { } return out; } - return this.#readOneRange(offset, length, signal); + return this.#readOneRange(offset, length, signal, onProgress); } async #fetchWithRetry(url: string, init: RequestInit): Promise { @@ -142,7 +156,12 @@ export class BrowserHttpReader implements HttpReader { throw new Error(`fetchWithRetry exhausted for ${url}`); } - async #readOneRange(offset: number, length: number, signal?: AbortSignal): Promise { + async #readOneRange( + offset: number, + length: number, + signal?: AbortSignal, + onProgress?: HttpReadProgress, + ): Promise { const end = offset + length - 1; const response = await this.#fetchWithRetry(this._url, { headers: { Range: `bytes=${offset}-${end}` }, @@ -158,7 +177,10 @@ export class BrowserHttpReader implements HttpReader { ); } - const buf = new Uint8Array(await response.arrayBuffer()); + const headerLen = Number(response.headers.get('content-length') ?? ''); + const expected = + Number.isFinite(headerLen) && headerLen > 0 ? headerLen : length; + const buf = await this.#readResponseBody(response, expected, onProgress); if (response.status === 206) { if (buf.byteLength === length) { @@ -181,4 +203,39 @@ export class BrowserHttpReader implements HttpReader { } throw new Error(`Unexpected response length ${buf.byteLength} for range length ${length}`); } + + async #readResponseBody( + response: Response, + expected: number, + onProgress?: HttpReadProgress, + ): Promise { + if (!response.body) { + const buf = new Uint8Array(await response.arrayBuffer()); + onProgress?.(buf.byteLength, expected > 0 ? expected : buf.byteLength); + return buf; + } + + const reader = response.body.getReader(); + const chunks: Uint8Array[] = []; + let received = 0; + for (;;) { + const { done, value } = await reader.read(); + if (done) { + break; + } + if (value) { + chunks.push(value); + received += value.byteLength; + onProgress?.(received, expected > 0 ? expected : received); + } + } + + const out = new Uint8Array(received); + let offset = 0; + for (const chunk of chunks) { + out.set(chunk, offset); + offset += chunk.byteLength; + } + return out; + } } diff --git a/src/infra/services/CachedFilelike.test.ts b/src/infra/services/CachedFilelike.test.ts index e9b58ab..0f4b109 100644 --- a/src/infra/services/CachedFilelike.test.ts +++ b/src/infra/services/CachedFilelike.test.ts @@ -12,6 +12,10 @@ class TestStream extends EventEmitter implements FileStream { public emitData(data: number[]): void { this.emit('data', new Uint8Array(data)); } + + public emitProgress(received: number, total: number): void { + this.emit('progress', received, total); + } } class TestFileReader { @@ -175,6 +179,30 @@ describe('CachedFilelike input validation', () => { }); }); +describe('CachedFilelike download progress', () => { + it('forwards stream progress to onDownloadProgress', async () => { + const reader = new TestFileReader(); + const updates: Array<{ loadedBytes: number; totalBytes: number; transferredBytes: number }> = []; + const filelike = new CachedFilelike({ + fileReader: reader, + cacheSizeInBytes: 32, + fetchBlockSizeInBytes: 8, + onDownloadProgress: (info) => updates.push({ ...info }), + }); + + const readPromise = filelike.read(0, 8); + await flushAsyncWork(); + expect(reader.streams).toHaveLength(1); + + reader.streams[0].emitProgress(4, 8); + reader.streams[0].emitData([0, 1, 2, 3, 4, 5, 6, 7]); + await readPromise; + + expect(updates.some((update) => update.loadedBytes === 4 && update.totalBytes === 8)).toBe(true); + expect(updates[0].transferredBytes).toBeGreaterThan(0); + }); +}); + describe('CachedFilelike avoids re-fetching already-satisfied ranges', () => { it('does not issue a second fetch for a block that is already fully downloaded', async () => { const reader = new TestFileReader(); diff --git a/src/infra/services/CachedFilelike.ts b/src/infra/services/CachedFilelike.ts index 822a8fe..0b36a6c 100644 --- a/src/infra/services/CachedFilelike.ts +++ b/src/infra/services/CachedFilelike.ts @@ -5,9 +5,16 @@ import EventEmitter from "eventemitter3"; export type FileStreamEvents = { data: (chunk: Uint8Array) => void; + progress: (received: number, total: number) => void; error: (err: Error) => void; }; +export type DownloadProgressInfo = { + loadedBytes: number; + totalBytes: number; + transferredBytes: number; +}; + export interface FileStream extends EventEmitter { destroy: () => void; } @@ -43,6 +50,9 @@ export default class CachedFilelike implements Readable { #virtualBuffer: VirtualLRUBuffer; #closed: boolean = false; #keepReconnectingCallback?: (reconnecting: boolean) => void; + #onDownloadProgress?: (info: DownloadProgressInfo) => void; + #transferredBytes = 0; + #currentRequestReceived = 0; #currentConnection: { stream: FileStream; remainingRange: Range; kind: "read" | "prefetch" } | undefined; @@ -64,6 +74,7 @@ export default class CachedFilelike implements Readable { maxRequestSizeInBytes?: number; preferCacheViews?: boolean; keepReconnectingCallback?: (reconnecting: boolean) => void; + onDownloadProgress?: (info: DownloadProgressInfo) => void; }) { this.#fileReader = options.fileReader; this.#cacheSizeInBytes = options.cacheSizeInBytes ?? this.#cacheSizeInBytes; @@ -80,6 +91,7 @@ export default class CachedFilelike implements Readable { ); this.#preferCacheViews = options.preferCacheViews ?? false; this.#keepReconnectingCallback = options.keepReconnectingCallback; + this.#onDownloadProgress = options.onDownloadProgress; this.#virtualBuffer = new VirtualLRUBuffer({ size: 0 }); } @@ -326,6 +338,22 @@ export default class CachedFilelike implements Readable { const stream = this.#fileReader.fetch(range.start, range.end - range.start); this.#currentConnection = { stream, remainingRange: range, kind }; + this.#currentRequestReceived = 0; + const requestTotal = range.end - range.start; + + stream.on("progress", (received: number, total: number) => { + const currentConnection = this.#currentConnection; + if (!currentConnection || stream !== currentConnection.stream) { + return; + } + const safeReceived = Math.max(0, received); + const delta = safeReceived - this.#currentRequestReceived; + if (delta > 0) { + this.#transferredBytes += delta; + this.#currentRequestReceived = safeReceived; + } + this.#reportDownloadProgress(safeReceived, total > 0 ? total : requestTotal); + }); stream.on("error", (error: Error) => { console.error(`Connection error @ ${range.start}-${range.end}:`, error); @@ -384,6 +412,10 @@ export default class CachedFilelike implements Readable { this.#virtualBuffer.copyFrom(chunk, currentConnection.remainingRange.start); bytesRead += chunk.byteLength; + if (this.#currentRequestReceived === 0 && chunk.byteLength > 0) { + this.#transferredBytes += chunk.byteLength; + this.#reportDownloadProgress(bytesRead, requestTotal); + } if (this.#virtualBuffer.hasData(range.start, range.end)) { stream.destroy(); @@ -399,4 +431,12 @@ export default class CachedFilelike implements Readable { this.#updateState(); }); } + + #reportDownloadProgress(loadedBytes: number, totalBytes: number): void { + this.#onDownloadProgress?.({ + loadedBytes, + totalBytes, + transferredBytes: this.#transferredBytes, + }); + } } diff --git a/src/infra/services/HttpFileReader.ts b/src/infra/services/HttpFileReader.ts index 8430f76..326053e 100644 --- a/src/infra/services/HttpFileReader.ts +++ b/src/infra/services/HttpFileReader.ts @@ -8,7 +8,11 @@ class HttpFileStream extends EventEmitter implements FileStrea constructor(reader: BrowserHttpReader, offset: number, length: number) { super(); - reader.read(offset, length, this._abortController.signal) + reader.read(offset, length, this._abortController.signal, (received, total) => { + if (!this._destroyed) { + this.emit("progress", received, total); + } + }) .then((data) => { if (!this._destroyed) { this.emit("data", data); diff --git a/src/infra/workers/CombinedSourceProxy.test.ts b/src/infra/workers/CombinedSourceProxy.test.ts index 4989d05..eddeedc 100644 --- a/src/infra/workers/CombinedSourceProxy.test.ts +++ b/src/infra/workers/CombinedSourceProxy.test.ts @@ -3,7 +3,7 @@ import type { Initialization, MessageEvent } from '@/core/types/ros'; import type { WorkerSerializedSource } from './WorkerSerializedSource'; import { CombinedSourceProxy, type CombinedSourceMember } from './CombinedSourceProxy'; import { CombinedMessageCursor } from './CombinedMessageCursor'; -import type { IMessageCursor } from './types'; +import type { IMessageCursor, SourceInitProgressCallback } from './types'; function makeInit(overrides: Partial = {}): Initialization { return { @@ -75,6 +75,27 @@ describe('CombinedSourceProxy', () => { expect(sourceB.initialize).toHaveBeenCalledTimes(1); }); + it('aggregates initialize progress from members', async () => { + const sourceA = makeMockWorkerSource({ + initialize: vi.fn(async (_args: Record, onProgress?: SourceInitProgressCallback) => { + onProgress?.({ phase: 'downloading', loadedBytes: 10, totalBytes: 40, transferredBytes: 10 }); + return makeInit({ topics: [{ name: '/a', type: 'std_msgs/String' }] }); + }), + }); + const sourceB = makeMockWorkerSource({ + initialize: vi.fn(async (_args: Record, onProgress?: SourceInitProgressCallback) => { + onProgress?.({ phase: 'downloading', loadedBytes: 20, totalBytes: 60, transferredBytes: 20 }); + return makeInit({ topics: [{ name: '/b', type: 'std_msgs/String' }] }); + }), + }); + const updates: Array<{ loadedBytes: number; totalBytes: number }> = []; + const proxy = new CombinedSourceProxy([makeMember('a.mcap', sourceA), makeMember('b.mcap', sourceB)]); + await proxy.initialize({}, (progress) => { + updates.push({ loadedBytes: progress.loadedBytes, totalBytes: progress.totalBytes }); + }); + expect(updates.some((update) => update.loadedBytes === 30 && update.totalBytes === 100)).toBe(true); + }); + it('excludes a member that fails to initialize, terminates its worker, and records a problem', async () => { const sourceA = makeMockWorkerSource({ initialize: vi.fn(async () => makeInit({ topics: [{ name: '/a', type: 'std_msgs/String' }] })), diff --git a/src/infra/workers/CombinedSourceProxy.ts b/src/infra/workers/CombinedSourceProxy.ts index 5c28ebd..aa5d6e3 100644 --- a/src/infra/workers/CombinedSourceProxy.ts +++ b/src/infra/workers/CombinedSourceProxy.ts @@ -15,6 +15,8 @@ import type { MessageIteratorArgs, PlaybackBufferStatus, PreparePlaybackBufferArgs, + SourceInitProgress, + SourceInitProgressCallback, } from './types'; import type { TransportDiagnostics } from './transport'; import type { WorkerSerializedSource } from './WorkerSerializedSource'; @@ -38,6 +40,22 @@ function errorFromReason(reason: unknown): Error { return reason instanceof Error ? reason : new Error(errorMessage(reason)); } +function aggregateInitProgress(parts: Array): SourceInitProgress { + const defined = parts.filter((part): part is SourceInitProgress => part != null); + if (defined.length === 0) { + return { phase: 'connecting', loadedBytes: 0, totalBytes: 0 }; + } + const loadedBytes = defined.reduce((sum, part) => sum + part.loadedBytes, 0); + const totalBytes = defined.reduce((sum, part) => sum + part.totalBytes, 0); + const transferredBytes = defined.reduce((sum, part) => sum + (part.transferredBytes ?? 0), 0); + const phase = defined.some((part) => part.phase === 'downloading') + ? 'downloading' + : defined.some((part) => part.phase === 'opening') + ? 'opening' + : 'connecting'; + return { phase, loadedBytes, totalBytes, transferredBytes }; +} + /** * Merges N independent `WorkerSerializedSource` instances (each backed by its * own Worker, possibly a different format) into a single `ISourceHandle` that @@ -57,9 +75,23 @@ export class CombinedSourceProxy implements ISourceHandle { this._members = members; } - async initialize(args: Record): Promise { + async initialize( + args: Record, + onProgress?: SourceInitProgressCallback, + ): Promise { + const memberProgress: Array = this._members.map(() => undefined); const results = await Promise.allSettled( - this._members.map((member) => member.source.initialize({ ...member.initArgs, ...args })), + this._members.map((member, memberIndex) => + member.source.initialize( + { ...member.initArgs, ...args }, + onProgress + ? (progress) => { + memberProgress[memberIndex] = progress; + onProgress(aggregateInitProgress(memberProgress)); + } + : undefined, + ), + ), ); const succeededSources: Array<{ label: string; initialization: Initialization }> = []; diff --git a/src/infra/workers/ISourceHandle.ts b/src/infra/workers/ISourceHandle.ts index a6c8f15..5bbdf54 100644 --- a/src/infra/workers/ISourceHandle.ts +++ b/src/infra/workers/ISourceHandle.ts @@ -5,6 +5,7 @@ import type { IMessageCursor, LoadProgress, MessageIteratorArgs, + SourceInitProgressCallback, PlaybackBufferStatus, PreparePlaybackBufferArgs, } from './types'; @@ -25,7 +26,10 @@ export type ResolveHighFrequencyLaneOptions = { * file or several. */ export interface ISourceHandle { - initialize(args: Record): Promise; + initialize( + args: Record, + onProgress?: SourceInitProgressCallback, + ): Promise; getMessageCursor(args: MessageIteratorArgs): Promise>; getBackfillMessages(args: GetBackfillMessagesArgs): Promise; getAdjacentMessage(args: GetAdjacentMessageArgs): Promise; diff --git a/src/infra/workers/WorkerSerializedSource.test.ts b/src/infra/workers/WorkerSerializedSource.test.ts index ebdbdea..7e04c8d 100644 --- a/src/infra/workers/WorkerSerializedSource.test.ts +++ b/src/infra/workers/WorkerSerializedSource.test.ts @@ -52,6 +52,7 @@ function dispatchWorkerError(message: string): void { vi.mock('comlink', () => ({ wrap: vi.fn(() => mockRemote), + proxy: vi.fn((value: unknown) => value), })); vi.mock('./transports/createWorkerTransport', () => ({ diff --git a/src/infra/workers/WorkerSerializedSource.ts b/src/infra/workers/WorkerSerializedSource.ts index 8a2ee03..eb7de2e 100644 --- a/src/infra/workers/WorkerSerializedSource.ts +++ b/src/infra/workers/WorkerSerializedSource.ts @@ -9,6 +9,7 @@ import type { LoadProgress, PlaybackBufferStatus, PreparePlaybackBufferArgs, + SourceInitProgressCallback, } from "./types"; import type { TransportDiagnostics } from "./transport"; import { isSharedPayloadRef } from "./transport"; @@ -62,7 +63,10 @@ export class WorkerSerializedSource implements ISourceHandle { } } - async initialize(args: Record): Promise { + async initialize( + args: Record, + onProgress?: SourceInitProgressCallback, + ): Promise { console.debug("WorkerSerializedSource: calling initialize"); try { // Ensure all objects passed to Comlink are plain objects @@ -84,7 +88,12 @@ export class WorkerSerializedSource implements ISourceHandle { this._transportConfigured = true; } const result = await this._raceWorkerFailure( - this._wrapAbortableInitialize(this._remote.initialize(sanitizedArgs)), + this._wrapAbortableInitialize( + this._remote.initialize( + sanitizedArgs, + onProgress ? Comlink.proxy(onProgress) : undefined, + ), + ), ); console.debug("WorkerSerializedSource: initialize result received"); return result; diff --git a/src/infra/workers/bag.worker.ts b/src/infra/workers/bag.worker.ts index 955b528..a1c11c9 100644 --- a/src/infra/workers/bag.worker.ts +++ b/src/infra/workers/bag.worker.ts @@ -8,6 +8,7 @@ import type { IMessageCursor, PlaybackBufferStatus, PreparePlaybackBufferArgs, + SourceInitProgressCallback, } from "./types"; import { BagIterableSource } from '@/infra/sources/BagIterableSource'; import { MessageCursor } from "./MessageCursor"; @@ -19,6 +20,7 @@ import type { TransportDiagnostics, WorkerTransportConfig } from "./transport"; import { SharedPayloadRing } from "./sharedPayloadRing"; import { resolveRemoteCacheBytes } from './remoteCacheConfig'; import { DataQualityScanController } from './dataQualityScanController'; +import { trackInitProgress } from './throttledInitProgress'; import { buildRemoteBagReadable, type SyncSizeBagReadable } from './remoteBagReadable'; class BagWorker implements IWorkerSerializedSourceWorker { @@ -32,7 +34,12 @@ class BagWorker implements IWorkerSerializedSourceWorker { }; private _qualityScan = new DataQualityScanController(); - async initialize(args: Record): Promise { + async initialize( + args: Record, + onProgress?: SourceInitProgressCallback, + ): Promise { + const report = trackInitProgress(onProgress); + report({ phase: "connecting", loadedBytes: 0, totalBytes: 0 }); const url = typeof args.url === 'string' ? args.url : undefined; const file = args.file instanceof Blob ? args.file : undefined; let sourceArgs: @@ -53,6 +60,14 @@ class BagWorker implements IWorkerSerializedSourceWorker { const readable = new CachedFilelike({ fileReader, cacheSizeInBytes: resolveRemoteCacheBytes(), + onDownloadProgress: (info) => { + report({ + phase: "downloading", + loadedBytes: info.loadedBytes, + totalBytes: info.totalBytes, + transferredBytes: info.transferredBytes, + }); + }, }); this._cachedReadable = readable; const bagReadable = await buildRemoteBagReadable(readable); @@ -72,6 +87,7 @@ class BagWorker implements IWorkerSerializedSourceWorker { ); } + report({ phase: "opening" }); this._source = new BagIterableSource(sourceArgs, { wasmBinary: zstdWasmBinary }); const init = await this._source.initialize(); this._initialization = init; diff --git a/src/infra/workers/bvh.worker.ts b/src/infra/workers/bvh.worker.ts index 7ae69d8..468ff7f 100644 --- a/src/infra/workers/bvh.worker.ts +++ b/src/infra/workers/bvh.worker.ts @@ -9,6 +9,7 @@ import type { MessageIteratorArgs, PlaybackBufferStatus, PreparePlaybackBufferArgs, + SourceInitProgressCallback, } from "./types"; import type { TransportDiagnostics, WorkerTransportConfig } from "./transport"; import { SharedPayloadRing } from "./sharedPayloadRing"; @@ -27,7 +28,11 @@ class BvhWorkerImpl implements IWorkerSerializedSourceWorker { private _totalBytes = 0; private _qualityScan = new DataQualityScanController(); - async initialize(args: { url?: string; file?: Blob; autoDataQualityScan?: boolean }): Promise { + async initialize( + args: { url?: string; file?: Blob; autoDataQualityScan?: boolean }, + onProgress?: SourceInitProgressCallback, + ): Promise { + onProgress?.({ phase: "connecting", loadedBytes: 0, totalBytes: 0 }); let text: string; if (args.file) { this._totalBytes = args.file.size; @@ -43,6 +48,12 @@ class BvhWorkerImpl implements IWorkerSerializedSourceWorker { throw new Error("BvhWorker: neither url nor file provided"); } + onProgress?.({ + phase: "opening", + loadedBytes: this._totalBytes, + totalBytes: this._totalBytes, + transferredBytes: this._totalBytes, + }); this._source = new BvhIterableSource(text, "bvh"); const init = await this._source.initialize(); this._initialization = init; diff --git a/src/infra/workers/db3.worker.ts b/src/infra/workers/db3.worker.ts index ee93589..0fb496f 100644 --- a/src/infra/workers/db3.worker.ts +++ b/src/infra/workers/db3.worker.ts @@ -8,6 +8,7 @@ import type { IMessageCursor, PlaybackBufferStatus, PreparePlaybackBufferArgs, + SourceInitProgressCallback, } from "./types"; import { RosDb3IterableSource } from '@/infra/sources/RosDb3IterableSource'; import { MessageCursor } from "./MessageCursor"; @@ -16,6 +17,7 @@ import type { TransportDiagnostics, WorkerTransportConfig } from "./transport"; import { SharedPayloadRing } from "./sharedPayloadRing"; import { DataQualityScanController } from './dataQualityScanController'; import { resolveWorkerHttpUrl } from '@/shared/utils/resolveWorkerHttpUrl'; +import { trackInitProgress } from './throttledInitProgress'; class Db3Worker implements IWorkerSerializedSourceWorker { private _source?: RosDb3IterableSource; @@ -29,7 +31,11 @@ class Db3Worker implements IWorkerSerializedSourceWorker { }; private _qualityScan = new DataQualityScanController(); - async initialize(args: Record): Promise { + async initialize( + args: Record, + onProgress?: SourceInitProgressCallback, + ): Promise { + const report = trackInitProgress(onProgress); const sqlWasmBinary = args.sqlWasmBinary instanceof ArrayBuffer ? args.sqlWasmBinary : undefined; const rawFiles = args.files; @@ -45,6 +51,12 @@ class Db3Worker implements IWorkerSerializedSourceWorker { this._source = new RosDb3IterableSource({ type: "files", files }, { sqlWasmBinary }); this._totalBytes = files.reduce((sum, f) => sum + f.size, 0); this._downloadedBytes = this._totalBytes; + report({ + phase: "opening", + loadedBytes: this._downloadedBytes, + totalBytes: this._totalBytes, + transferredBytes: this._downloadedBytes, + }); } else if (typeof args.url === "string" && args.url.length > 0) { // Remote URL: db3/SQLite cannot be range-streamed (random access over the // whole database), so download the file in full, then open it in memory. @@ -52,7 +64,8 @@ class Db3Worker implements IWorkerSerializedSourceWorker { typeof args.knownTotalBytes === "number" && Number.isFinite(args.knownTotalBytes) ? Math.floor(args.knownTotalBytes) : 0; - const data = await this._downloadToBytes(resolveWorkerHttpUrl(args.url), knownTotal); + report({ phase: "connecting", loadedBytes: 0, totalBytes: knownTotal }); + const data = await this._downloadToBytes(resolveWorkerHttpUrl(args.url), knownTotal, report); this._source = new RosDb3IterableSource({ type: "data", datas: [data] }, { sqlWasmBinary }); this._totalBytes = data.byteLength; this._downloadedBytes = data.byteLength; @@ -60,6 +73,12 @@ class Db3Worker implements IWorkerSerializedSourceWorker { throw new Error("Invalid arguments for Db3Worker: provide url or files"); } + report({ + phase: "opening", + loadedBytes: this._downloadedBytes, + totalBytes: this._totalBytes, + transferredBytes: this._downloadedBytes, + }); const init = await this._source.initialize(); this._initialization = init; this._loaded = true; @@ -67,7 +86,11 @@ class Db3Worker implements IWorkerSerializedSourceWorker { return init; } - private async _downloadToBytes(url: string, knownTotal: number): Promise { + private async _downloadToBytes( + url: string, + knownTotal: number, + report: (update: { phase: "downloading"; loadedBytes: number; totalBytes: number; transferredBytes: number }) => void, + ): Promise { const resp = await fetch(url); if (!resp.ok) { throw new Error(`Db3Worker: failed to download db3 (${resp.status} ${resp.statusText})`); @@ -83,6 +106,12 @@ class Db3Worker implements IWorkerSerializedSourceWorker { const buf = new Uint8Array(await resp.arrayBuffer()); this._totalBytes = buf.byteLength; this._downloadedBytes = buf.byteLength; + report({ + phase: "downloading", + loadedBytes: buf.byteLength, + totalBytes: buf.byteLength, + transferredBytes: buf.byteLength, + }); return buf; } @@ -96,6 +125,12 @@ class Db3Worker implements IWorkerSerializedSourceWorker { chunks.push(value); received += value.byteLength; this._downloadedBytes = received; + report({ + phase: "downloading", + loadedBytes: received, + totalBytes: this._totalBytes, + transferredBytes: received, + }); } } diff --git a/src/infra/workers/hdf5.worker.ts b/src/infra/workers/hdf5.worker.ts index a0ba3ff..beca4c2 100644 --- a/src/infra/workers/hdf5.worker.ts +++ b/src/infra/workers/hdf5.worker.ts @@ -10,6 +10,7 @@ import type { MessageIteratorArgs, PlaybackBufferStatus, PreparePlaybackBufferArgs, + SourceInitProgressCallback, } from './types'; import type { TransportDiagnostics, WorkerTransportConfig } from './transport'; import { SharedPayloadRing } from './sharedPayloadRing'; @@ -30,12 +31,16 @@ class Hdf5WorkerImpl implements IWorkerSerializedSourceWorker { }; private _qualityScan = new DataQualityScanController(); - async initialize(args: { - url?: string; - file?: Blob; - autoDataQualityScan?: boolean; - hdf5WasmBinary?: ArrayBuffer; - }): Promise { + async initialize( + args: { + url?: string; + file?: Blob; + autoDataQualityScan?: boolean; + hdf5WasmBinary?: ArrayBuffer; + }, + onProgress?: SourceInitProgressCallback, + ): Promise { + onProgress?.({ phase: 'connecting', loadedBytes: 0, totalBytes: 0 }); if (!args.hdf5WasmBinary) { throw new Error('Hdf5Worker: hdf5WasmBinary required (pass wasm bytes from main thread for inline workers)'); } @@ -58,6 +63,7 @@ class Hdf5WorkerImpl implements IWorkerSerializedSourceWorker { throw err; } + onProgress?.({ phase: 'opening', loadedBytes: 0, totalBytes: 0 }); this._source = new Hdf5IterableSource(this._h5file as never, { fileName: this._mounted.path, }); diff --git a/src/infra/workers/mcap.worker.ts b/src/infra/workers/mcap.worker.ts index 10fa317..11c42cc 100644 --- a/src/infra/workers/mcap.worker.ts +++ b/src/infra/workers/mcap.worker.ts @@ -11,6 +11,7 @@ import type { IMessageCursor, PlaybackBufferStatus, PreparePlaybackBufferArgs, + SourceInitProgressCallback, } from "./types"; import { McapIndexedIterableSource } from '@/infra/sources/McapIndexedIterableSource'; import { loadDecompressHandlers } from '@/infra/sources/decompressHandlers'; @@ -27,6 +28,7 @@ import type { Range } from '@/shared/utils/ranges'; import { compactTimeRanges, inferProgressTimeRangeCompaction } from '@/shared/utils/timeRanges'; import { workerPerf } from './workerPerf'; import { DataQualityScanController } from './dataQualityScanController'; +import { trackInitProgress } from './throttledInitProgress'; import { getPlayableTimeRanges, isByteRangeCovered, @@ -58,16 +60,21 @@ class McapWorkerImpl implements IWorkerSerializedSourceWorker { }; private _qualityScan = new DataQualityScanController(); - async initialize(args: Record): Promise { + async initialize( + args: Record, + onProgress?: SourceInitProgressCallback, + ): Promise { workerPerf.configure({ enabled: args.workerPerf === true, label: "mcap", }); console.debug("McapWorker: initialize starting", args); + const report = trackInitProgress(onProgress); try { let rawReadable: IReadable; const url = typeof args.url === 'string' ? args.url : undefined; const file = args.file instanceof Blob ? args.file : undefined; + report({ phase: "connecting", loadedBytes: 0, totalBytes: 0 }); if (url) { const knownRaw = args.knownTotalBytes; const knownTotalBytes = @@ -85,6 +92,14 @@ class McapWorkerImpl implements IWorkerSerializedSourceWorker { fileReader, cacheSizeInBytes: this._remoteCacheBytes, preferCacheViews: true, + onDownloadProgress: (info) => { + report({ + phase: "downloading", + loadedBytes: info.loadedBytes, + totalBytes: info.totalBytes, + transferredBytes: info.transferredBytes, + }); + }, }); this._cachedReadable = cachedReadable; rawReadable = { @@ -131,6 +146,7 @@ class McapWorkerImpl implements IWorkerSerializedSourceWorker { decompressHandlers, }), ); + report({ phase: "opening" }); this._source = new McapIndexedIterableSource(reader); const init = await workerPerf.timeAsync( "initialize.source", diff --git a/src/infra/workers/throttledInitProgress.ts b/src/infra/workers/throttledInitProgress.ts new file mode 100644 index 0000000..043dbdf --- /dev/null +++ b/src/infra/workers/throttledInitProgress.ts @@ -0,0 +1,40 @@ +import type { SourceInitPhase, SourceInitProgress, SourceInitProgressCallback } from './types'; + +const DEFAULT_INTERVAL_MS = 100; + +/** + * Coalesce high-frequency download ticks so Comlink callbacks do not flood the + * main thread. Phase changes always flush immediately. + */ +export function createThrottledInitProgress( + onProgress: SourceInitProgressCallback | undefined, + intervalMs = DEFAULT_INTERVAL_MS, +): SourceInitProgressCallback | undefined { + if (!onProgress) { + return undefined; + } + let lastSentAt = 0; + let lastPhase: SourceInitPhase | undefined; + return (progress: SourceInitProgress) => { + const now = Date.now(); + const phaseChanged = progress.phase !== lastPhase; + if (!phaseChanged && now - lastSentAt < intervalMs) { + return; + } + lastSentAt = now; + lastPhase = progress.phase; + onProgress(progress); + }; +} + +/** Remember the last byte counts so phase-only updates keep the current request stats. */ +export function trackInitProgress( + onProgress?: SourceInitProgressCallback, +): (update: Partial & Pick) => void { + const report = createThrottledInitProgress(onProgress); + let last: SourceInitProgress = { phase: 'connecting', loadedBytes: 0, totalBytes: 0 }; + return (update) => { + last = { ...last, ...update }; + report?.(last); + }; +} diff --git a/src/infra/workers/types.ts b/src/infra/workers/types.ts index ce6dd42..bd61afd 100644 --- a/src/infra/workers/types.ts +++ b/src/infra/workers/types.ts @@ -1,3 +1,4 @@ +import type { SourceInitPhase } from '@/core/types/player'; import type { DataQualityReport, Initialization, MessageEvent, Time, TimeRange } from '@/core/types/ros'; import type { TransportDiagnostics, WorkerTransportConfig } from './transport'; import type { Range } from '@/shared/utils/ranges'; @@ -43,6 +44,20 @@ export interface LoadProgress { bufferedAheadMs?: number; } +export type { SourceInitPhase }; + +export interface SourceInitProgress { + phase: SourceInitPhase; + /** Bytes received in the current HTTP range (or full file for db3). */ + loadedBytes: number; + /** Size of the current request, or file size when known. */ + totalBytes: number; + /** Session-cumulative HTTP bytes (monotonic). */ + transferredBytes?: number; +} + +export type SourceInitProgressCallback = (progress: SourceInitProgress) => void; + /** `T` is the deserialized message payload carried in {@link MessageEvent.message}. */ export interface IMessageCursor { next(): Promise>>; @@ -54,7 +69,10 @@ export interface IMessageCursor { } export interface IWorkerSerializedSourceWorker { - initialize(args: Record): Promise; + initialize( + args: Record, + onProgress?: SourceInitProgressCallback, + ): Promise; configureTransport(config: WorkerTransportConfig): Promise; startDataQualityScan(): Promise; getMessageCursor(args: MessageIteratorArgs): Promise>; diff --git a/src/shared/intl/messages/en/welcome.json b/src/shared/intl/messages/en/welcome.json index 13d8846..e312f4d 100644 --- a/src/shared/intl/messages/en/welcome.json +++ b/src/shared/intl/messages/en/welcome.json @@ -50,8 +50,12 @@ "welcome.openDirectory": "Choose folder", "welcome.fileTypes": "mcap · bag · db3 · hdf5 · bvh", "welcome.directoryHint": "All matching files in folder", - "welcome.loadingTitle": "Opening…", + "welcome.loadingTitle": "Loading", "welcome.loadingPhase.preparing": "Preparing…", + "welcome.loadingSpeed": "{speed}/s", + "welcome.loadingEtaSeconds": "about {n}s", + "welcome.loadingEtaMinutes": "about {n} min", + "welcome.loadingEtaHours": "about {n} hr", "welcome.cancelLoading": "Cancel", "welcome.manualOpenFileHint": "Please manually open the {name} file.", "welcome.manualOpenFolderHint": "Please manually open the {name} folder." diff --git a/src/shared/intl/messages/ja/welcome.json b/src/shared/intl/messages/ja/welcome.json index 55348f5..191153e 100644 --- a/src/shared/intl/messages/ja/welcome.json +++ b/src/shared/intl/messages/ja/welcome.json @@ -50,8 +50,12 @@ "welcome.openDirectory": "フォルダを選ぶ", "welcome.fileTypes": "mcap · bag · db3 · hdf5 · bvh", "welcome.directoryHint": "フォルダ内の該当ファイルすべて", - "welcome.loadingTitle": "開いています…", + "welcome.loadingTitle": "読み込み中", "welcome.loadingPhase.preparing": "準備中…", + "welcome.loadingSpeed": "{speed}/s", + "welcome.loadingEtaSeconds": "約 {n} 秒", + "welcome.loadingEtaMinutes": "約 {n} 分", + "welcome.loadingEtaHours": "約 {n} 時間", "welcome.cancelLoading": "キャンセル", "welcome.manualOpenFileHint": "{name} ファイルを手動で開いてください。", "welcome.manualOpenFolderHint": "{name} フォルダを手動で開いてください。" diff --git a/src/shared/intl/messages/zh/welcome.json b/src/shared/intl/messages/zh/welcome.json index ba208f3..2e3d42e 100644 --- a/src/shared/intl/messages/zh/welcome.json +++ b/src/shared/intl/messages/zh/welcome.json @@ -50,8 +50,12 @@ "welcome.openDirectory": "选择文件夹", "welcome.fileTypes": "mcap · bag · db3 · hdf5 · bvh", "welcome.directoryHint": "导入文件夹内全部匹配文件", - "welcome.loadingTitle": "正在打开…", + "welcome.loadingTitle": "加载中", "welcome.loadingPhase.preparing": "准备中…", + "welcome.loadingSpeed": "{speed}/s", + "welcome.loadingEtaSeconds": "约 {n} 秒", + "welcome.loadingEtaMinutes": "约 {n} 分钟", + "welcome.loadingEtaHours": "约 {n} 小时", "welcome.cancelLoading": "取消", "welcome.manualOpenFileHint": "请手动打开 {name} 文件。", "welcome.manualOpenFolderHint": "请手动打开 {name} 文件夹。" diff --git a/src/shared/utils/formatEta.test.ts b/src/shared/utils/formatEta.test.ts new file mode 100644 index 0000000..01ea092 --- /dev/null +++ b/src/shared/utils/formatEta.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest'; +import { estimateEtaSeconds, formatEtaParts } from './formatEta'; + +describe('formatEta', () => { + it('estimates remaining seconds from throughput', () => { + expect(estimateEtaSeconds(20_000_000, 2_000_000)).toBe(10); + expect(estimateEtaSeconds(0, 2_000_000)).toBeUndefined(); + expect(estimateEtaSeconds(10, 0)).toBeUndefined(); + }); + + it('buckets durations into seconds, minutes, and hours', () => { + expect(formatEtaParts(20)).toEqual({ unit: 'seconds', n: 20 }); + expect(formatEtaParts(90)).toEqual({ unit: 'minutes', n: 2 }); + expect(formatEtaParts(7200)).toEqual({ unit: 'hours', n: 2 }); + }); +}); diff --git a/src/shared/utils/formatEta.ts b/src/shared/utils/formatEta.ts new file mode 100644 index 0000000..ec5d6d4 --- /dev/null +++ b/src/shared/utils/formatEta.ts @@ -0,0 +1,19 @@ +export type EtaUnit = 'seconds' | 'minutes' | 'hours'; + +export function estimateEtaSeconds(remainingBytes: number, bytesPerSecond: number): number | undefined { + if (!(bytesPerSecond > 0) || !(remainingBytes > 0) || !Number.isFinite(remainingBytes)) { + return undefined; + } + const seconds = remainingBytes / bytesPerSecond; + return Number.isFinite(seconds) ? seconds : undefined; +} + +export function formatEtaParts(seconds: number): { unit: EtaUnit; n: number } { + if (seconds < 60) { + return { unit: 'seconds', n: Math.max(1, Math.round(seconds)) }; + } + if (seconds < 3600) { + return { unit: 'minutes', n: Math.max(1, Math.round(seconds / 60)) }; + } + return { unit: 'hours', n: Math.max(1, Math.round(seconds / 3600)) }; +} diff --git a/src/shared/utils/sourceDisplayName.test.ts b/src/shared/utils/sourceDisplayName.test.ts new file mode 100644 index 0000000..d2bebf1 --- /dev/null +++ b/src/shared/utils/sourceDisplayName.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from 'vitest'; +import { sourceDisplayName } from './sourceDisplayName'; + +describe('sourceDisplayName', () => { + it('returns the URL basename and ignores query strings', () => { + expect( + sourceDisplayName( + 'https://cdn.example.com/bags/RealMan_PicknPlace.mcap?sign=abc', + ), + ).toBe('RealMan_PicknPlace.mcap'); + }); + + it('returns the local filename as-is', () => { + expect(sourceDisplayName('run.mcap')).toBe('run.mcap'); + expect(sourceDisplayName('/tmp/data/run.bag')).toBe('run.bag'); + }); +}); diff --git a/src/shared/utils/sourceDisplayName.ts b/src/shared/utils/sourceDisplayName.ts new file mode 100644 index 0000000..d3b7fa1 --- /dev/null +++ b/src/shared/utils/sourceDisplayName.ts @@ -0,0 +1,18 @@ +/** Basename for a URL or local filename, used in the loading overlay and navbar. */ +export function sourceDisplayName(sourceName?: string): string | undefined { + if (!sourceName) { + return undefined; + } + const trimmed = sourceName.trim(); + if (!trimmed) { + return undefined; + } + try { + const url = new URL(trimmed); + const base = url.pathname.split('/').filter(Boolean).pop(); + return base ? decodeURIComponent(base) : trimmed; + } catch { + const slash = Math.max(trimmed.lastIndexOf('/'), trimmed.lastIndexOf('\\')); + return slash >= 0 ? trimmed.slice(slash + 1) : trimmed; + } +} diff --git a/vite.config.ts b/vite.config.ts index 5b24076..65546d0 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -28,10 +28,10 @@ export default defineConfig({ plugins: [react()], resolve: { alias: { - '@': path.resolve(__dirname, './src'), + '@': path.resolve(import.meta.dirname, './src'), // protobufjs probes require('fs') at load; silent null avoids Vite's noisy external stub. // Keep `node:fs` unaliased so Vitest/Node fixtures can still use the real module. - fs: path.resolve(__dirname, './src/shims/empty-fs.js'), + fs: path.resolve(import.meta.dirname, './src/shims/empty-fs.js'), }, }, // Native Vite asset URLs (`*.wasm?url`); no vite-plugin-wasm (ESM wasm import path unused).