feat: enhance experiment designer action definitions, refactor trial analysis UI, and update video playback controls

This commit is contained in:
2026-03-01 19:00:23 -05:00
parent 60d4fae72c
commit 61af467cc8
22 changed files with 591 additions and 269 deletions

178
bun.lock
View File

@@ -73,6 +73,7 @@
"@eslint/eslintrc": "^3.3.3", "@eslint/eslintrc": "^3.3.3",
"@tailwindcss/postcss": "^4.1.18", "@tailwindcss/postcss": "^4.1.18",
"@types/bcryptjs": "^3.0.0", "@types/bcryptjs": "^3.0.0",
"@types/bun": "^1.3.9",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/node": "^20.19.33", "@types/node": "^20.19.33",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
@@ -89,6 +90,7 @@
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.55.0", "typescript-eslint": "^8.55.0",
"vitest": "^4.0.18",
}, },
}, },
}, },
@@ -247,10 +249,16 @@
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.19.12", "", { "os": "linux", "cpu": "x64" }, "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg=="], "@esbuild/linux-x64": ["@esbuild/linux-x64@0.19.12", "", { "os": "linux", "cpu": "x64" }, "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.19.12", "", { "os": "none", "cpu": "x64" }, "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA=="], "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.19.12", "", { "os": "none", "cpu": "x64" }, "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.19.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw=="], "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.19.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.19.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA=="], "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.19.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.19.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A=="], "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.19.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A=="],
@@ -509,6 +517,56 @@
"@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="],
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
"@rushstack/eslint-patch": ["@rushstack/eslint-patch@1.12.0", "", {}, "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw=="], "@rushstack/eslint-patch": ["@rushstack/eslint-patch@1.12.0", "", {}, "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw=="],
@@ -617,6 +675,8 @@
"@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], "@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
@@ -673,8 +733,14 @@
"@types/bcryptjs": ["@types/bcryptjs@3.0.0", "", { "dependencies": { "bcryptjs": "*" } }, "sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg=="], "@types/bcryptjs": ["@types/bcryptjs@3.0.0", "", { "dependencies": { "bcryptjs": "*" } }, "sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg=="],
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
"@types/crypto-js": ["@types/crypto-js@4.2.2", "", {}, "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ=="], "@types/crypto-js": ["@types/crypto-js@4.2.2", "", {}, "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ=="],
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/js-cookie": ["@types/js-cookie@3.0.6", "", {}, "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ=="], "@types/js-cookie": ["@types/js-cookie@3.0.6", "", {}, "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ=="],
@@ -749,6 +815,20 @@
"@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="], "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="],
"@vitest/expect": ["@vitest/expect@4.0.18", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ=="],
"@vitest/mocker": ["@vitest/mocker@4.0.18", "", { "dependencies": { "@vitest/spy": "4.0.18", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ=="],
"@vitest/pretty-format": ["@vitest/pretty-format@4.0.18", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw=="],
"@vitest/runner": ["@vitest/runner@4.0.18", "", { "dependencies": { "@vitest/utils": "4.0.18", "pathe": "^2.0.3" } }, "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw=="],
"@vitest/snapshot": ["@vitest/snapshot@4.0.18", "", { "dependencies": { "@vitest/pretty-format": "4.0.18", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA=="],
"@vitest/spy": ["@vitest/spy@4.0.18", "", {}, "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw=="],
"@vitest/utils": ["@vitest/utils@4.0.18", "", { "dependencies": { "@vitest/pretty-format": "4.0.18", "tinyrainbow": "^3.0.3" } }, "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA=="],
"@zxing/text-encoding": ["@zxing/text-encoding@0.9.0", "", {}, "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA=="], "@zxing/text-encoding": ["@zxing/text-encoding@0.9.0", "", {}, "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA=="],
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
@@ -783,6 +863,8 @@
"arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="],
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
"ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="], "ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="],
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
@@ -821,6 +903,8 @@
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
@@ -831,6 +915,8 @@
"caniuse-lite": ["caniuse-lite@1.0.30001731", "", {}, "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg=="], "caniuse-lite": ["caniuse-lite@1.0.30001731", "", {}, "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg=="],
"chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
@@ -915,6 +1001,8 @@
"es-iterator-helpers": ["es-iterator-helpers@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.0.3", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.6", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.4", "safe-array-concat": "^1.1.3" } }, "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w=="], "es-iterator-helpers": ["es-iterator-helpers@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.0.3", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.6", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.4", "safe-array-concat": "^1.1.3" } }, "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w=="],
"es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
@@ -961,12 +1049,16 @@
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
"execa": ["execa@7.2.0", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.1", "human-signals": "^4.3.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^3.0.7", "strip-final-newline": "^3.0.0" } }, "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA=="], "execa": ["execa@7.2.0", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.1", "human-signals": "^4.3.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^3.0.7", "strip-final-newline": "^3.0.0" } }, "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA=="],
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="], "fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="],
@@ -979,7 +1071,7 @@
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
"fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
@@ -1001,6 +1093,8 @@
"fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="], "fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="], "function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="],
@@ -1257,6 +1351,8 @@
"object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
"onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="], "onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="],
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
@@ -1277,6 +1373,8 @@
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
@@ -1345,6 +1443,8 @@
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
@@ -1385,6 +1485,8 @@
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
@@ -1401,6 +1503,10 @@
"stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="], "stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="],
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
"stdin-discarder": ["stdin-discarder@0.1.0", "", { "dependencies": { "bl": "^5.0.0" } }, "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ=="], "stdin-discarder": ["stdin-discarder@0.1.0", "", { "dependencies": { "bl": "^5.0.0" } }, "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ=="],
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
@@ -1451,7 +1557,13 @@
"through2": ["through2@4.0.2", "", { "dependencies": { "readable-stream": "3" } }, "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw=="], "through2": ["through2@4.0.2", "", { "dependencies": { "readable-stream": "3" } }, "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw=="],
"tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
@@ -1499,6 +1611,10 @@
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
"vitest": ["vitest@4.0.18", "", { "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", "@vitest/pretty-format": "4.0.18", "@vitest/runner": "4.0.18", "@vitest/snapshot": "4.0.18", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.18", "@vitest/browser-preview": "4.0.18", "@vitest/browser-webdriverio": "4.0.18", "@vitest/ui": "4.0.18", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ=="],
"wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="],
"web-encoding": ["web-encoding@1.1.5", "", { "dependencies": { "util": "^0.12.3" }, "optionalDependencies": { "@zxing/text-encoding": "0.9.0" } }, "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA=="], "web-encoding": ["web-encoding@1.1.5", "", { "dependencies": { "util": "^0.12.3" }, "optionalDependencies": { "@zxing/text-encoding": "0.9.0" } }, "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA=="],
@@ -1515,6 +1631,8 @@
"which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="], "which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="],
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], "ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
@@ -1631,14 +1749,14 @@
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"@typescript-eslint/typescript-estree/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"cmdk/@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw=="], "cmdk/@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw=="],
"cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-import-resolver-typescript/tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], "eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], "eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
@@ -1679,6 +1797,8 @@
"sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"vite/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
"@aws-crypto/crc32/@aws-sdk/types/@smithy/types": ["@smithy/types@4.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA=="], "@aws-crypto/crc32/@aws-sdk/types/@smithy/types": ["@smithy/types@4.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA=="],
"@aws-crypto/crc32c/@aws-sdk/types/@smithy/types": ["@smithy/types@4.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA=="], "@aws-crypto/crc32c/@aws-sdk/types/@smithy/types": ["@smithy/types@4.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA=="],
@@ -1745,8 +1865,6 @@
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
"@typescript-eslint/typescript-estree/tinyglobby/fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"cmdk/@radix-ui/react-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], "cmdk/@radix-ui/react-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="],
"cmdk/@radix-ui/react-dialog/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ=="], "cmdk/@radix-ui/react-dialog/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ=="],
@@ -1759,8 +1877,56 @@
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"eslint-import-resolver-typescript/tinyglobby/fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="],
"restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], "restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
"vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
"vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
"vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
"vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
"vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
"vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
"vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
"vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
"vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
"vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
"vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
"vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
"vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
"vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
"vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
"vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
"vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
"vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
"vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
"vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
"vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
"vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],

45
errors.txt Normal file
View File

@@ -0,0 +1,45 @@
scripts/seed-dev.ts(762,61): error TS2769: No overload matches this call.
Overload 1 of 2, '(value: { experimentId: string | SQL<unknown> | Placeholder<string, any>; duration?: number | SQL<unknown> | Placeholder<string, any> | null | undefined; id?: string | ... 2 more ... | undefined; ... 11 more ...; parameters?: unknown; }): PgInsertBase<...>', gave the following error.
Object literal may only specify known properties, and 'currentStepId' does not exist in type '{ experimentId: string | SQL<unknown> | Placeholder<string, any>; duration?: number | SQL<unknown> | Placeholder<string, any> | null | undefined; id?: string | SQL<...> | Placeholder<...> | undefined; ... 11 more ...; parameters?: unknown; }'.
Overload 2 of 2, '(values: { experimentId: string | SQL<unknown> | Placeholder<string, any>; duration?: number | SQL<unknown> | Placeholder<string, any> | null | undefined; id?: string | ... 2 more ... | undefined; ... 11 more ...; parameters?: unknown; }[]): PgInsertBase<...>', gave the following error.
Object literal may only specify known properties, and 'experimentId' does not exist in type '{ experimentId: string | SQL<unknown> | Placeholder<string, any>; duration?: number | SQL<unknown> | Placeholder<string, any> | null | undefined; id?: string | SQL<...> | Placeholder<...> | undefined; ... 11 more ...; parameters?: unknown; }[]'.
src/app/(dashboard)/studies/[id]/trials/[trialId]/analysis/page.tsx(99,13): error TS2322: Type '{ startedAt: Date | null; completedAt: Date | null; eventCount: any; mediaCount: any; media: { url: string; contentType: string; id: string; trialId: string; mediaType: "video" | "audio" | "image" | null; ... 8 more ...; createdAt: Date; }[]; ... 13 more ...; participant: { ...; }; }' is not assignable to type '{ id: string; status: string; startedAt: Date | null; completedAt: Date | null; duration: number | null; experiment: { name: string; studyId: string; }; participant: { participantCode: string; }; eventCount?: number | undefined; mediaCount?: number | undefined; media?: { ...; }[] | undefined; }'.
Types of property 'media' are incompatible.
Type '{ url: string; contentType: string; id: string; trialId: string; mediaType: "video" | "audio" | "image" | null; storagePath: string; fileSize: number | null; duration: number | null; format: string | null; ... 4 more ...; createdAt: Date; }[]' is not assignable to type '{ url: string; mediaType: string; format?: string | undefined; contentType?: string | undefined; }[]'.
Type '{ url: string; contentType: string; id: string; trialId: string; mediaType: "video" | "audio" | "image" | null; storagePath: string; fileSize: number | null; duration: number | null; format: string | null; ... 4 more ...; createdAt: Date; }' is not assignable to type '{ url: string; mediaType: string; format?: string | undefined; contentType?: string | undefined; }'.
Types of property 'mediaType' are incompatible.
Type 'string | null' is not assignable to type 'string'.
Type 'null' is not assignable to type 'string'.
src/lib/experiment-designer/__tests__/control-flow.test.ts(2,38): error TS2307: Cannot find module 'vitest' or its corresponding type declarations.
src/lib/experiment-designer/__tests__/control-flow.test.ts(64,16): error TS2532: Object is possibly 'undefined'.
src/lib/experiment-designer/__tests__/control-flow.test.ts(65,17): error TS2532: Object is possibly 'undefined'.
src/lib/experiment-designer/__tests__/control-flow.test.ts(70,16): error TS2532: Object is possibly 'undefined'.
src/lib/experiment-designer/__tests__/control-flow.test.ts(71,17): error TS2532: Object is possibly 'undefined'.
src/lib/experiment-designer/__tests__/control-flow.test.ts(72,17): error TS2532: Object is possibly 'undefined'.
src/lib/experiment-designer/__tests__/control-flow.test.ts(100,16): error TS2532: Object is possibly 'undefined'.
src/lib/experiment-designer/__tests__/control-flow.test.ts(101,17): error TS2532: Object is possibly 'undefined'.
src/lib/experiment-designer/__tests__/control-flow.test.ts(107,17): error TS2532: Object is possibly 'undefined'.
src/lib/experiment-designer/__tests__/control-flow.test.ts(108,17): error TS2532: Object is possibly 'undefined'.
src/lib/experiment-designer/__tests__/hashing.test.ts(2,38): error TS2307: Cannot find module 'vitest' or its corresponding type declarations.
src/lib/experiment-designer/__tests__/hashing.test.ts(65,19): error TS2741: Property 'category' is missing in type '{ id: string; type: string; name: string; parameters: { message: string; }; source: { kind: "core"; baseActionId: string; }; execution: { transport: "internal"; }; }' but required in type 'ExperimentAction'.
src/lib/experiment-designer/__tests__/hashing.test.ts(86,19): error TS2741: Property 'category' is missing in type '{ id: string; type: string; name: string; parameters: { message: string; }; source: { kind: "core"; baseActionId: string; }; execution: { transport: "internal"; }; }' but required in type 'ExperimentAction'.
src/lib/experiment-designer/__tests__/store.test.ts(2,50): error TS2307: Cannot find module 'vitest' or its corresponding type declarations.
src/lib/experiment-designer/__tests__/store.test.ts(39,16): error TS2532: Object is possibly 'undefined'.
src/lib/experiment-designer/__tests__/store.test.ts(58,16): error TS2532: Object is possibly 'undefined'.
src/lib/experiment-designer/__tests__/store.test.ts(103,16): error TS2532: Object is possibly 'undefined'.
src/lib/experiment-designer/__tests__/store.test.ts(104,16): error TS2532: Object is possibly 'undefined'.
src/lib/experiment-designer/__tests__/store.test.ts(107,16): error TS2532: Object is possibly 'undefined'.
src/lib/experiment-designer/__tests__/store.test.ts(108,16): error TS2532: Object is possibly 'undefined'.
src/lib/experiment-designer/__tests__/store.test.ts(123,15): error TS2741: Property 'category' is missing in type '{ id: string; type: string; name: string; parameters: {}; source: { kind: "core"; baseActionId: string; }; execution: { transport: "internal"; }; }' but required in type 'ExperimentAction'.
src/lib/experiment-designer/__tests__/store.test.ts(135,16): error TS18048: 'storedStep' is possibly 'undefined'.
src/lib/experiment-designer/__tests__/store.test.ts(136,16): error TS18048: 'storedStep' is possibly 'undefined'.
src/lib/experiment-designer/__tests__/store.test.ts(136,16): error TS2532: Object is possibly 'undefined'.
src/lib/experiment-designer/__tests__/validators.test.ts(2,38): error TS2307: Cannot find module 'vitest' or its corresponding type declarations.
src/lib/experiment-designer/__tests__/validators.test.ts(11,5): error TS2322: Type '"utility"' is not assignable to type 'ActionCategory'.
src/lib/experiment-designer/__tests__/validators.test.ts(14,91): error TS2353: Object literal may only specify known properties, and 'default' does not exist in type 'ActionParameter'.
src/lib/experiment-designer/__tests__/validators.test.ts(36,20): error TS2532: Object is possibly 'undefined'.
src/lib/experiment-designer/__tests__/validators.test.ts(58,17): error TS2353: Object literal may only specify known properties, and 'order' does not exist in type 'ExperimentAction'.
src/lib/experiment-designer/__tests__/validators.test.ts(78,17): error TS2353: Object literal may only specify known properties, and 'order' does not exist in type 'ExperimentAction'.
src/lib/experiment-designer/__tests__/validators.test.ts(107,17): error TS2353: Object literal may only specify known properties, and 'order' does not exist in type 'ExperimentAction'.
src/lib/experiment-designer/__tests__/validators.test.ts(119,20): error TS2532: Object is possibly 'undefined'.
src/server/services/__tests__/trial-execution.test.ts(2,56): error TS2307: Cannot find module 'bun:test' or its corresponding type declarations.

View File

@@ -91,6 +91,7 @@
"@eslint/eslintrc": "^3.3.3", "@eslint/eslintrc": "^3.3.3",
"@tailwindcss/postcss": "^4.1.18", "@tailwindcss/postcss": "^4.1.18",
"@types/bcryptjs": "^3.0.0", "@types/bcryptjs": "^3.0.0",
"@types/bun": "^1.3.9",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/node": "^20.19.33", "@types/node": "^20.19.33",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
@@ -106,7 +107,8 @@
"ts-unused-exports": "^11.0.1", "ts-unused-exports": "^11.0.1",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.55.0" "typescript-eslint": "^8.55.0",
"vitest": "^4.0.18"
}, },
"ct3aMetadata": { "ct3aMetadata": {
"initVersion": "7.39.3" "initVersion": "7.39.3"

View File

@@ -758,20 +758,12 @@ async function main() {
startTime.setMinutes(startTime.getMinutes() - 10); // Started 10 mins ago startTime.setMinutes(startTime.getMinutes() - 10); // Started 10 mins ago
const endTime = new Date(); // Ended just now const endTime = new Date(); // Ended just now
// Create the trial
const [analyticsTrial] = await db.insert(schema.trials).values({ const [analyticsTrial] = await db.insert(schema.trials).values({
experimentId: experiment!.id, experimentId: experiment!.id,
participantId: p101.id, participantId: p101.id,
// studyId is not in trials table, it is inferred from experiment
status: "completed", status: "completed",
startedAt: startTime, startedAt: startTime,
completedAt: endTime, completedAt: endTime,
currentStepId: step5!.id, // Ended at last step
runId: randomUUID(),
metadata: {
condition: "HRIStudio",
notes: "Seeded for analytics testing"
}
}).returning(); }).returning();
// Create a series of events // Create a series of events

View File

@@ -92,6 +92,12 @@ function AnalysisPageContent() {
completedAt: trial.completedAt ? new Date(trial.completedAt) : null, completedAt: trial.completedAt ? new Date(trial.completedAt) : null,
eventCount: (trial as any).eventCount, eventCount: (trial as any).eventCount,
mediaCount: (trial as any).mediaCount, mediaCount: (trial as any).mediaCount,
media: trial.media?.map(m => ({
...m,
mediaType: m.mediaType ?? "video",
format: m.format ?? undefined,
contentType: m.contentType ?? undefined
})) ?? [],
}; };
return ( return (

View File

@@ -21,6 +21,12 @@ import {
Search, Search,
Settings, Settings,
Users, Users,
Radio,
Gamepad2,
AlertTriangle,
Bot,
User,
MessageSquare,
} from "lucide-react"; } from "lucide-react";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
@@ -69,14 +75,13 @@ export default function DashboardPage() {
studyId: studyFilter ?? undefined, studyId: studyFilter ?? undefined,
}); });
const { data: scheduledTrials } = api.trials.list.useQuery({ const { data: liveTrials } = api.dashboard.getLiveTrials.useQuery(
studyId: studyFilter ?? undefined, { studyId: studyFilter ?? undefined },
status: "scheduled", { refetchInterval: 5000 }
limit: 5, );
});
const { data: recentActivity } = api.dashboard.getRecentActivity.useQuery({ const { data: recentActivity } = api.dashboard.getRecentActivity.useQuery({
limit: 10, limit: 15,
studyId: studyFilter ?? undefined, studyId: studyFilter ?? undefined,
}); });
@@ -164,10 +169,10 @@ export default function DashboardPage() {
iconColor="text-violet-500" iconColor="text-violet-500"
/> />
<StatsCard <StatsCard
title="Total Studies" title="Total Interventions"
value={userStudies.length} value={stats?.totalInterventions ?? 0}
icon={FlaskConical} icon={Gamepad2}
description="Active research projects" description="Wizard manual overrides"
iconColor="text-orange-500" iconColor="text-orange-500"
/> />
</div> </div>
@@ -250,21 +255,44 @@ export default function DashboardPage() {
<CardContent> <CardContent>
<ScrollArea className="h-[400px] pr-4"> <ScrollArea className="h-[400px] pr-4">
<div className="space-y-4"> <div className="space-y-4">
{recentActivity?.map((activity) => ( {recentActivity?.map((activity) => {
<div key={activity.id} className="relative pl-4 pb-1 border-l last:border-0 border-muted-foreground/20"> let eventColor = "bg-primary/30 ring-background";
<span className="absolute left-[-5px] top-1 h-2.5 w-2.5 rounded-full bg-primary/30 ring-4 ring-background" /> let Icon = Activity;
<div className="mb-1 text-sm font-medium leading-none">{activity.title}</div> if (activity.type === "trial_started") {
eventColor = "bg-blue-500 ring-blue-100 dark:ring-blue-900";
Icon = PlayCircle;
} else if (activity.type === "trial_completed") {
eventColor = "bg-green-500 ring-green-100 dark:ring-green-900";
Icon = CheckCircle;
} else if (activity.type === "error") {
eventColor = "bg-red-500 ring-red-100 dark:ring-red-900";
Icon = AlertTriangle;
} else if (activity.type === "intervention") {
eventColor = "bg-orange-500 ring-orange-100 dark:ring-orange-900";
Icon = Gamepad2;
} else if (activity.type === "annotation") {
eventColor = "bg-yellow-500 ring-yellow-100 dark:ring-yellow-900";
Icon = MessageSquare;
}
return (
<div key={activity.id} className="relative pl-6 pb-4 border-l last:border-0 border-muted-foreground/20">
<span className={`absolute left-[-9px] top-0 h-4 w-4 rounded-full flex items-center justify-center ring-4 ${eventColor}`}>
<Icon className="h-2.5 w-2.5 text-white" />
</span>
<div className="mb-0.5 text-sm font-medium leading-none">{activity.title}</div>
<div className="text-xs text-muted-foreground mb-1">{activity.description}</div> <div className="text-xs text-muted-foreground mb-1">{activity.description}</div>
<div className="text-[10px] text-muted-foreground/70 uppercase"> <div className="text-[10px] text-muted-foreground/70 uppercase font-mono">
{formatDistanceToNow(activity.time, { addSuffix: true })} {formatDistanceToNow(new Date(activity.time), { addSuffix: true })}
</div> </div>
</div> </div>
))} )
})}
{!recentActivity?.length && ( {!recentActivity?.length && (
<div className="flex flex-col items-center justify-center py-8 text-center text-muted-foreground"> <div className="flex flex-col items-center justify-center py-8 text-center text-muted-foreground">
<Clock className="h-10 w-10 mb-3 opacity-20" /> <Clock className="h-10 w-10 mb-3 opacity-20" />
<p>No recent activity recorded.</p> <p>No recent activity recorded.</p>
<p className="text-sm">Start a trial to see updates here.</p> <p className="text-xs mt-1">Start a trial to see experiment events stream here.</p>
</div> </div>
)} )}
</div> </div>
@@ -274,52 +302,58 @@ export default function DashboardPage() {
</div> </div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7"> <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
{/* Scheduled Trials (Restored from previous page.tsx but styled to fit) */} {/* Live Trials */}
<Card id="tour-scheduled-trials" className="col-span-4 border-muted/40 shadow-sm"> <Card id="tour-live-trials" className={`${liveTrials && liveTrials.length > 0 ? 'border-primary shadow-sm bg-primary/5' : 'border-muted/40'} col-span-4 transition-colors duration-500`}>
<CardHeader> <CardHeader>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<CardTitle>Upcoming Sessions</CardTitle> <CardTitle className="flex items-center gap-2">
Live Sessions
{liveTrials && liveTrials.length > 0 && <span className="relative flex h-3 w-3">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-3 w-3 bg-red-500"></span>
</span>}
</CardTitle>
<CardDescription> <CardDescription>
You have {scheduledTrials?.length ?? 0} scheduled trials coming up. Currently running trials in the Wizard interface
</CardDescription> </CardDescription>
</div> </div>
<Button variant="ghost" size="sm" asChild> <Button variant="ghost" size="sm" asChild>
<Link href="/trials?status=scheduled">View All <ArrowRight className="ml-2 h-4 w-4" /></Link> <Link href="/trials">View All <ArrowRight className="ml-2 h-4 w-4" /></Link>
</Button> </Button>
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{!scheduledTrials?.length ? ( {!liveTrials?.length ? (
<div className="flex h-[150px] flex-col items-center justify-center rounded-md border border-dashed text-center animate-in fade-in-50"> <div className="flex h-[150px] flex-col items-center justify-center rounded-md border border-dashed border-muted-foreground/30 text-center animate-in fade-in-50 bg-background/50">
<Calendar className="h-8 w-8 text-muted-foreground/50" /> <Radio className="h-8 w-8 text-muted-foreground/50 mb-2" />
<p className="mt-2 text-sm text-muted-foreground">No scheduled trials found.</p> <p className="text-sm text-muted-foreground">No trials are currently running.</p>
<Button variant="link" size="sm" asChild className="mt-1"> <Button variant="link" size="sm" asChild className="mt-1">
<Link href="/trials/new">Schedule a Trial</Link> <Link href="/trials">Start a Trial</Link>
</Button> </Button>
</div> </div>
) : ( ) : (
<div className="space-y-4"> <div className="space-y-4">
{scheduledTrials.map((trial) => ( {liveTrials.map((trial) => (
<div key={trial.id} className="flex items-center justify-between rounded-lg border p-3 bg-muted/10 hover:bg-muted/50 hover:shadow-sm transition-all duration-200"> <div key={trial.id} className="flex items-center justify-between rounded-lg border border-primary/20 p-3 bg-background shadow-sm hover:shadow transition-all duration-200">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400"> <div className="flex h-10 w-10 items-center justify-center rounded-full bg-red-100 dark:bg-red-900/40 text-red-600 dark:text-red-400">
<Calendar className="h-5 w-5" /> <Radio className="h-5 w-5 animate-pulse" />
</div> </div>
<div> <div>
<p className="font-medium text-sm"> <p className="font-medium text-sm">
{trial.participant.participantCode} {trial.participantCode}
<span className="ml-2 text-muted-foreground font-normal text-xs"> {trial.experiment.name}</span> <span className="ml-2 text-muted-foreground font-normal text-xs"> {trial.experimentName}</span>
</p> </p>
<div className="flex items-center gap-2 text-xs text-muted-foreground"> <div className="flex items-center gap-2 text-xs text-muted-foreground">
<Clock className="h-3 w-3" /> <Clock className="h-3 w-3" />
{trial.scheduledAt ? format(trial.scheduledAt, "MMM d, h:mm a") : "Unscheduled"} Started {trial.startedAt ? formatDistanceToNow(new Date(trial.startedAt), { addSuffix: true }) : 'just now'}
</div> </div>
</div> </div>
</div> </div>
<Button size="sm" className="gap-2" asChild> <Button size="sm" className="gap-2 bg-primary hover:bg-primary/90" asChild>
<Link href={`/wizard/${trial.id}`}> <Link href={`/wizard/${trial.id}`}>
<Play className="h-3.5 w-3.5" /> Start <Play className="h-3.5 w-3.5" /> Spectate / Jump In
</Link> </Link>
</Button> </Button>
</div> </div>

View File

@@ -14,6 +14,7 @@ import {
Home, Home,
LogOut, LogOut,
MoreHorizontal, MoreHorizontal,
PlayCircle,
Puzzle, Puzzle,
Settings, Settings,
TestTube, TestTube,
@@ -23,6 +24,7 @@ import {
} from "lucide-react"; } from "lucide-react";
import { useSidebar } from "~/components/ui/sidebar"; import { useSidebar } from "~/components/ui/sidebar";
import { useTour } from "~/components/onboarding/TourProvider";
import { import {
DropdownMenu, DropdownMenu,
@@ -118,7 +120,13 @@ const helpItems = [
{ {
title: "Help Center", title: "Help Center",
url: "/help", url: "/help",
icon: BookOpen, // Make sure to import this from lucide-react icon: BookOpen,
},
{
title: "Interactive Tour",
url: "#tour",
icon: PlayCircle,
action: "tour",
}, },
]; ];
@@ -138,6 +146,8 @@ export function AppSidebar({
const { selectedStudyId, userStudies, selectStudy, refreshStudyData, isLoadingUserStudies } = const { selectedStudyId, userStudies, selectStudy, refreshStudyData, isLoadingUserStudies } =
useStudyManagement(); useStudyManagement();
const { startTour } = useTour();
// Reference to track if we've already attempted auto-selection to avoid fighting with manual clearing // Reference to track if we've already attempted auto-selection to avoid fighting with manual clearing
const hasAutoSelected = useRef(false); const hasAutoSelected = useRef(false);
@@ -566,7 +576,15 @@ export function AppSidebar({
{helpItems.map((item) => { {helpItems.map((item) => {
const isActive = pathname.startsWith(item.url); const isActive = pathname.startsWith(item.url);
const menuButton = ( const menuButton = item.action === "tour" ? (
<SidebarMenuButton
onClick={() => startTour("full_platform")}
isActive={false}
>
<item.icon className="h-4 w-4" />
<span>{item.title}</span>
</SidebarMenuButton>
) : (
<SidebarMenuButton asChild isActive={isActive}> <SidebarMenuButton asChild isActive={isActive}>
<Link href={item.url}> <Link href={item.url}>
<item.icon className="h-4 w-4" /> <item.icon className="h-4 w-4" />

View File

@@ -115,6 +115,7 @@ export function EventsDataTable({ data, startTime }: EventsDataTableProps) {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex flex-1 items-center space-x-2"> <div className="flex flex-1 items-center space-x-2">
<Input <Input
id="tour-analytics-filter"
placeholder="Search event data..." placeholder="Search event data..."
value={globalFilter} value={globalFilter}
onChange={(e) => setGlobalFilter(e.target.value)} onChange={(e) => setGlobalFilter(e.target.value)}
@@ -141,7 +142,7 @@ export function EventsDataTable({ data, startTime }: EventsDataTableProps) {
</div> </div>
</div> </div>
<div className="rounded-md border bg-background"> <div id="tour-analytics-table" className="rounded-md border bg-background">
<div> <div>
<Table className="w-full"> <Table className="w-full">
<TableHeader className="sticky top-0 bg-background z-10 shadow-sm"> <TableHeader className="sticky top-0 bg-background z-10 shadow-sm">

View File

@@ -43,16 +43,13 @@ export function EventTimeline() {
const startTime = useMemo(() => { const startTime = useMemo(() => {
if (contextStartTime) return new Date(contextStartTime).getTime(); if (contextStartTime) return new Date(contextStartTime).getTime();
if (sortedEvents.length > 0) return new Date(sortedEvents[0]!.timestamp).getTime();
return 0; return 0;
}, [contextStartTime, sortedEvents]); }, [contextStartTime]);
const effectiveDuration = useMemo(() => { const effectiveDuration = useMemo(() => {
if (duration > 0) return duration * 1000; if (duration > 0) return duration * 1000;
if (sortedEvents.length === 0) return 60000; // 1 min default return 60000; // 1 min default
const end = new Date(sortedEvents[sortedEvents.length - 1]!.timestamp).getTime(); }, [duration]);
return Math.max(end - startTime, 1000);
}, [duration, sortedEvents, startTime]);
// Dimensions // Dimensions
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);

View File

@@ -15,6 +15,7 @@ interface PlaybackContextType {
isPlaying: boolean; isPlaying: boolean;
playbackRate: number; playbackRate: number;
startTime?: Date; startTime?: Date;
endTime?: Date;
// Actions // Actions
play: () => void; play: () => void;
@@ -44,11 +45,23 @@ interface PlaybackProviderProps {
children: React.ReactNode; children: React.ReactNode;
events?: TrialEvent[]; events?: TrialEvent[];
startTime?: Date; startTime?: Date;
endTime?: Date;
} }
export function PlaybackProvider({ children, events = [], startTime }: PlaybackProviderProps) { export function PlaybackProvider({ children, events = [], startTime, endTime }: PlaybackProviderProps) {
const trialDuration = React.useMemo(() => {
if (startTime && endTime) return (new Date(endTime).getTime() - new Date(startTime).getTime()) / 1000;
return 0;
}, [startTime, endTime]);
const [currentTime, setCurrentTime] = useState(0); const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0); const [duration, setDuration] = useState(trialDuration);
useEffect(() => {
if (trialDuration > 0 && duration === 0) {
setDuration(trialDuration);
}
}, [trialDuration, duration]);
const [isPlaying, setIsPlaying] = useState(false); const [isPlaying, setIsPlaying] = useState(false);
const [playbackRate, setPlaybackRate] = useState(1); const [playbackRate, setPlaybackRate] = useState(1);
@@ -105,6 +118,8 @@ export function PlaybackProvider({ children, events = [], startTime }: PlaybackP
setCurrentTime, setCurrentTime,
events, events,
currentEventIndex, currentEventIndex,
startTime,
endTime,
}; };
return ( return (

View File

@@ -70,7 +70,6 @@ export function PlaybackPlayer({ src }: PlaybackPlayerProps) {
const handleLoadedMetadata = () => { const handleLoadedMetadata = () => {
if (videoRef.current) { if (videoRef.current) {
setDuration(videoRef.current.duration);
setIsBuffering(false); setIsBuffering(false);
} }
}; };
@@ -85,7 +84,6 @@ export function PlaybackPlayer({ src }: PlaybackPlayerProps) {
<video <video
ref={videoRef} ref={videoRef}
src={src} src={src}
controls
muted={muted} muted={muted}
className="w-full h-full object-contain" className="w-full h-full object-contain"
onTimeUpdate={handleTimeUpdate} onTimeUpdate={handleTimeUpdate}

View File

@@ -192,34 +192,34 @@ export function TrialAnalysisView({ trial, backHref }: TrialAnalysisViewProps) {
<div className="flex flex-col xl:flex-row gap-3 shrink-0"> <div className="flex flex-col xl:flex-row gap-3 shrink-0">
<Card id="tour-trial-metrics" className="shadow-sm flex-1"> <Card id="tour-trial-metrics" className="shadow-sm flex-1">
<CardContent className="p-0 h-full"> <CardContent className="p-0 h-full">
<div className="flex flex-row divide-x h-full"> <div className="grid grid-cols-2 grid-rows-2 h-full divide-x divide-y">
<div className="flex-1 flex flex-col p-3 px-4 justify-center"> <div className="flex flex-col p-4 md:p-6 justify-center">
<p className="text-xs font-medium text-muted-foreground flex items-center gap-1.5 mb-1"> <p className="text-sm font-medium text-muted-foreground flex items-center gap-1.5 mb-2">
<Clock className="h-3.5 w-3.5 text-blue-500" /> Duration <Clock className="h-4 w-4 text-blue-500" /> Duration
</p> </p>
<p className="text-base font-bold"> <p className="text-2xl font-bold">
{trial.duration ? <span>{Math.floor(trial.duration / 60)}m {trial.duration % 60}s</span> : "--:--"} {trial.duration ? <span>{Math.floor(trial.duration / 60)}m {trial.duration % 60}s</span> : "--:--"}
</p> </p>
</div> </div>
<div className="flex-1 flex flex-col p-3 px-4 justify-center"> <div className="flex flex-col p-4 md:p-6 justify-center border-t-0">
<p className="text-xs font-medium text-muted-foreground flex items-center gap-1.5 mb-1"> <p className="text-sm font-medium text-muted-foreground flex items-center gap-1.5 mb-2">
<Bot className="h-3.5 w-3.5 text-purple-500" /> Robot Actions <Bot className="h-4 w-4 text-purple-500" /> Robot Actions
</p> </p>
<p className="text-base font-bold">{robotActionCount}</p> <p className="text-2xl font-bold">{robotActionCount}</p>
</div> </div>
<div className="flex-1 flex flex-col p-3 px-4 justify-center"> <div className="flex flex-col p-4 md:p-6 justify-center">
<p className="text-xs font-medium text-muted-foreground flex items-center gap-1.5 mb-1"> <p className="text-sm font-medium text-muted-foreground flex items-center gap-1.5 mb-2">
<AlertTriangle className="h-3.5 w-3.5 text-orange-500" /> Interventions <AlertTriangle className="h-4 w-4 text-orange-500" /> Interventions
</p> </p>
<p className="text-base font-bold">{interventionCount}</p> <p className="text-2xl font-bold">{interventionCount}</p>
</div> </div>
<div className="flex-1 flex flex-col p-3 px-4 justify-center"> <div className="flex flex-col p-4 md:p-6 justify-center">
<p className="text-xs font-medium text-muted-foreground flex items-center gap-1.5 mb-1"> <p className="text-sm font-medium text-muted-foreground flex items-center gap-1.5 mb-2">
<Activity className="h-3.5 w-3.5 text-green-500" /> Completeness <Activity className="h-4 w-4 text-green-500" /> Completeness
</p> </p>
<div className="flex items-center gap-1.5 text-base font-bold"> <div className="flex items-center gap-2 text-2xl font-bold">
<span className={cn( <span className={cn(
"inline-block h-2 w-2 rounded-full", "inline-block h-3 w-3 rounded-full",
trial.status === 'completed' ? "bg-green-500" : "bg-yellow-500" trial.status === 'completed' ? "bg-green-500" : "bg-yellow-500"
)} /> )} />
{trial.status === 'completed' ? '100%' : 'Incomplete'} {trial.status === 'completed' ? '100%' : 'Incomplete'}
@@ -244,7 +244,7 @@ export function TrialAnalysisView({ trial, backHref }: TrialAnalysisViewProps) {
<div className="flex-1 min-h-0 rounded-xl border shadow-sm overflow-hidden bg-background flex flex-col"> <div className="flex-1 min-h-0 rounded-xl border shadow-sm overflow-hidden bg-background flex flex-col">
{/* FIXED TIMELINE: Always visible at top */} {/* FIXED TIMELINE: Always visible at top */}
<div className="shrink-0 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 p-1"> <div id="tour-trial-timeline" className="shrink-0 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 p-1">
<EventTimeline /> <EventTimeline />
</div> </div>

View File

@@ -1149,7 +1149,7 @@ export const WizardInterface = React.memo(function WizardInterface({
<TabsTrigger value="robot" className="text-xs flex-1">Robot Control</TabsTrigger> <TabsTrigger value="robot" className="text-xs flex-1">Robot Control</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="camera_obs" className="flex-1 flex flex-col m-0 p-0 h-full overflow-hidden min-h-0"> <TabsContent value="camera_obs" className="flex-1 flex-col m-0 p-0 h-full overflow-hidden min-h-0 data-[state=active]:flex">
<div className="flex-none bg-muted/30 border-b h-48 sm:h-56 relative group shrink-0"> <div className="flex-none bg-muted/30 border-b h-48 sm:h-56 relative group shrink-0">
<WebcamPanel readOnly={trial.status === 'completed'} trialId={trial.id} trialStatus={trial.status} /> <WebcamPanel readOnly={trial.status === 'completed'} trialId={trial.id} trialStatus={trial.status} />
</div> </div>
@@ -1164,7 +1164,7 @@ export const WizardInterface = React.memo(function WizardInterface({
</div> </div>
</TabsContent> </TabsContent>
<TabsContent value="robot" className="flex-1 m-0 h-full overflow-hidden"> <TabsContent value="robot" className="flex-1 flex-col m-0 p-0 h-full overflow-hidden min-h-0 data-[state=active]:flex">
<WizardMonitoringPanel <WizardMonitoringPanel
rosConnected={rosConnected} rosConnected={rosConnected}
rosConnecting={rosConnecting} rosConnecting={rosConnecting}

View File

@@ -4,21 +4,12 @@ import React, { useCallback, useRef, useState } from "react";
import Webcam from "react-webcam"; import Webcam from "react-webcam";
import { Camera, CameraOff, Video, StopCircle, Loader2 } from "lucide-react"; import { Camera, CameraOff, Video, StopCircle, Loader2 } from "lucide-react";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "~/components/ui/select";
import { Alert, AlertDescription } from "~/components/ui/alert"; import { Alert, AlertDescription } from "~/components/ui/alert";
import { AspectRatio } from "~/components/ui/aspect-ratio"; import { AspectRatio } from "~/components/ui/aspect-ratio";
import { toast } from "sonner"; import { toast } from "sonner";
import { api } from "~/trpc/react"; import { api } from "~/trpc/react";
export function WebcamPanel({ readOnly = false, trialId, trialStatus }: { readOnly?: boolean; trialId?: string; trialStatus?: string }) { export function WebcamPanel({ readOnly = false, trialId, trialStatus }: { readOnly?: boolean; trialId?: string; trialStatus?: string }) {
const [deviceId, setDeviceId] = useState<string | null>(null);
const [devices, setDevices] = useState<MediaDeviceInfo[]>([]);
const [isCameraEnabled, setIsCameraEnabled] = useState(false); const [isCameraEnabled, setIsCameraEnabled] = useState(false);
const [isRecording, setIsRecording] = useState(false); const [isRecording, setIsRecording] = useState(false);
const [uploading, setUploading] = useState(false); const [uploading, setUploading] = useState(false);
@@ -35,19 +26,11 @@ export function WebcamPanel({ readOnly = false, trialId, trialStatus }: { readOn
const saveRecordingMutation = api.storage.saveRecording.useMutation(); const saveRecordingMutation = api.storage.saveRecording.useMutation();
const logEventMutation = api.trials.logEvent.useMutation(); const logEventMutation = api.trials.logEvent.useMutation();
const handleDevices = useCallback(
(mediaDevices: MediaDeviceInfo[]) => {
setDevices(mediaDevices.filter(({ kind, deviceId }) => kind === "videoinput" && deviceId !== ""));
},
[setDevices],
);
const [isMounted, setIsMounted] = useState(false); const [isMounted, setIsMounted] = useState(false);
React.useEffect(() => { React.useEffect(() => {
setIsMounted(true); setIsMounted(true);
navigator.mediaDevices.enumerateDevices().then(handleDevices); }, []);
}, [handleDevices]);
const handleEnableCamera = () => { const handleEnableCamera = () => {
setIsCameraEnabled(true); setIsCameraEnabled(true);
@@ -87,6 +70,10 @@ export function WebcamPanel({ readOnly = false, trialId, trialStatus }: { readOn
const handleStartRecording = () => { const handleStartRecording = () => {
if (!webcamRef.current?.stream) return; if (!webcamRef.current?.stream) return;
if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") {
console.log("Already recording, skipping start");
return;
}
setIsRecording(true); setIsRecording(true);
chunksRef.current = []; chunksRef.current = [];
@@ -125,7 +112,7 @@ export function WebcamPanel({ readOnly = false, trialId, trialStatus }: { readOn
}; };
const handleStopRecording = () => { const handleStopRecording = () => {
if (mediaRecorderRef.current && isRecording) { if (mediaRecorderRef.current && isRecording && mediaRecorderRef.current.state === "recording") {
mediaRecorderRef.current.stop(); mediaRecorderRef.current.stop();
setIsRecording(false); setIsRecording(false);
if (trialId) { if (trialId) {
@@ -197,32 +184,10 @@ export function WebcamPanel({ readOnly = false, trialId, trialStatus }: { readOn
return ( return (
<div className="flex h-full flex-col"> <div className="flex h-full flex-col">
<div className="flex items-center justify-between border-b p-3"> <div className="flex items-center justify-end border-b px-2 py-1 bg-muted/10 h-10 shrink-0">
<h2 className="text-sm font-semibold flex items-center gap-2">
<Camera className="h-4 w-4" />
Webcam Feed
</h2>
{!readOnly && ( {!readOnly && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{devices.length > 0 && isMounted && (
<Select
value={deviceId ?? undefined}
onValueChange={setDeviceId}
disabled={!isCameraEnabled || isRecording}
>
<SelectTrigger className="h-7 w-[130px] text-xs">
<SelectValue placeholder="Select Camera" />
</SelectTrigger>
<SelectContent>
{devices.map((device, key) => (
<SelectItem key={key} value={device.deviceId} className="text-xs">
{device.label || `Camera ${key + 1}`}
</SelectItem>
))}
</SelectContent>
</Select>
)}
{isCameraEnabled && ( {isCameraEnabled && (
!isRecording ? ( !isRecording ? (
@@ -284,7 +249,6 @@ export function WebcamPanel({ readOnly = false, trialId, trialStatus }: { readOn
audio={false} audio={false}
width="100%" width="100%"
height="100%" height="100%"
videoConstraints={{ deviceId: deviceId ?? undefined }}
onUserMedia={handleUserMedia} onUserMedia={handleUserMedia}
onUserMediaError={(err) => setError(String(err))} onUserMediaError={(err) => setError(String(err))}
className="object-contain w-full h-full" className="object-contain w-full h-full"

View File

@@ -82,10 +82,6 @@ const WizardMonitoringPanel = function WizardMonitoringPanel({
<div className="flex h-full flex-col p-2"> <div className="flex h-full flex-col p-2">
{/* Robot Controls - Scrollable */} {/* Robot Controls - Scrollable */}
<div className="flex-1 min-h-0 bg-background rounded-lg border shadow-sm overflow-hidden flex flex-col"> <div className="flex-1 min-h-0 bg-background rounded-lg border shadow-sm overflow-hidden flex flex-col">
<div className="px-3 py-2 border-b bg-muted/30 flex items-center gap-2">
<Bot className="h-4 w-4 text-muted-foreground" />
<span className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Robot Control</span>
</div>
<ScrollArea className="flex-1"> <ScrollArea className="flex-1">
<div className="space-y-4 p-3"> <div className="space-y-4 p-3">
{/* Robot Status */} {/* Robot Status */}

View File

@@ -47,6 +47,14 @@ export function WizardObservationPane({
const [tags, setTags] = useState<string[]>([]); const [tags, setTags] = useState<string[]>([]);
const [currentTag, setCurrentTag] = useState(""); const [currentTag, setCurrentTag] = useState("");
const placeholders: Record<string, string> = {
observation: "Type your observation here...",
participant_behavior: "Describe the participant's behavior...",
system_issue: "Describe the system issue...",
success: "Describe the success...",
failure: "Describe the failure...",
};
const handleSubmit = async () => { const handleSubmit = async () => {
if (!note.trim()) return; if (!note.trim()) return;
@@ -72,10 +80,10 @@ export function WizardObservationPane({
return ( return (
<div className="flex h-full flex-col bg-background"> <div className="flex h-full flex-col bg-background">
<div className="flex-1 flex flex-col p-4 m-0"> <div className="flex-1 flex flex-col p-4 m-0 overflow-hidden">
<div className="flex flex-1 flex-col gap-2"> <div className="flex flex-1 flex-col gap-2">
<Textarea <Textarea
placeholder={readOnly ? "Session is read-only" : "Type your observation here..."} placeholder={readOnly ? "Session is read-only" : (placeholders[category] || "Type your observation here...")}
className="flex-1 resize-none font-mono text-sm" className="flex-1 resize-none font-mono text-sm"
value={note} value={note}
onChange={(e) => setNote(e.target.value)} onChange={(e) => setNote(e.target.value)}
@@ -83,9 +91,11 @@ export function WizardObservationPane({
disabled={readOnly} disabled={readOnly}
/> />
<div className="flex items-center gap-2"> <div className="flex flex-col gap-2 shrink-0">
{/* Top Line: Category & Tags */}
<div className="flex items-center gap-2 w-full">
<Select value={category} onValueChange={setCategory} disabled={readOnly}> <Select value={category} onValueChange={setCategory} disabled={readOnly}>
<SelectTrigger className="w-[140px] h-8 text-xs"> <SelectTrigger className="w-[140px] h-8 text-xs shrink-0">
<SelectValue placeholder="Category" /> <SelectValue placeholder="Category" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -97,12 +107,12 @@ export function WizardObservationPane({
</SelectContent> </SelectContent>
</Select> </Select>
<div className="flex flex-1 items-center gap-2 rounded-md border px-2 h-8"> <div className="flex flex-1 min-w-[80px] items-center gap-2 rounded-md border px-2 h-8">
<Tag className={`h-3 w-3 ${readOnly ? "text-muted-foreground/50" : "text-muted-foreground"}`} /> <Tag className={`h-3 w-3 shrink-0 ${readOnly ? "text-muted-foreground/50" : "text-muted-foreground"}`} />
<input <input
type="text" type="text"
placeholder={readOnly ? "" : "Add tags..."} placeholder={readOnly ? "" : "Add tags..."}
className="flex-1 bg-transparent text-xs outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed" className="flex-1 bg-transparent text-xs outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed w-full min-w-0"
value={currentTag} value={currentTag}
onChange={(e) => setCurrentTag(e.target.value)} onChange={(e) => setCurrentTag(e.target.value)}
onKeyDown={(e) => { onKeyDown={(e) => {
@@ -115,28 +125,32 @@ export function WizardObservationPane({
disabled={readOnly} disabled={readOnly}
/> />
</div> </div>
</div>
<Button {/* Bottom Line: Actions */}
size="sm" <div className="flex items-center justify-end gap-2 w-full">
onClick={handleSubmit}
disabled={isSubmitting || !note.trim() || readOnly}
className="h-8 shrink-0"
>
<Send className="mr-2 h-3 w-3" />
Add Note
</Button>
{onFlagIntervention && ( {onFlagIntervention && (
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
onClick={() => onFlagIntervention()} onClick={() => onFlagIntervention()}
disabled={readOnly} disabled={readOnly}
className="h-8 shrink-0 border-yellow-200 bg-yellow-50 text-yellow-700 hover:bg-yellow-100 hover:text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-300 dark:border-yellow-700/50 dark:hover:bg-yellow-900/40" className="h-8 shrink-0 flex-1 sm:flex-none border-yellow-200 bg-yellow-50 text-yellow-700 hover:bg-yellow-100 hover:text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-300 dark:border-yellow-700/50 dark:hover:bg-yellow-900/40"
> >
<AlertTriangle className="mr-2 h-3 w-3" /> <AlertTriangle className="mr-2 h-3 w-3" />
Intervention Intervention
</Button> </Button>
)} )}
<Button
size="sm"
onClick={handleSubmit}
disabled={isSubmitting || !note.trim() || readOnly}
className="h-8 shrink-0 flex-1 sm:flex-none"
>
<Send className="mr-2 h-3 w-3" />
Save Note
</Button>
</div>
</div> </div>
{tags.length > 0 && ( {tags.length > 0 && (

View File

@@ -61,15 +61,15 @@ describe("Control Flow Persistence", () => {
// console.log("DB Rows Conditions:", JSON.stringify(dbRows[0].conditions, null, 2)); // console.log("DB Rows Conditions:", JSON.stringify(dbRows[0].conditions, null, 2));
// END DEBUG // END DEBUG
expect(dbRows[0].type).toBe("conditional"); expect(dbRows[0]!.type).toBe("conditional");
expect((dbRows[0].conditions as any).options).toHaveLength(2); expect((dbRows[0]!.conditions as any).options).toHaveLength(2);
// Simulate Load // Simulate Load
const hydratedSteps = convertDatabaseToSteps(dbRows); const hydratedSteps = convertDatabaseToSteps(dbRows);
expect(hydratedSteps[0].type).toBe("conditional"); expect(hydratedSteps[0]!.type).toBe("conditional");
expect((hydratedSteps[0].trigger.conditions as any).options).toHaveLength(2); expect((hydratedSteps[0]!.trigger.conditions as any).options).toHaveLength(2);
expect((hydratedSteps[0].trigger.conditions as any).options[0].label).toBe("Yes"); expect((hydratedSteps[0]!.trigger.conditions as any).options[0].label).toBe("Yes");
}); });
it("should persist loop configuration", () => { it("should persist loop configuration", () => {
@@ -97,14 +97,14 @@ describe("Control Flow Persistence", () => {
const dbRows = convertStepsToDatabase(originalSteps); const dbRows = convertStepsToDatabase(originalSteps);
// Note: 'loop' type is mapped to 'conditional' in DB, but detailed conditions should survive // Note: 'loop' type is mapped to 'conditional' in DB, but detailed conditions should survive
expect(dbRows[0].type).toBe("conditional"); expect(dbRows[0]!.type).toBe("conditional");
expect((dbRows[0].conditions as any).loop.iterations).toBe(5); expect((dbRows[0]!.conditions as any).loop.iterations).toBe(5);
// Simulate Load // Simulate Load
const hydratedSteps = convertDatabaseToSteps(dbRows); const hydratedSteps = convertDatabaseToSteps(dbRows);
// Checking data integrity // Checking data integrity
expect((hydratedSteps[0].trigger.conditions as any).loop).toBeDefined(); expect((hydratedSteps[0]!.trigger.conditions as any).loop).toBeDefined();
expect((hydratedSteps[0].trigger.conditions as any).loop.iterations).toBe(5); expect((hydratedSteps[0]!.trigger.conditions as any).loop.iterations).toBe(5);
}); });
}); });

View File

@@ -66,6 +66,7 @@ describe("Hashing Utilities", () => {
id: "act-1", id: "act-1",
type: "log", type: "log",
name: "Log", name: "Log",
category: "observation",
parameters: { message: "A" }, parameters: { message: "A" },
source: { kind: "core", baseActionId: "log" }, source: { kind: "core", baseActionId: "log" },
execution: { transport: "internal" } execution: { transport: "internal" }
@@ -87,6 +88,7 @@ describe("Hashing Utilities", () => {
id: "act-1", id: "act-1",
type: "log", type: "log",
name: "Log", name: "Log",
category: "observation",
parameters: { message: "A" }, parameters: { message: "A" },
source: { kind: "core", baseActionId: "log" }, source: { kind: "core", baseActionId: "log" },
execution: { transport: "internal" } execution: { transport: "internal" }

View File

@@ -36,7 +36,7 @@ describe("Designer Store Integration", () => {
store.getState().upsertStep(step); store.getState().upsertStep(step);
expect(store.getState().steps).toHaveLength(1); expect(store.getState().steps).toHaveLength(1);
expect(store.getState().steps[0].id).toBe("step-1"); expect(store.getState().steps[0]!.id).toBe("step-1");
}); });
it("should update an existing step", () => { it("should update an existing step", () => {
@@ -55,7 +55,7 @@ describe("Designer Store Integration", () => {
store.getState().upsertStep(updatedStep); store.getState().upsertStep(updatedStep);
expect(store.getState().steps).toHaveLength(1); expect(store.getState().steps).toHaveLength(1);
expect(store.getState().steps[0].name).toBe("Updated Step"); expect(store.getState().steps[0]!.name).toBe("Updated Step");
}); });
it("should remove a step", () => { it("should remove a step", () => {
@@ -100,12 +100,12 @@ describe("Designer Store Integration", () => {
store.getState().reorderStep(0, 1); store.getState().reorderStep(0, 1);
const steps = store.getState().steps; const steps = store.getState().steps;
expect(steps[0].id).toBe("step-2"); expect(steps[0]!.id).toBe("step-2");
expect(steps[1].id).toBe("step-1"); expect(steps[1]!.id).toBe("step-1");
// Orders should be updated // Orders should be updated
expect(steps[0].order).toBe(0); expect(steps[0]!.order).toBe(0);
expect(steps[1].order).toBe(1); expect(steps[1]!.order).toBe(1);
}); });
it("should upsert an action into a step", () => { it("should upsert an action into a step", () => {
@@ -124,6 +124,7 @@ describe("Designer Store Integration", () => {
id: "act-1", id: "act-1",
type: "log", type: "log",
name: "Log", name: "Log",
category: "observation",
parameters: {}, parameters: {},
source: { kind: "core", baseActionId: "log" }, source: { kind: "core", baseActionId: "log" },
execution: { transport: "internal" } execution: { transport: "internal" }
@@ -132,7 +133,7 @@ describe("Designer Store Integration", () => {
store.getState().upsertAction("step-1", action); store.getState().upsertAction("step-1", action);
const storedStep = store.getState().steps[0]; const storedStep = store.getState().steps[0];
expect(storedStep.actions).toHaveLength(1); expect(storedStep!.actions).toHaveLength(1);
expect(storedStep.actions[0].id).toBe("act-1"); expect(storedStep!.actions[0]!.id).toBe("act-1");
}); });
}); });

View File

@@ -8,10 +8,13 @@ const mockActionDef: ActionDefinition = {
id: "core.log", id: "core.log",
name: "Log Info", name: "Log Info",
type: "log", type: "log",
category: "utility", description: "Logs information",
category: "observation",
icon: "lucide-info",
color: "blue",
parameters: [ parameters: [
{ id: "message", name: "Message", type: "text", required: true }, { id: "message", name: "Message", type: "text", required: true },
{ id: "level", name: "Level", type: "select", options: ["info", "warn", "error"], default: "info" } { id: "level", name: "Level", type: "select", options: ["info", "warn", "error"], value: "info" }
], ],
source: { kind: "core", baseActionId: "log" } source: { kind: "core", baseActionId: "log" }
}; };
@@ -33,7 +36,7 @@ describe("Experiment Validators", () => {
it("should fail if experiment has no steps", () => { it("should fail if experiment has no steps", () => {
const result = validateExperimentDesign([], { steps: [], actionDefinitions: [] }); const result = validateExperimentDesign([], { steps: [], actionDefinitions: [] });
expect(result.valid).toBe(false); expect(result.valid).toBe(false);
expect(result.issues[0].message).toContain("at least one step"); expect(result.issues[0]!.message).toContain("at least one step");
}); });
it("should fail if step name is empty", () => { it("should fail if step name is empty", () => {
@@ -55,7 +58,7 @@ describe("Experiment Validators", () => {
id: "act-1", id: "act-1",
type: "log", type: "log",
name: "Log", name: "Log",
order: 0, category: "observation",
parameters: {}, // Missing 'message' parameters: {}, // Missing 'message'
source: { kind: "core", baseActionId: "log" }, source: { kind: "core", baseActionId: "log" },
execution: { transport: "internal" } execution: { transport: "internal" }
@@ -75,7 +78,7 @@ describe("Experiment Validators", () => {
id: "act-1", id: "act-1",
type: "log", type: "log",
name: "Log", name: "Log",
order: 0, category: "observation",
parameters: { message: "Hello" }, parameters: { message: "Hello" },
source: { kind: "core", baseActionId: "log" }, source: { kind: "core", baseActionId: "log" },
execution: { transport: "internal" } execution: { transport: "internal" }
@@ -104,7 +107,7 @@ describe("Experiment Validators", () => {
id: "act-1", id: "act-1",
type: "math", type: "math",
name: "Math", name: "Math",
order: 0, category: "observation",
parameters: { val: 15 }, // Too high parameters: { val: 15 }, // Too high
source: { kind: "core", baseActionId: "math" }, source: { kind: "core", baseActionId: "math" },
execution: { transport: "internal" } execution: { transport: "internal" }
@@ -116,7 +119,7 @@ describe("Experiment Validators", () => {
actionDefinitions: [rangeActionDef] actionDefinitions: [rangeActionDef]
}); });
expect(issues[0].message).toContain("must be at most 10"); expect(issues[0]!.message).toContain("must be at most 10");
}); });
}); });
}); });

View File

@@ -9,6 +9,7 @@ import {
studies, studies,
studyMembers, studyMembers,
trials, trials,
trialEvents,
users, users,
userSystemRoles, userSystemRoles,
} from "~/server/db/schema"; } from "~/server/db/schema";
@@ -39,39 +40,105 @@ export const dashboardRouter = createTRPCRouter({
// Build where conditions // Build where conditions
const whereConditions = input.studyId const whereConditions = input.studyId
? eq(activityLogs.studyId, input.studyId) ? and(
: inArray(activityLogs.studyId, studyIds); eq(experiments.studyId, input.studyId),
inArray(
trialEvents.eventType,
['trial_started', 'trial_completed', 'intervention', 'error', 'annotation']
)
)
: and(
inArray(experiments.studyId, studyIds),
inArray(
trialEvents.eventType,
['trial_started', 'trial_completed', 'intervention', 'error', 'annotation']
)
);
// Get recent activity logs // Get recent interesting trial events
const activities = await ctx.db const activities = await ctx.db
.select({ .select({
id: activityLogs.id, id: trialEvents.id,
action: activityLogs.action, type: trialEvents.eventType,
description: activityLogs.description, data: trialEvents.data,
createdAt: activityLogs.createdAt, timestamp: trialEvents.timestamp,
trialId: trials.id,
experimentName: experiments.name,
participantCode: participants.participantCode,
user: { user: {
name: users.name, name: users.name,
email: users.email,
},
study: {
name: studies.name,
}, },
}) })
.from(activityLogs) .from(trialEvents)
.innerJoin(users, eq(activityLogs.userId, users.id)) .innerJoin(trials, eq(trialEvents.trialId, trials.id))
.innerJoin(studies, eq(activityLogs.studyId, studies.id)) .innerJoin(experiments, eq(trials.experimentId, experiments.id))
.innerJoin(participants, eq(trials.participantId, participants.id))
.leftJoin(users, eq(trialEvents.createdBy, users.id))
.where(whereConditions) .where(whereConditions)
.orderBy(desc(activityLogs.createdAt)) .orderBy(desc(trialEvents.timestamp))
.limit(input.limit); .limit(input.limit);
return activities.map((activity) => ({ return activities.map((activity) => {
let title = activity.type.replace(/_/g, " ");
title = title.charAt(0).toUpperCase() + title.slice(1);
let description = `${activity.participantCode}${activity.experimentName}`;
if (activity.user?.name) {
description += ` • by ${activity.user.name}`;
}
return {
id: activity.id, id: activity.id,
type: activity.action, type: activity.type,
title: activity.description, title: title,
description: `${activity.study.name} - ${activity.user.name}`, description: description,
time: activity.createdAt, time: activity.timestamp,
status: "info" as const, status: activity.type === "error" ? "error" : activity.type === "trial_completed" ? "success" : "info" as const,
})); data: activity.data,
trialId: activity.trialId,
};
});
}),
getLiveTrials: protectedProcedure
.input(
z.object({
studyId: z.string().uuid().optional(),
}),
)
.query(async ({ ctx, input }) => {
const userId = ctx.session.user.id;
// Get studies the user has access to
const accessibleStudies = await ctx.db
.select({ studyId: studyMembers.studyId })
.from(studyMembers)
.where(eq(studyMembers.userId, userId));
const studyIds = accessibleStudies.map((s) => s.studyId);
if (studyIds.length === 0) return [];
const whereConditions = input.studyId
? and(eq(experiments.studyId, input.studyId), eq(trials.status, "in_progress"))
: and(inArray(experiments.studyId, studyIds), eq(trials.status, "in_progress"));
const live = await ctx.db
.select({
id: trials.id,
startedAt: trials.startedAt,
experimentName: experiments.name,
participantCode: participants.participantCode,
studyName: studies.name,
})
.from(trials)
.innerJoin(experiments, eq(trials.experimentId, experiments.id))
.innerJoin(participants, eq(trials.participantId, participants.id))
.innerJoin(studies, eq(experiments.studyId, studies.id))
.where(whereConditions)
.orderBy(desc(trials.startedAt));
return live;
}), }),
getStudyProgress: protectedProcedure getStudyProgress: protectedProcedure
@@ -262,6 +329,19 @@ export const dashboardRouter = createTRPCRouter({
), ),
); );
// Get total interventions
const [interventionsCount] = await ctx.db
.select({ count: count() })
.from(trialEvents)
.innerJoin(trials, eq(trialEvents.trialId, trials.id))
.innerJoin(experiments, eq(trials.experimentId, experiments.id))
.where(
and(
inArray(experiments.studyId, studyIds),
eq(trialEvents.eventType, "intervention"),
),
);
return { return {
totalStudies: studyCount?.count ?? 0, totalStudies: studyCount?.count ?? 0,
totalExperiments: experimentCount?.count ?? 0, totalExperiments: experimentCount?.count ?? 0,
@@ -270,6 +350,7 @@ export const dashboardRouter = createTRPCRouter({
activeTrials: activeTrialsCount?.count ?? 0, activeTrials: activeTrialsCount?.count ?? 0,
scheduledTrials: scheduledTrialsCount?.count ?? 0, scheduledTrials: scheduledTrialsCount?.count ?? 0,
completedToday: completedTodayCount?.count ?? 0, completedToday: completedTodayCount?.count ?? 0,
totalInterventions: interventionsCount?.count ?? 0,
}; };
}), }),

View File

@@ -211,12 +211,6 @@ export class TrialExecutionEngine {
}) })
.where(eq(trials.id, trialId)); .where(eq(trials.id, trialId));
// Log trial start event
await this.logTrialEvent(trialId, "trial_started", {
wizardId: context.wizardId,
startTime: context.startTime.toISOString(),
});
return { return {
success: true, success: true,
data: { data: {
@@ -983,13 +977,6 @@ export class TrialExecutionEngine {
}) })
.where(eq(trials.id, trialId)); .where(eq(trials.id, trialId));
// Log completion
await this.logTrialEvent(trialId, "trial_completed", {
endTime: endTime.toISOString(),
duration,
totalSteps: this.stepDefinitions.get(trialId)?.length || 0,
});
// Clean up // Clean up
this.activeTrials.delete(trialId); this.activeTrials.delete(trialId);
this.stepDefinitions.delete(trialId); this.stepDefinitions.delete(trialId);