From f65945977429e52e0a063f0e72e371677bdf8f83 Mon Sep 17 00:00:00 2001
From: neo773
Date: Thu, 13 Mar 2025 03:47:28 +0530
Subject: [PATCH 01/36] feat: Loom importer extension
---
apps/loom-importer-extension/.gitignore | 5 +
apps/loom-importer-extension/README.md | 19 +
apps/loom-importer-extension/bun.lock | 255 ++
apps/loom-importer-extension/index.html | 14 +
apps/loom-importer-extension/package.json | 32 +
apps/loom-importer-extension/popup.html | 12 +
.../postcss.config.cjs | 6 +
.../public/assets/images/icons/icon128.png | Bin 0 -> 2973 bytes
.../public/assets/images/icons/icon16.png | Bin 0 -> 240 bytes
.../public/assets/images/icons/icon32.png | Bin 0 -> 647 bytes
.../public/assets/images/icons/icon48.png | Bin 0 -> 1137 bytes
.../public/assets/js/initializeUI.js | 8 +
.../public/manifest.json | 45 +
apps/loom-importer-extension/src/api/cap.ts | 130 +
.../loom-importer-extension/src/background.ts | 168 ++
.../src/components/EmailSelector.tsx | 42 +
.../src/components/ImportChecklist.tsx | 150 ++
.../src/components/ImportProgress.tsx | 77 +
.../src/components/Logo.tsx | 52 +
.../src/components/LoomImporter.tsx | 109 +
.../src/components/LoomLogo.tsx | 19 +
.../src/components/UserProfile.tsx | 27 +
.../src/components/WorkspaceSelector.tsx | 50 +
.../src/components/avatar.tsx | 28 +
.../src/content_scripts/App.css | 3 +
.../src/content_scripts/App.tsx | 32 +
.../src/content_scripts/main.tsx | 25 +
.../src/context/ImportContext.tsx | 80 +
.../src/hooks/useAuth.ts | 134 +
.../src/hooks/useWorkspaces.ts | 62 +
.../src/popup/popup.css | 26 +
.../src/popup/popup.tsx | 153 ++
.../src/services/loomScraper.ts | 279 ++
.../src/store/importStore.ts | 368 +++
.../src/types/index.ts | 41 +
.../loom-importer-extension/src/types/loom.ts | 25 +
apps/loom-importer-extension/src/utils/dom.ts | 30 +
.../src/utils/importUtils.ts | 80 +
.../loom-importer-extension/src/utils/urls.ts | 39 +
.../loom-importer-extension/src/vite-env.d.ts | 0
.../tailwind.config.js | 23 +
apps/loom-importer-extension/tsconfig.json | 22 +
apps/loom-importer-extension/vite.config.ts | 40 +
apps/web/app/api/import/loom/route.ts | 304 +++
.../api/settings/workspace/details/route.ts | 23 +
.../_components/AdminNavbar/AdminNavItems.tsx | 6 +-
packages/database/schema.ts | 1 +
pnpm-lock.yaml | 2358 +++++++++++++++--
scripts/setup.js | 5 +
49 files changed, 5139 insertions(+), 268 deletions(-)
create mode 100644 apps/loom-importer-extension/.gitignore
create mode 100644 apps/loom-importer-extension/README.md
create mode 100644 apps/loom-importer-extension/bun.lock
create mode 100644 apps/loom-importer-extension/index.html
create mode 100644 apps/loom-importer-extension/package.json
create mode 100644 apps/loom-importer-extension/popup.html
create mode 100644 apps/loom-importer-extension/postcss.config.cjs
create mode 100644 apps/loom-importer-extension/public/assets/images/icons/icon128.png
create mode 100644 apps/loom-importer-extension/public/assets/images/icons/icon16.png
create mode 100644 apps/loom-importer-extension/public/assets/images/icons/icon32.png
create mode 100644 apps/loom-importer-extension/public/assets/images/icons/icon48.png
create mode 100644 apps/loom-importer-extension/public/assets/js/initializeUI.js
create mode 100644 apps/loom-importer-extension/public/manifest.json
create mode 100644 apps/loom-importer-extension/src/api/cap.ts
create mode 100644 apps/loom-importer-extension/src/background.ts
create mode 100644 apps/loom-importer-extension/src/components/EmailSelector.tsx
create mode 100644 apps/loom-importer-extension/src/components/ImportChecklist.tsx
create mode 100644 apps/loom-importer-extension/src/components/ImportProgress.tsx
create mode 100644 apps/loom-importer-extension/src/components/Logo.tsx
create mode 100644 apps/loom-importer-extension/src/components/LoomImporter.tsx
create mode 100644 apps/loom-importer-extension/src/components/LoomLogo.tsx
create mode 100644 apps/loom-importer-extension/src/components/UserProfile.tsx
create mode 100644 apps/loom-importer-extension/src/components/WorkspaceSelector.tsx
create mode 100644 apps/loom-importer-extension/src/components/avatar.tsx
create mode 100644 apps/loom-importer-extension/src/content_scripts/App.css
create mode 100644 apps/loom-importer-extension/src/content_scripts/App.tsx
create mode 100644 apps/loom-importer-extension/src/content_scripts/main.tsx
create mode 100644 apps/loom-importer-extension/src/context/ImportContext.tsx
create mode 100644 apps/loom-importer-extension/src/hooks/useAuth.ts
create mode 100644 apps/loom-importer-extension/src/hooks/useWorkspaces.ts
create mode 100644 apps/loom-importer-extension/src/popup/popup.css
create mode 100644 apps/loom-importer-extension/src/popup/popup.tsx
create mode 100644 apps/loom-importer-extension/src/services/loomScraper.ts
create mode 100644 apps/loom-importer-extension/src/store/importStore.ts
create mode 100644 apps/loom-importer-extension/src/types/index.ts
create mode 100644 apps/loom-importer-extension/src/types/loom.ts
create mode 100644 apps/loom-importer-extension/src/utils/dom.ts
create mode 100644 apps/loom-importer-extension/src/utils/importUtils.ts
create mode 100644 apps/loom-importer-extension/src/utils/urls.ts
create mode 100644 apps/loom-importer-extension/src/vite-env.d.ts
create mode 100644 apps/loom-importer-extension/tailwind.config.js
create mode 100644 apps/loom-importer-extension/tsconfig.json
create mode 100644 apps/loom-importer-extension/vite.config.ts
create mode 100644 apps/web/app/api/import/loom/route.ts
diff --git a/apps/loom-importer-extension/.gitignore b/apps/loom-importer-extension/.gitignore
new file mode 100644
index 000000000..d451ff16c
--- /dev/null
+++ b/apps/loom-importer-extension/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+.DS_Store
+dist
+dist-ssr
+*.local
diff --git a/apps/loom-importer-extension/README.md b/apps/loom-importer-extension/README.md
new file mode 100644
index 000000000..27024f9f8
--- /dev/null
+++ b/apps/loom-importer-extension/README.md
@@ -0,0 +1,19 @@
+## Cap Loom Importer
+
+This is a Chrome extension that allows you to import your Loom videos into Cap.
+
+## Structure
+
+```
+├── src
+│ ├── background.ts # Background script for handling auth
+│ ├── content_scripts
+│ │ └── main.tsx # Import UI injected on Loom's website
+│ ├── popup
+│ └── popup.tsx # Popup for the extension (shown when the extension is clicked)
+└── vite.config.ts
+```
+
+## Development
+
+Go to chrome://extensions/ and click "Load unpacked" and select the `dist` folder.
diff --git a/apps/loom-importer-extension/bun.lock b/apps/loom-importer-extension/bun.lock
new file mode 100644
index 000000000..dd6402710
--- /dev/null
+++ b/apps/loom-importer-extension/bun.lock
@@ -0,0 +1,255 @@
+{
+ "lockfileVersion": 1,
+ "workspaces": {
+ "": {
+ "name": "vite-project",
+ "dependencies": {
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-frame-component": "^5.2.1",
+ "vite-tsconfig-paths": "^3.3.17",
+ },
+ "devDependencies": {
+ "@types/chrome": "^0.0.176",
+ "@types/react": "^19.0.10",
+ "@types/react-dom": "^19.0.4",
+ "@vitejs/plugin-react": "^1.0.7",
+ "typescript": "^4.4.4",
+ "vite": "^2.7.2",
+ },
+ },
+ },
+ "packages": {
+ "@babel/code-frame": ["@babel/code-frame@7.16.7", "", { "dependencies": { "@babel/highlight": "^7.16.7" } }, "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg=="],
+
+ "@babel/compat-data": ["@babel/compat-data@7.16.8", "", {}, "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q=="],
+
+ "@babel/core": ["@babel/core@7.16.12", "", { "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", "@babel/helper-compilation-targets": "^7.16.7", "@babel/helper-module-transforms": "^7.16.7", "@babel/helpers": "^7.16.7", "@babel/parser": "^7.16.12", "@babel/template": "^7.16.7", "@babel/traverse": "^7.16.10", "@babel/types": "^7.16.8", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.1.2", "semver": "^6.3.0", "source-map": "^0.5.0" } }, "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg=="],
+
+ "@babel/generator": ["@babel/generator@7.16.8", "", { "dependencies": { "@babel/types": "^7.16.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw=="],
+
+ "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.16.7", "", { "dependencies": { "@babel/types": "^7.16.7" } }, "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw=="],
+
+ "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.16.7", "", { "dependencies": { "@babel/compat-data": "^7.16.4", "@babel/helper-validator-option": "^7.16.7", "browserslist": "^4.17.5", "semver": "^6.3.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA=="],
+
+ "@babel/helper-environment-visitor": ["@babel/helper-environment-visitor@7.16.7", "", { "dependencies": { "@babel/types": "^7.16.7" } }, "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag=="],
+
+ "@babel/helper-function-name": ["@babel/helper-function-name@7.16.7", "", { "dependencies": { "@babel/helper-get-function-arity": "^7.16.7", "@babel/template": "^7.16.7", "@babel/types": "^7.16.7" } }, "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA=="],
+
+ "@babel/helper-get-function-arity": ["@babel/helper-get-function-arity@7.16.7", "", { "dependencies": { "@babel/types": "^7.16.7" } }, "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw=="],
+
+ "@babel/helper-hoist-variables": ["@babel/helper-hoist-variables@7.16.7", "", { "dependencies": { "@babel/types": "^7.16.7" } }, "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg=="],
+
+ "@babel/helper-module-imports": ["@babel/helper-module-imports@7.16.7", "", { "dependencies": { "@babel/types": "^7.16.7" } }, "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg=="],
+
+ "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.16.7", "", { "dependencies": { "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", "@babel/helper-simple-access": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", "@babel/helper-validator-identifier": "^7.16.7", "@babel/template": "^7.16.7", "@babel/traverse": "^7.16.7", "@babel/types": "^7.16.7" } }, "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng=="],
+
+ "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.16.7", "", {}, "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA=="],
+
+ "@babel/helper-simple-access": ["@babel/helper-simple-access@7.16.7", "", { "dependencies": { "@babel/types": "^7.16.7" } }, "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g=="],
+
+ "@babel/helper-split-export-declaration": ["@babel/helper-split-export-declaration@7.16.7", "", { "dependencies": { "@babel/types": "^7.16.7" } }, "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw=="],
+
+ "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.16.7", "", {}, "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw=="],
+
+ "@babel/helper-validator-option": ["@babel/helper-validator-option@7.16.7", "", {}, "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ=="],
+
+ "@babel/helpers": ["@babel/helpers@7.16.7", "", { "dependencies": { "@babel/template": "^7.16.7", "@babel/traverse": "^7.16.7", "@babel/types": "^7.16.7" } }, "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw=="],
+
+ "@babel/highlight": ["@babel/highlight@7.16.10", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw=="],
+
+ "@babel/parser": ["@babel/parser@7.16.12", "", { "bin": { "parser": "bin/babel-parser.js" } }, "sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A=="],
+
+ "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.16.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q=="],
+
+ "@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.16.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-jsx": "^7.16.7", "@babel/types": "^7.16.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8D16ye66fxiE8m890w0BpPpngG9o9OVBBy0gH2E+2AR7qMR2ZpTYJEqLxAsoroenMId0p/wMW+Blc0meDgu0Ag=="],
+
+ "@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.16.7", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.16.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A=="],
+
+ "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.16.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oe5VuWs7J9ilH3BCCApGoYjHoSO48vkjX2CbA5bFVhIuO2HKxA3vyF7rleA4o6/4rTDbk6r8hBW7Ul8E+UZrpA=="],
+
+ "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.16.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-rONFiQz9vgbsnaMtQlZCjIRwhJvlrPET8TabIUK2hzlXw9B9s2Ieaxte1SCOOXMbWRHodbKixNf3BLcWVOQ8Bw=="],
+
+ "@babel/template": ["@babel/template@7.16.7", "", { "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/parser": "^7.16.7", "@babel/types": "^7.16.7" } }, "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w=="],
+
+ "@babel/traverse": ["@babel/traverse@7.16.10", "", { "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-function-name": "^7.16.7", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", "@babel/parser": "^7.16.10", "@babel/types": "^7.16.8", "debug": "^4.1.0", "globals": "^11.1.0" } }, "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw=="],
+
+ "@babel/types": ["@babel/types@7.16.8", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" } }, "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg=="],
+
+ "@cush/relative": ["@cush/relative@1.0.0", "", {}, "sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA=="],
+
+ "@rollup/pluginutils": ["@rollup/pluginutils@4.1.2", "", { "dependencies": { "estree-walker": "^2.0.1", "picomatch": "^2.2.2" } }, "sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ=="],
+
+ "@types/chrome": ["@types/chrome@0.0.176", "", { "dependencies": { "@types/filesystem": "*", "@types/har-format": "*" } }, "sha512-LOveFOMIUhMJjvRzZv5whGBpncP/gdJ4hcxeAqg94wGi6CyKaCmLgFSofgItf85GuLTl/0BQ6J/Y1e8BqZWfEg=="],
+
+ "@types/filesystem": ["@types/filesystem@0.0.32", "", { "dependencies": { "@types/filewriter": "*" } }, "sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ=="],
+
+ "@types/filewriter": ["@types/filewriter@0.0.29", "", {}, "sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ=="],
+
+ "@types/har-format": ["@types/har-format@1.2.8", "", {}, "sha512-OP6L9VuZNdskgNN3zFQQ54ceYD8OLq5IbqO4VK91ORLfOm7WdT/CiT/pHEBSQEqCInJ2y3O6iCm/zGtPElpgJQ=="],
+
+ "@types/json5": ["@types/json5@0.0.29", "", {}, ""],
+
+ "@types/react": ["@types/react@19.0.10", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g=="],
+
+ "@types/react-dom": ["@types/react-dom@19.0.4", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg=="],
+
+ "@vitejs/plugin-react": ["@vitejs/plugin-react@1.1.4", "", { "dependencies": { "@babel/core": "^7.16.5", "@babel/plugin-transform-react-jsx": "^7.16.5", "@babel/plugin-transform-react-jsx-development": "^7.16.5", "@babel/plugin-transform-react-jsx-self": "^7.16.5", "@babel/plugin-transform-react-jsx-source": "^7.16.5", "@rollup/pluginutils": "^4.1.2", "react-refresh": "^0.11.0", "resolve": "^1.20.0" } }, "sha512-cMUBDonNY8PPeHWjIrYKbRn6bLSunh/Ixo2XLLBd3DM0uYBZft+c+04zkGhhN1lAwvoRKJ2FdtvhGhPgViHc6w=="],
+
+ "ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
+
+ "browserslist": ["browserslist@4.19.1", "", { "dependencies": { "caniuse-lite": "^1.0.30001286", "electron-to-chromium": "^1.4.17", "escalade": "^3.1.1", "node-releases": "^2.0.1", "picocolors": "^1.0.0" }, "bin": "cli.js" }, "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A=="],
+
+ "caniuse-lite": ["caniuse-lite@1.0.30001301", "", {}, "sha512-csfD/GpHMqgEL3V3uIgosvh+SVIQvCh43SNu9HRbP1lnxkKm1kjDG4f32PP571JplkLjfS+mg2p1gxR7MYrrIA=="],
+
+ "chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
+
+ "color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
+
+ "color-name": ["color-name@1.1.3", "", {}, ""],
+
+ "convert-source-map": ["convert-source-map@1.8.0", "", { "dependencies": { "safe-buffer": "~5.1.1" } }, "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA=="],
+
+ "csstype": ["csstype@3.0.10", "", {}, "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA=="],
+
+ "debug": ["debug@4.3.3", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q=="],
+
+ "electron-to-chromium": ["electron-to-chromium@1.4.51", "", {}, "sha512-JNEmcYl3mk1tGQmy0EvL5eik/CKSBuzAyGP0QFdG6LIgxQe3II0BL1m2zKc2MZMf3uGqHWE1TFddJML0RpjSHQ=="],
+
+ "esbuild": ["esbuild@0.13.15", "", { "optionalDependencies": { "esbuild-android-arm64": "0.13.15", "esbuild-darwin-64": "0.13.15", "esbuild-darwin-arm64": "0.13.15", "esbuild-freebsd-64": "0.13.15", "esbuild-freebsd-arm64": "0.13.15", "esbuild-linux-32": "0.13.15", "esbuild-linux-64": "0.13.15", "esbuild-linux-arm": "0.13.15", "esbuild-linux-arm64": "0.13.15", "esbuild-linux-mips64le": "0.13.15", "esbuild-linux-ppc64le": "0.13.15", "esbuild-netbsd-64": "0.13.15", "esbuild-openbsd-64": "0.13.15", "esbuild-sunos-64": "0.13.15", "esbuild-windows-32": "0.13.15", "esbuild-windows-64": "0.13.15", "esbuild-windows-arm64": "0.13.15" }, "bin": "bin/esbuild" }, "sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw=="],
+
+ "esbuild-android-arm64": ["esbuild-android-arm64@0.13.15", "", { "os": "android", "cpu": "arm64" }, "sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg=="],
+
+ "esbuild-darwin-64": ["esbuild-darwin-64@0.13.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ=="],
+
+ "esbuild-darwin-arm64": ["esbuild-darwin-arm64@0.13.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ=="],
+
+ "esbuild-freebsd-64": ["esbuild-freebsd-64@0.13.15", "", { "os": "freebsd", "cpu": "x64" }, "sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA=="],
+
+ "esbuild-freebsd-arm64": ["esbuild-freebsd-arm64@0.13.15", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ=="],
+
+ "esbuild-linux-32": ["esbuild-linux-32@0.13.15", "", { "os": "linux", "cpu": "ia32" }, "sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g=="],
+
+ "esbuild-linux-64": ["esbuild-linux-64@0.13.15", "", { "os": "linux", "cpu": "x64" }, "sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA=="],
+
+ "esbuild-linux-arm": ["esbuild-linux-arm@0.13.15", "", { "os": "linux", "cpu": "arm" }, "sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA=="],
+
+ "esbuild-linux-arm64": ["esbuild-linux-arm64@0.13.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA=="],
+
+ "esbuild-linux-mips64le": ["esbuild-linux-mips64le@0.13.15", "", { "os": "linux", "cpu": "none" }, "sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg=="],
+
+ "esbuild-linux-ppc64le": ["esbuild-linux-ppc64le@0.13.15", "", { "os": "linux", "cpu": "ppc64" }, "sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ=="],
+
+ "esbuild-netbsd-64": ["esbuild-netbsd-64@0.13.15", "", { "os": "none", "cpu": "x64" }, "sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w=="],
+
+ "esbuild-openbsd-64": ["esbuild-openbsd-64@0.13.15", "", { "os": "openbsd", "cpu": "x64" }, "sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g=="],
+
+ "esbuild-sunos-64": ["esbuild-sunos-64@0.13.15", "", { "os": "sunos", "cpu": "x64" }, "sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw=="],
+
+ "esbuild-windows-32": ["esbuild-windows-32@0.13.15", "", { "os": "win32", "cpu": "ia32" }, "sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw=="],
+
+ "esbuild-windows-64": ["esbuild-windows-64@0.13.15", "", { "os": "win32", "cpu": "x64" }, "sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ=="],
+
+ "esbuild-windows-arm64": ["esbuild-windows-arm64@0.13.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA=="],
+
+ "escalade": ["escalade@3.1.1", "", {}, "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="],
+
+ "escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, ""],
+
+ "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
+
+ "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
+
+ "function-bind": ["function-bind@1.1.1", "", {}, "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="],
+
+ "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
+
+ "glob-regex": ["glob-regex@0.3.2", "", {}, "sha512-m5blUd3/OqDTWwzBBtWBPrGlAzatRywHameHeekAZyZrskYouOGdNB8T/q6JucucvJXtOuyHIn0/Yia7iDasDw=="],
+
+ "globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
+
+ "globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
+
+ "has": ["has@1.0.3", "", { "dependencies": { "function-bind": "^1.1.1" } }, "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw=="],
+
+ "has-flag": ["has-flag@3.0.0", "", {}, ""],
+
+ "is-core-module": ["is-core-module@2.8.1", "", { "dependencies": { "has": "^1.0.3" } }, "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA=="],
+
+ "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
+
+ "jsesc": ["jsesc@2.5.2", "", { "bin": "bin/jsesc" }, "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="],
+
+ "json5": ["json5@2.2.0", "", { "dependencies": { "minimist": "^1.2.5" }, "bin": "lib/cli.js" }, "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA=="],
+
+ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": "cli.js" }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
+
+ "minimist": ["minimist@1.2.5", "", {}, "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="],
+
+ "ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
+
+ "nanoid": ["nanoid@3.2.0", "", { "bin": "bin/nanoid.cjs" }, "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA=="],
+
+ "node-releases": ["node-releases@2.0.1", "", {}, "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA=="],
+
+ "object-assign": ["object-assign@4.1.1", "", {}, ""],
+
+ "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
+
+ "picocolors": ["picocolors@1.0.0", "", {}, "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="],
+
+ "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+
+ "postcss": ["postcss@8.4.5", "", { "dependencies": { "nanoid": "^3.1.30", "picocolors": "^1.0.0", "source-map-js": "^1.0.1" } }, "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg=="],
+
+ "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
+
+ "react": ["react@19.0.0", "", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="],
+
+ "react-dom": ["react-dom@19.0.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="],
+
+ "react-frame-component": ["react-frame-component@5.2.1", "", { "peerDependencies": { "prop-types": "^15.5.9", "react": ">= 16.3", "react-dom": ">= 16.3" } }, "sha512-nrSh1OZuHlX69eWqJPiUkPT9S6/wxc4PpJV+vOQ4pHQQ8XmIsIT+utWT+nX32ZfANHZuKONA7JsWMUGT36CqaQ=="],
+
+ "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
+
+ "react-refresh": ["react-refresh@0.11.0", "", {}, "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="],
+
+ "recrawl-sync": ["recrawl-sync@2.2.1", "", { "dependencies": { "@cush/relative": "^1.0.0", "glob-regex": "^0.3.0", "slash": "^3.0.0", "tslib": "^1.9.3" } }, "sha512-A2yLDgeXNaduJJMlqyUdIN7fewopnNm/mVeeGytS1d2HLXKpS5EthQ0j8tWeX+as9UXiiwQRwfoslKC+/gjqxg=="],
+
+ "resolve": ["resolve@1.22.0", "", { "dependencies": { "is-core-module": "^2.8.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw=="],
+
+ "rollup": ["rollup@2.66.0", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, "sha512-L6mKOkdyP8HK5kKJXaiWG7KZDumPJjuo1P+cfyHOJPNNTK3Moe7zCH5+fy7v8pVmHXtlxorzaBjvkBMB23s98g=="],
+
+ "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
+
+ "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="],
+
+ "semver": ["semver@6.3.0", "", { "bin": "bin/semver.js" }, "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="],
+
+ "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
+
+ "source-map": ["source-map@0.5.7", "", {}, ""],
+
+ "source-map-js": ["source-map-js@1.0.2", "", {}, "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="],
+
+ "strip-bom": ["strip-bom@3.0.0", "", {}, ""],
+
+ "supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
+
+ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
+
+ "to-fast-properties": ["to-fast-properties@2.0.0", "", {}, ""],
+
+ "tsconfig-paths": ["tsconfig-paths@3.12.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.1", "minimist": "^1.2.0", "strip-bom": "^3.0.0" } }, "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg=="],
+
+ "tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
+
+ "typescript": ["typescript@4.5.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA=="],
+
+ "vite": ["vite@2.7.13", "", { "dependencies": { "esbuild": "^0.13.12", "postcss": "^8.4.5", "resolve": "^1.20.0", "rollup": "^2.59.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "less": "*", "sass": "*", "stylus": "*" }, "optionalPeers": ["less", "sass", "stylus"], "bin": "bin/vite.js" }, "sha512-Mq8et7f3aK0SgSxjDNfOAimZGW9XryfHRa/uV0jseQSilg+KhYDSoNb9h1rknOy6SuMkvNDLKCYAYYUMCE+IgQ=="],
+
+ "vite-tsconfig-paths": ["vite-tsconfig-paths@3.3.17", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "recrawl-sync": "^2.0.3", "tsconfig-paths": "^3.9.0" }, "peerDependencies": { "vite": ">2.0.0-0" } }, "sha512-wx+rfC53moVLxMBj2EApJZgY6HtvWUFVZ4dBxNGYBxSSqU6UaHdKlcOxrfGDxyTGtYEr9beWCryHn18C4EtZkg=="],
+
+ "tsconfig-paths/json5": ["json5@1.0.1", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": "lib/cli.js" }, "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow=="],
+ }
+}
diff --git a/apps/loom-importer-extension/index.html b/apps/loom-importer-extension/index.html
new file mode 100644
index 000000000..051a46479
--- /dev/null
+++ b/apps/loom-importer-extension/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+ Cap Loom Importer
+
+
+
+
+
+
diff --git a/apps/loom-importer-extension/package.json b/apps/loom-importer-extension/package.json
new file mode 100644
index 000000000..0ef39eb09
--- /dev/null
+++ b/apps/loom-importer-extension/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "loom-importer-extension",
+ "version": "0.0.0",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "watch": "vite build --watch",
+ "type-check": "tsc --noEmit",
+ "dev:extension": "echo '⚡ Building in watch mode. Load the extension from the dist folder in Chrome extensions page (chrome://extensions/)' && pnpm watch",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@cap/ui": "workspace:^",
+ "@cap/ui-solid": "workspace:^",
+ "js-confetti": "^0.12.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-frame-component": "^5.2.1",
+ "vite-tsconfig-paths": "^3.3.17",
+ "zustand": "^5.0.3"
+ },
+ "devDependencies": {
+ "@types/chrome": "^0.0.176",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
+ "@vitejs/plugin-react": "^1.0.7",
+ "hot-reload-extension-vite": "^1.0.13",
+ "tailwindcss": "^3.4.16",
+ "typescript": "^4.4.4",
+ "vite": "^2.7.2"
+ }
+}
diff --git a/apps/loom-importer-extension/popup.html b/apps/loom-importer-extension/popup.html
new file mode 100644
index 000000000..627461c26
--- /dev/null
+++ b/apps/loom-importer-extension/popup.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Cap Loom Importer Popup
+
+
+
+
+
+
diff --git a/apps/loom-importer-extension/postcss.config.cjs b/apps/loom-importer-extension/postcss.config.cjs
new file mode 100644
index 000000000..12a703d90
--- /dev/null
+++ b/apps/loom-importer-extension/postcss.config.cjs
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
diff --git a/apps/loom-importer-extension/public/assets/images/icons/icon128.png b/apps/loom-importer-extension/public/assets/images/icons/icon128.png
new file mode 100644
index 0000000000000000000000000000000000000000..5f484dda3db28c6f63d73d514b0c327c93e7673a
GIT binary patch
literal 2973
zcmV;O3u5$%P)Px=S4l)cRCr$PT|tN(#}$38Q6kB^BY7gtt|u5<4#6Z~DRx8x309Cv1U^_05?>q}
zEL}_>M}ZGH*s>2f81@(lxmX4}xi}#p9Slmah(ZDhiXF+2U_vYzlkx6I9!s+;?Ixb>
zP`mC;@9cE<|5d-Lt9zDG1-rGcDTQIgwqz+eC
z(Yrnv8A6{K*Z}Bu`}@&|-b-dMyKgt5h%LsZjwVL-7BCFs`#PQ06^;T}mP=FpUF|)b
zaY9?m8u+kxV`BtPXdnY1G5*x99cWJPw7i@kUPl9qw^p#Yw7k}p3W7oae6Y}euz~0;
zB{X=wj#=FIN!VbB_@TRJT5spgAQu4L`Ti+H(euIh?u}?V;zme_;}<%!ty5X@G64`g
z-_!)<_opyHCIFI{A85aPLz>JW0YO$yFctv8_$Dg%uWud*wg4a)-#-IN%qR;4n*fj$
zd8Vg!V)~9965|FLo@ijOxQwOSE5JyxXfpslt?Ab#?*Ha5z|YQ^S>T69f$x6JOolJL
z$FaZ$0FtM8RW<(73h@0oH>&>CNnom?1i1!+D1M~VZk<=!NT~iC&ceaPBfNcgBs;
zvqm}j7;yApxpUx*KFR`{08o{ud5`{tvt)N|-}te+v2;g@;~4YTZf=x{*8ot3%>Uda
zVD7B$vfcLcofXn7;5`6dy#T1cDqg+R{w!_U~(qH-)25Il?*%#3v%3;{S`U1AlziRLy?(S9c65G?aZMVZa*zxbphXw}Aa^
zU~dy>?pim);5MMP**i0Y1B3WJ+uy
zgJs~Ce_(eT+rf26=zTPRdhcK@2XGAl3FEu+cB*+LSivO-N&|qY@qIJ=C_qes;0gq#
z0KmlfWl}wr1(J5Ap}_?RoCCnb_`Z967C=ncz%~e+0zlXJPdp4XEB+3O2^-i1finQ;
z-s)A=>m}L1<}IQV0O%HZY#3iD^NX{zX#UT6+4@8>s!m9-RV;D>03GU=GXA%|4D1ss
zZCo1wzyCW8JVmW^Kwu*jSO);t_&c@(Pai2#U;gj+?gI{e!AwR`Z&eb$dabBc{_n4x
z2Ub4e$&dztRRE}+J8B0H=Q8np4!v{cl-E{)z~uP=mkGXkKk(e+QnLrF0)XrNy2fws
z1`dBoPANK)-uN4EYelCgzy*OM3s?hyE8~Cf>v~GJ)$7-P){|To2&@2ryNyf7^FOm4
zIQCUNW!vth%LW_}NNq2(0sxNp>lAq;GkaTLxN!^k-8($#++vYLC=dq#ciT4C`+cMQ
zA`^2%0*3{jIt-{klS>=`9Ph8Y@e{+?1c50FhysA){aos=n&*#tB?kom`2;w<_QqcE
zW%|y6C;)7E|F<6i_S{jd44+$YtB9OXV8Q~W0DwdN>Ue@jzv8oL>2o{tSKz;l${!U3
zUI4(B_g9_zO+o<`0BQ_f5Qs4V6#yLXubc68l0qs7N(BHX`J)1$PSiij0vG-P^ls8Z
z2NeJ(j{!#y&}61ti7)`w`#G6^UA>>n0-QSn9Tsp30IK(^N4e^eZk;dAW)mt4lmYhAqq7N7z^XV;K115oLoYJ3-)IecLj&ZZJ72)HcZ9RMc9`)#p60Dv#zP(Cmk08nh$#bw|Zgwx0Z0E%_#T?@*sU?l+%LI4eG6aZ8*1cW3|4gi%*0>f>f
z8~`eL8>p5~c^DLuz+#|OvJgZCKqv)IH~`f0CzS;#382zXtr;Kypn?n#HiQ%#jnYDr
z0i13KQH!H&3ZZ_rBWwxFh70v;C7ddt8qMfo5ETH_L~s(rfTZ^;{RIF}IM<^#0RSV$
z2rq_=N2iTD0Fe+7UJcWy6KU@+<<&47dB7wTg*Zpd+VXx82H*gI^SPJ07lo+a&pA7g
zn*fRd;M`?k?hK6zDbEFzBp=TIrLBnHigS`E)%$f%3p2hAOqNfn2XJl&>wHT{^?qCB
z-!x>M17OlYV8i%2_x}k9!~np_1*l;_$@KY9XfFA4sNaS6i{${^Fu-MjI+TdHheuOm
zzlryY1Auc{80U_l4*39w`Z<}uZt`a(1aQItmj$W}0*>)H)Nktj;tZe!zItK(k9u+P
zfhvN)rtwX^-wFV@EWkNCFacE+4v(gu{E>DP4)sfTzcm23_+*UFCy8m>NA3*Z}@a^ONsFT@CdA>CDC-ZUw0M4!+?cOsq(J-9lXm9j3;?>JK=sop7THmkzEp0JCq0sM+)vAP3IG!jNah9l
zX4LDcF~ipvX$Mu~p=u%)=W1NQZ=?#jDfH
z_P2q(O`y4Jz4hQWaD4z=>Cp~lE*gXKyNgV}5)6=@49fQU^o-U45TA(DKUE&k-rGcD+ct0X#@$}#&BVt{L~0wC
z)EbAYtLR-Hpfhu4v;lxJ6T!TNao#zQQjnCKrw#+^&$la~|4INLQC^jRAhB)RCTO+*
zAPEDr`*tH*Ck3xBUvmVXVtkeX)AX;}Y*@SOdVmh%d|-u>$AF^;bY@>ppS1UH20*WW
z1ab7HPj>?x71=5%jhjQ61ssk}#)4ZD-wMVDE0
zF{XM;Na)!nC44YUOPCtZP?{rXUJ`=zjQO%@$|3NwONTJQJFssKM5db9|}fO@Wkf9&=_5E|&m>K#^VrW(_}&
l4J(v1HG}8O)Hr*N;Y851@+6hVDnM5=c)I$ztaD0e0syqdQA+>-
literal 0
HcmV?d00001
diff --git a/apps/loom-importer-extension/public/assets/images/icons/icon32.png b/apps/loom-importer-extension/public/assets/images/icons/icon32.png
new file mode 100644
index 0000000000000000000000000000000000000000..7fd48851c644a1ff2405e08505384257f6130a77
GIT binary patch
literal 647
zcmV;20(kw2P)Px%K}keGR9HvNm(NQZK@`Wo>kqU|FuR8hNe>D_p{GC*q#%M)4=sA~R_al_c@^;w
zD8;Kc@o4epsiFr<1rbW26mlvQ3hki@yN6wMV-nP?Ov1P?yR+F1i5V9HVdu@f&wKOs
zJM$Ef$FeN7a;;d&r$bQs6gt%Lf4AnsxvHWc1KD&sS>y}ednp+9*NmQ;>DW_@1&ZeR
zNj9y{GSfOhFP(Ef4xt!^lhO4=AqIHeQ@H=0bi%u!E
z*glSi2nC>NXL7Loy4m@w=K#wFeOT1remB3{`2y73#yin=gaW+z;4MTWpMC-8Bds%G
zUI3@RJR6#cqmAadLI7Ir)cN9Mdt0#oJqY3dI)nf`dRlOCBKPCbOwhulSqK0yk(vM4
zSgMogIgBU^2kV|#1pt`3afRamqJoRN8MH8YEilDoKlTIwM8((c-uvNr7DUAu3cwU@
zF4tJ9TL8y-PceBNJYE1=gx$(OtSyjUgwj7lmU^i|!~mpAG0lQs5#va5$A=MfD*ZTS
z5K=EBTuys(C6j(Bm3}Q}{h+F<Px(D@jB_RA@uxT3JsMQ561$pzN(cTj~NNA`u0ROI$EwLgaxEqqx1{i$>5Npb>w7
z82teXMql*J-5A0HhD41B#3c%f0Zagu(w3zys}RPVlG4uY40C5%!HGBNLo?@`d%k<`
zS-v|XKw&sso+qLx768bJi4g$MZz+d}xjB(0>=6W^-EK9P%gr95Nl*%qL-QlBnq4Z6Kg
zg#qVix7}*a(Q4-UGvGzLRxNq?g9X5a!`f{*T%H~RAlj{FZtY(q!08*x;t4B(_!U4>
z9Iz*MUaqAJ7@r2FXMu@nW!*dHfiZd0FxN21{)(8nYKo}J(VAa~|BuLJhy$-B0D17J#+|Ah|kBtXB`
zZh5`eoxqnL(r0Qh!3Ci7iqxo_^OVo`Z3Jw5p#--Nc-09|RSg|-AQ~0DOGGm0My%*)ktRwU480UkM4?&^8qVQ+JJff
zGC$Z|sILz1ofHA|x}=TS@jXCjsW`9+u&9>~O$eP{fPy$MhR1*hZ-f7-0nD~aO)3IV
zJNw6WYx0d-_))92Sl|MX7zb1o1r1Z4Uzf)1lP19*!_qo&_a!j-J7|JUsayakSWgxP
zjg)K&FH)MkTtt`(UE}AV!2M=vn$-n>R=aFR(CFVfK7koRk5T=5fOaJ?bOB%kcqsv}
zUF-h|!0<=N@Ig!z@zjCkNu&2(NhzqV7$Dc#Chl(qa?}bL+e)x}m&gon@6TH!h$FoG
z8kA-;kfv5bt(hYNQy%4J?s1!x;6&8S(K@KL(~%Gvc2`(p6LC8om_V(m`=<#Ff#9K(
zkq{e}$k3corIcilhtw0nDazyL2-N$(`6eYWG*{F~JsCCpwF-eS^u<;np#R~k#pqn_
zvTgTb1&rmpp|U(ziUE25 {
+ const app = document.createElement("div");
+ app.id = "root";
+ document.body.append(app);
+
+ const src = chrome?.runtime?.getURL("/react/main.js");
+ await import(src);
+})();
diff --git a/apps/loom-importer-extension/public/manifest.json b/apps/loom-importer-extension/public/manifest.json
new file mode 100644
index 000000000..8bff62e86
--- /dev/null
+++ b/apps/loom-importer-extension/public/manifest.json
@@ -0,0 +1,45 @@
+{
+ "manifest_version": 3,
+ "name": "Cap Loom Importer",
+ "description": "Import your Loom data to Cap",
+ "version": "1.0.0",
+ "icons": {
+ "16": "assets/images/icons/icon16.png",
+ "48": "assets/images/icons/icon48.png",
+ "128": "assets/images/icons/icon128.png"
+ },
+ "content_scripts": [
+ {
+ "matches": ["https://www.loom.com/*"],
+ "js": ["/assets/js/initializeUI.js"],
+ "run_at": "document_end",
+ "all_frames": true
+ }
+ ],
+ "background": {
+ "service_worker": "react/background.js",
+ "type": "module"
+ },
+ "action": {
+ "default_popup": "popup.html"
+ },
+ "web_accessible_resources": [
+ {
+ "resources": [
+ "/react/main.js",
+ "/react/vendor.js",
+ "/react/main.css",
+ "/react/jsx-runtime.js",
+ "/react/client.js",
+ "/react/urls.js"
+ ],
+ "matches": ["https://www.loom.com/*"]
+ }
+ ],
+ "permissions": ["cookies", "storage", "tabs", "scripting"],
+ "host_permissions": [
+ "https://cap.so/*",
+ "http://localhost:3000/*",
+ "https://www.loom.com/*"
+ ]
+}
diff --git a/apps/loom-importer-extension/src/api/cap.ts b/apps/loom-importer-extension/src/api/cap.ts
new file mode 100644
index 000000000..4cb49e5fd
--- /dev/null
+++ b/apps/loom-importer-extension/src/api/cap.ts
@@ -0,0 +1,130 @@
+import { LoomExportData } from "../types/loom";
+import { getApiBaseUrl } from "../utils/urls";
+
+interface ApiResponse {
+ data: T;
+ error?: string;
+}
+
+interface UserResponse {
+ user: User;
+ expires: string;
+}
+interface User {
+ name: string;
+ email: string;
+ image: string;
+ id: string;
+}
+
+interface LoomImportResponse {
+ success: boolean;
+ message: string;
+}
+
+interface Workspace {
+ id: string;
+ name: string;
+ ownerId: string;
+ metadata: null;
+ allowedEmailDomain: null;
+ customDomain: null;
+ domainVerified: null;
+ createdAt: string;
+ updatedAt: string;
+ workosOrganizationId: null;
+ workosConnectionId: null;
+}
+
+interface WorkspaceResponse {
+ workspaces: Workspace[];
+}
+
+export class CapApi {
+ private baseUrl = getApiBaseUrl();
+ private headers: HeadersInit = {
+ accept: "*/*",
+ "content-type": "application/json",
+ };
+
+ private async getAuthToken(): Promise {
+ return new Promise((resolve) => {
+ chrome.runtime.sendMessage({ action: "getAuthStatus" }, (response) => {
+ resolve(response?.token || null);
+ });
+ });
+ }
+
+ private async getHeaders(): Promise {
+ const token = await this.getAuthToken();
+ return {
+ ...this.headers,
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
+ };
+ }
+
+ private async request(
+ endpoint: string,
+ options: RequestInit = {}
+ ): Promise> {
+ try {
+ const headers = await this.getHeaders();
+ const response = await fetch(`${this.baseUrl}${endpoint}`, {
+ ...options,
+ headers: {
+ ...headers,
+ ...options.headers,
+ },
+ });
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ throw new Error(data.error || "API request failed");
+ }
+
+ return { data: data as T };
+ } catch (error) {
+ return {
+ data: {} as T,
+ error:
+ error instanceof Error ? error.message : "Unknown error occurred",
+ };
+ }
+ }
+
+ public async getUser(): Promise {
+ const response = await this.request("/auth/session");
+ return response.data;
+ }
+
+ /**
+ * Sends imported Loom data to Cap.so
+ * @param loomData The exported Loom data to import into Cap.so
+ * @returns Response with import status
+ */
+ public async sendLoomData(
+ loomData: LoomExportData
+ ): Promise {
+ const response = await this.request("/import/loom", {
+ method: "POST",
+ body: JSON.stringify(loomData),
+ });
+
+ if (response.error) {
+ return {
+ success: false,
+ message: response.error,
+ };
+ }
+
+ return response.data;
+ }
+
+ public async getWorkspaceDetails(): Promise {
+ const response = await this.request(
+ "/settings/workspace/details"
+ );
+ return response.data;
+ }
+}
diff --git a/apps/loom-importer-extension/src/background.ts b/apps/loom-importer-extension/src/background.ts
new file mode 100644
index 000000000..a966df524
--- /dev/null
+++ b/apps/loom-importer-extension/src/background.ts
@@ -0,0 +1,168 @@
+interface AuthResponse {
+ token: string | null;
+ timestamp?: number;
+}
+
+interface CookieChangeInfo {
+ removed: boolean;
+ cookie: chrome.cookies.Cookie;
+}
+
+interface ImportMessage {
+ type: string;
+ [key: string]: any;
+}
+
+import { getServerBaseUrl, CapUrls } from "./utils/urls";
+
+const baseUrl = getServerBaseUrl();
+
+async function checkAuthToken(): Promise {
+ try {
+ const cookie = await chrome.cookies.get({
+ url: baseUrl,
+ name: "next-auth.session-token",
+ });
+
+ if (cookie) {
+ const authData: AuthResponse = {
+ token: cookie.value,
+ timestamp: Date.now(),
+ };
+ await chrome.storage.local.set({ authData: authData });
+ return cookie.value;
+ }
+
+ return null;
+ } catch (error) {
+ console.error("Error checking auth token:", error);
+ return null;
+ }
+}
+
+async function verifyTokenFreshness(): Promise {
+ try {
+ const data = await chrome.storage.local.get("authData");
+ const authData = data.authData as AuthResponse | undefined;
+
+ if (!authData?.timestamp || Date.now() - authData.timestamp > 60000) {
+ await checkAuthToken();
+ }
+ } catch (error) {
+ console.error("Error verifying token freshness:", error);
+ }
+}
+
+async function redirectToLogin(): Promise {
+ const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
+ const currentTab = tabs[0];
+ if (currentTab?.id && currentTab.url?.startsWith(baseUrl)) {
+ await chrome.tabs.update(currentTab.id, { url: CapUrls.LOGIN });
+ }
+}
+
+chrome.cookies.onChanged.addListener(async (changeInfo: CookieChangeInfo) => {
+ if (
+ changeInfo.cookie.domain.includes(baseUrl) &&
+ changeInfo.cookie.name === "next-auth.session-token"
+ ) {
+ if (!changeInfo.removed) {
+ const authData: AuthResponse = {
+ token: changeInfo.cookie.value,
+ timestamp: Date.now(),
+ };
+ await chrome.storage.local.set({ authData: authData });
+ } else {
+ await chrome.storage.local.remove("authData");
+ }
+ }
+});
+
+setInterval(verifyTokenFreshness, 30000);
+function forwardToPopup(message: ImportMessage): void {
+ try {
+ chrome.runtime.sendMessage(message, (response) => {
+ if (chrome.runtime.lastError) {
+ console.warn(
+ "Error forwarding message to popup (it might be closed):",
+ chrome.runtime.lastError
+ );
+ }
+ });
+ } catch (err) {
+ console.warn(
+ "Error forwarding message to popup (it might be closed):",
+ err
+ );
+ }
+}
+
+const messageListeners = new Map();
+
+chrome.runtime.onMessage.addListener(
+ (
+ request: { action?: string; type?: string; [key: string]: any },
+ sender: chrome.runtime.MessageSender,
+ sendResponse: (response: any) => void
+ ) => {
+ if (request.action === "getAuthStatus") {
+ verifyTokenFreshness()
+ .then(() => checkAuthToken())
+ .then((token) => {
+ if (!token) {
+ redirectToLogin();
+ }
+ sendResponse({ token });
+ })
+ .catch((error) => {
+ console.error("Error getting auth status:", error);
+ sendResponse({ token: null, error: String(error) });
+ });
+ return true;
+ }
+
+ if (request.type && request.type.startsWith("CAP_")) {
+ console.log(`Received Cap message: ${request.type}`, request);
+
+ if (request.type === "CAP_LOOM_VIDEOS_SELECTED" && request.videos) {
+ chrome.storage.local.set({ selectedVideos: request.videos });
+
+ const capMessage: ImportMessage = {
+ type: request.type,
+ videos: [...request.videos],
+ };
+ forwardToPopup(capMessage);
+ } else if (request.type) {
+ const capMessage: ImportMessage = {
+ type: request.type,
+ ...request,
+ };
+ forwardToPopup(capMessage);
+ }
+
+ sendResponse({ success: true });
+ return true;
+ }
+
+ return false;
+ }
+);
+
+chrome.runtime.onMessageExternal.addListener(
+ (
+ request: { type: string; [key: string]: any },
+ sender: chrome.runtime.MessageSender,
+ sendResponse: (response: any) => void
+ ) => {
+ if (request.type && request.type.startsWith("CAP_")) {
+ const capMessage: ImportMessage = {
+ ...request,
+ };
+ forwardToPopup(capMessage);
+ sendResponse({ success: true });
+ return true;
+ }
+
+ return false;
+ }
+);
diff --git a/apps/loom-importer-extension/src/components/EmailSelector.tsx b/apps/loom-importer-extension/src/components/EmailSelector.tsx
new file mode 100644
index 000000000..942d17f8a
--- /dev/null
+++ b/apps/loom-importer-extension/src/components/EmailSelector.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+import { WorkspaceMember } from "../types/loom";
+
+interface EmailSelectorProps {
+ workspaceMembers: WorkspaceMember[];
+ onEmailSelected: (email: string) => void;
+ selectedEmail: string | null;
+}
+
+const EmailSelector: React.FC = ({
+ workspaceMembers,
+ onEmailSelected,
+ selectedEmail,
+}) => {
+ return (
+
+
+
+ Select Loom Email
+
+
+
onEmailSelected(e.target.value)}
+ >
+ Select an email
+ {workspaceMembers.map((member) => (
+
+ {member.email} ({member.name})
+
+ ))}
+
+
+ Select the email address you use with Loom to correctly identify your
+ videos.
+
+
+ );
+};
+
+export default EmailSelector;
diff --git a/apps/loom-importer-extension/src/components/ImportChecklist.tsx b/apps/loom-importer-extension/src/components/ImportChecklist.tsx
new file mode 100644
index 000000000..88a26f906
--- /dev/null
+++ b/apps/loom-importer-extension/src/components/ImportChecklist.tsx
@@ -0,0 +1,150 @@
+import React from "react";
+
+export type ChecklistItemStatus =
+ | "waiting"
+ | "in-progress"
+ | "complete"
+ | "error";
+
+export interface ChecklistItem {
+ message: string;
+ status: ChecklistItemStatus;
+}
+
+interface ImportChecklistProps {
+ items: ChecklistItem[];
+}
+
+const ImportChecklist: React.FC = ({ items }) => {
+ const getStatusIcon = (status: ChecklistItemStatus) => {
+ switch (status) {
+ case "waiting":
+ return (
+
+ );
+ case "in-progress":
+ return (
+
+ );
+ case "complete":
+ return (
+
+ );
+ case "error":
+ return (
+
+ );
+ default:
+ return (
+
+ );
+ }
+ };
+
+ const getStatusColor = (status: ChecklistItemStatus) => {
+ switch (status) {
+ case "waiting":
+ return "text-gray-500";
+ case "in-progress":
+ return "text-blue-600";
+ case "complete":
+ return "text-green-500";
+ case "error":
+ return "text-red-500";
+ default:
+ return "text-gray-500";
+ }
+ };
+
+ const getConnectionLine = (index: number) => {
+ if (index === items.length - 1) return null;
+
+ let lineColorClass = "border-gray-200";
+
+ if (items[index].status === "complete") {
+ if (
+ items[index + 1].status === "complete" ||
+ items[index + 1].status === "in-progress"
+ ) {
+ lineColorClass = "border-green-500";
+ }
+ }
+
+ return
;
+ };
+
+ return (
+
+
+ {items.map((item, index) => (
+
+
+
{getStatusIcon(item.status)}
+
+
+ {item.message}
+
+ {item.status === "in-progress" && (
+
Processing...
+ )}
+
+
+ {getConnectionLine(index)}
+
+ ))}
+
+
+ {items.length === 0 && (
+
+
No import steps started yet
+
+ )}
+
+ );
+};
+
+export default ImportChecklist;
diff --git a/apps/loom-importer-extension/src/components/ImportProgress.tsx b/apps/loom-importer-extension/src/components/ImportProgress.tsx
new file mode 100644
index 000000000..02be9ef41
--- /dev/null
+++ b/apps/loom-importer-extension/src/components/ImportProgress.tsx
@@ -0,0 +1,77 @@
+import React from "react";
+import ImportChecklist, { ChecklistItem } from "./ImportChecklist";
+import { ImportStep } from "../context/ImportContext";
+import { getChecklistItemsForStep } from "../utils/importUtils";
+
+interface ImportProgressProps {
+ importState: {
+ currentStep: ImportStep;
+ error: string | null;
+ };
+ processVideos: () => Promise;
+}
+
+const ImportProgress: React.FC = ({
+ importState,
+ processVideos,
+}) => {
+ const isProcessingDisabled =
+ importState.currentStep !== ImportStep.VIDEOS_SELECTED;
+
+ if (
+ importState.currentStep === ImportStep.IMPORT_COMPLETE &&
+ window.location.href.includes("loom.com")
+ ) {
+ return <>>;
+ }
+
+ return (
+
+
+
+ {importState.currentStep !== ImportStep.IDLE && (
+
+ {importState.currentStep === ImportStep.PROCESSING_VIDEOS
+ ? "Processing..."
+ : "Complete"}
+
+ )}
+
+ {importState.currentStep === ImportStep.PROCESSING_COMPLETE && (
+
+
+ Success 🎉
+
+
+ Open the Extension UI to finish
+
+
+ )}
+
+ {importState.error && (
+
+
+ Error
+
+
+ {importState.error}
+
+
window.location.reload()}
+ className="mt-2 px-3 py-1.5 bg-red-100 hover:bg-red-200 rounded-md text-red-600 text-xs font-medium transition-colors duration-200"
+ >
+ Reload page
+
+
+ )}
+
+ );
+};
+
+export default ImportProgress;
diff --git a/apps/loom-importer-extension/src/components/Logo.tsx b/apps/loom-importer-extension/src/components/Logo.tsx
new file mode 100644
index 000000000..bbe705be9
--- /dev/null
+++ b/apps/loom-importer-extension/src/components/Logo.tsx
@@ -0,0 +1,52 @@
+export const Logo = ({
+ className,
+ white,
+}: {
+ className: string;
+ white?: boolean;
+}) => {
+ return (
+
+
+ {/* */}
+ {/* */}
+
+
+
+
+
+
+ );
+};
diff --git a/apps/loom-importer-extension/src/components/LoomImporter.tsx b/apps/loom-importer-extension/src/components/LoomImporter.tsx
new file mode 100644
index 000000000..4fa88a972
--- /dev/null
+++ b/apps/loom-importer-extension/src/components/LoomImporter.tsx
@@ -0,0 +1,109 @@
+import React from "react";
+import ImportChecklist from "./ImportChecklist";
+import LoomLogo from "./LoomLogo";
+import { ChecklistItem } from "../types";
+import { ImportState, ImportStep, useImport } from "../context/ImportContext";
+import EmailSelector from "./EmailSelector";
+import { useImportStore } from "../store/importStore";
+import { CapUrls } from "../utils/urls";
+
+interface LoomImporterProps {
+ importStarted: boolean;
+ checklistItems: ChecklistItem[];
+ selectedWorkspaceId: string | null;
+ currentStep: ImportStep;
+ hasSelectedEmail: boolean;
+ onStartImport: () => void;
+ onSendToCap: () => void;
+ onResetImport: () => void;
+}
+
+const LoomImporter: React.FC = ({
+ importStarted,
+ checklistItems,
+ selectedWorkspaceId,
+ currentStep,
+ hasSelectedEmail,
+ onStartImport,
+ onSendToCap,
+ onResetImport,
+}) => {
+ if (currentStep === ImportStep.IMPORT_COMPLETE) {
+ return (
+
+
+
+ 🎉
+
+
+ Import Complete!
+
+
+
{
+ chrome.tabs.create({ url: CapUrls.DASHBOARD });
+ onResetImport();
+ }}
+ className="bg-blue-500 text-white px-4 py-2 rounded-full text-xs font-medium transition-colors duration-200 hover:bg-blue-600 w-full"
+ >
+ Go to Cap.so
+
+
+ );
+ }
+
+ return (
+ <>
+
+ {currentStep === ImportStep.PROCESSING_COMPLETE ? (
+
+
+
+
+ Import
+
+
+
+
+ Reset Import Data
+
+
+
+ ) : importStarted ? (
+
+
+
+ ) : (
+
+ {importStarted ? (
+
+ Reset Import Data
+
+ ) : (
+
+ Import from Loom
+
+
+ )}
+
+ )}
+
+ >
+ );
+};
+
+export default LoomImporter;
diff --git a/apps/loom-importer-extension/src/components/LoomLogo.tsx b/apps/loom-importer-extension/src/components/LoomLogo.tsx
new file mode 100644
index 000000000..e7d4feade
--- /dev/null
+++ b/apps/loom-importer-extension/src/components/LoomLogo.tsx
@@ -0,0 +1,19 @@
+import type { SVGProps } from "react";
+
+const Loom = (props: SVGProps) => (
+
+
+
+);
+
+export default Loom;
diff --git a/apps/loom-importer-extension/src/components/UserProfile.tsx b/apps/loom-importer-extension/src/components/UserProfile.tsx
new file mode 100644
index 000000000..69bb04e68
--- /dev/null
+++ b/apps/loom-importer-extension/src/components/UserProfile.tsx
@@ -0,0 +1,27 @@
+import React from "react";
+import Avatar from "./avatar";
+import { User } from "../types";
+
+interface UserProfileProps {
+ user: User | null;
+ onLogout: () => void;
+}
+
+const UserProfile: React.FC = ({ user, onLogout }) => {
+ return (
+
+
+
+
{user?.name || "Loading..."}
+
+
+ Logout
+
+
+ );
+};
+
+export default UserProfile;
diff --git a/apps/loom-importer-extension/src/components/WorkspaceSelector.tsx b/apps/loom-importer-extension/src/components/WorkspaceSelector.tsx
new file mode 100644
index 000000000..242000e92
--- /dev/null
+++ b/apps/loom-importer-extension/src/components/WorkspaceSelector.tsx
@@ -0,0 +1,50 @@
+import React from "react";
+import { Workspace } from "../types";
+
+interface WorkspaceSelectorProps {
+ workspaces: Workspace[];
+ selectedWorkspaceId: string | null;
+ onSelectWorkspace: (workspaceId: string) => void;
+ onCreateWorkspace: () => void;
+}
+
+const WorkspaceSelector: React.FC = ({
+ workspaces,
+ selectedWorkspaceId,
+ onSelectWorkspace,
+ onCreateWorkspace,
+}) => {
+ if (!workspaces || workspaces.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+
+ Select Workspace
+
+
+ + Create
+
+
+
onSelectWorkspace(e.target.value)}
+ >
+ Choose a workspace
+ {workspaces.map((workspace) => (
+
+ {workspace.name}
+
+ ))}
+
+
+ );
+};
+
+export default WorkspaceSelector;
diff --git a/apps/loom-importer-extension/src/components/avatar.tsx b/apps/loom-importer-extension/src/components/avatar.tsx
new file mode 100644
index 000000000..f782bf587
--- /dev/null
+++ b/apps/loom-importer-extension/src/components/avatar.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+
+interface AvatarProps {
+ username: string;
+ imageUrl?: string;
+}
+
+const Avatar: React.FC = ({ username, imageUrl }) => {
+ const initial = username.slice(0, 1).toUpperCase();
+
+ return (
+
+ {imageUrl ? (
+
+ ) : (
+
+ {initial}
+
+ )}
+
+ );
+};
+
+export default Avatar;
diff --git a/apps/loom-importer-extension/src/content_scripts/App.css b/apps/loom-importer-extension/src/content_scripts/App.css
new file mode 100644
index 000000000..b5c61c956
--- /dev/null
+++ b/apps/loom-importer-extension/src/content_scripts/App.css
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/apps/loom-importer-extension/src/content_scripts/App.tsx b/apps/loom-importer-extension/src/content_scripts/App.tsx
new file mode 100644
index 000000000..9e73dbe8a
--- /dev/null
+++ b/apps/loom-importer-extension/src/content_scripts/App.tsx
@@ -0,0 +1,32 @@
+import React from "react";
+import "./App.css";
+import { ImportProvider, useImport } from "../context/ImportContext";
+import ImportProgress from "../components/ImportProgress";
+
+const AppContent: React.FC = () => {
+ const { importState, processVideos } = useImport();
+ const { currentPage } = importState;
+
+ if (currentPage === "other") return null;
+
+ return (
+
+ );
+};
+
+function App() {
+ return (
+
+
+
+ );
+}
+
+export default App;
diff --git a/apps/loom-importer-extension/src/content_scripts/main.tsx b/apps/loom-importer-extension/src/content_scripts/main.tsx
new file mode 100644
index 000000000..86c824268
--- /dev/null
+++ b/apps/loom-importer-extension/src/content_scripts/main.tsx
@@ -0,0 +1,25 @@
+import React from "react";
+import App from "./App";
+import { createRoot } from "react-dom/client";
+
+const container = document.createElement("div");
+container.id = "cap-loom-importer";
+container.style.position = "fixed";
+container.style.bottom = "200px";
+container.style.right = "200px";
+container.style.zIndex = "9999";
+document.body.appendChild(container);
+
+const styleLink = document.createElement("link");
+styleLink.type = "text/css";
+styleLink.rel = "stylesheet";
+styleLink.href = chrome.runtime.getURL("/react/main.css");
+document.head.appendChild(styleLink);
+
+const root = createRoot(container);
+
+root.render(
+
+
+
+);
diff --git a/apps/loom-importer-extension/src/context/ImportContext.tsx b/apps/loom-importer-extension/src/context/ImportContext.tsx
new file mode 100644
index 000000000..d41676e5f
--- /dev/null
+++ b/apps/loom-importer-extension/src/context/ImportContext.tsx
@@ -0,0 +1,80 @@
+import React, { createContext, useContext, ReactNode, useEffect } from "react";
+import { LoomExportData } from "../types/loom";
+import * as LoomScraper from "../services/loomScraper";
+import { ImportStep, useImportStore } from "../store/importStore";
+
+export { ImportStep };
+export interface ImportState {
+ currentStep: ImportStep;
+ error: string | null;
+ data: LoomExportData;
+ currentPage: LoomScraper.LoomPage;
+}
+
+interface ImportContextType {
+ importState: ImportState;
+ startImport: (
+ workspaceId: string | null
+ ) => Promise<{ success: boolean; message?: string }>;
+ processVideos: () => Promise;
+ sendDataToCap: () => Promise<{ success: boolean; message?: string }>;
+ resetImport: () => void;
+}
+
+const ImportContext = createContext(undefined);
+
+export const ImportProvider: React.FC<{ children: ReactNode }> = ({
+ children,
+}) => {
+ const store = useImportStore();
+
+ useEffect(() => {
+ store.initializePageDetection();
+ }, []);
+
+ useEffect(() => {
+ store.loadExistingData();
+ }, [store.currentPage]);
+
+ useEffect(() => {
+ const cleanup = store.setupMemberScraping();
+ return cleanup;
+ }, [store.currentPage, store.currentStep, store.data]);
+
+ useEffect(() => {
+ const cleanup = store.setupWorkspaceDetection();
+ return cleanup;
+ }, [store.currentPage, store.currentStep]);
+
+ useEffect(() => {
+ const cleanup = store.setupVideoSelection();
+ return cleanup;
+ }, [store.currentPage, store.currentStep]);
+
+ const importContextValue: ImportContextType = {
+ importState: {
+ currentStep: store.currentStep,
+ error: store.error,
+ data: store.data,
+ currentPage: store.currentPage,
+ },
+ startImport: store.startImport,
+ processVideos: store.processVideos,
+ sendDataToCap: store.sendDataToCap,
+ resetImport: store.resetImport,
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useImport = (): ImportContextType => {
+ const context = useContext(ImportContext);
+ if (context === undefined) {
+ throw new Error("useImport must be used within an ImportProvider");
+ }
+ return context;
+};
diff --git a/apps/loom-importer-extension/src/hooks/useAuth.ts b/apps/loom-importer-extension/src/hooks/useAuth.ts
new file mode 100644
index 000000000..5e710b2b3
--- /dev/null
+++ b/apps/loom-importer-extension/src/hooks/useAuth.ts
@@ -0,0 +1,134 @@
+import { useState, useCallback, useEffect, useMemo } from "react";
+import { CapApi } from "../api/cap";
+import { AuthResponse, User } from "../types";
+import { CapUrls } from "../utils/urls";
+
+interface AuthState {
+ status: string;
+ token: string;
+ isError: boolean;
+ isAuthenticated: boolean;
+ user: User | null;
+}
+
+export function useAuth() {
+ const [authState, setAuthState] = useState({
+ status: "",
+ token: "",
+ isError: false,
+ isAuthenticated: false,
+ user: null,
+ });
+
+ const api = useMemo(() => new CapApi(), []);
+
+ const fetchUserData = useCallback(async () => {
+ try {
+ const userData = await api.getUser();
+ if (!userData.user) {
+ return;
+ }
+ setAuthState((prev) => ({
+ ...prev,
+ user: {
+ name: userData.user.name,
+ image: userData.user.image,
+ },
+ }));
+ } catch (error) {
+ console.error("Error fetching user data:", error);
+ setAuthState((prev) => ({ ...prev, isError: true }));
+ }
+ }, [api]);
+
+ useEffect(() => {
+ let isMounted = true;
+ const updateAuthState = (
+ status: string,
+ token?: string,
+ isError: boolean = false,
+ isAuthenticated: boolean = false
+ ) => {
+ if (!isMounted) return;
+
+ setAuthState((prev) => ({
+ ...prev,
+ status,
+ isError,
+ token: token || "No token found",
+ isAuthenticated,
+ }));
+ };
+
+ try {
+ chrome.runtime.sendMessage(
+ { action: "getAuthStatus" },
+ async (response: AuthResponse) => {
+ if (!isMounted) return;
+
+ if (chrome.runtime.lastError) {
+ updateAuthState(
+ "Error checking authentication status",
+ undefined,
+ true,
+ false
+ );
+ console.error("Runtime error:", chrome.runtime.lastError);
+ return;
+ }
+
+ if (response && response.token) {
+ const timestamp = response.timestamp
+ ? new Date(response.timestamp).toLocaleTimeString()
+ : "unknown";
+ updateAuthState(
+ `Authenticated (Last updated: ${timestamp})`,
+ response.token,
+ false,
+ true
+ );
+ await fetchUserData();
+ } else {
+ updateAuthState("Not authenticated", undefined, true, false);
+ }
+ }
+ );
+ } catch (error) {
+ if (!isMounted) return;
+
+ const errorMessage =
+ error instanceof Error ? error.message : "Unknown error occurred";
+ updateAuthState(`Error: ${errorMessage}`, undefined, true, false);
+ console.error("Error in useAuth:", error);
+ }
+
+ return () => {
+ isMounted = false;
+ };
+ }, [fetchUserData]);
+
+ const handleLogin = () => {
+ chrome.tabs.create({
+ url: CapUrls.LOGIN,
+ });
+ };
+
+ const handleLogout = () => {
+ chrome.storage.local.clear(() => {
+ console.log("Storage cleared");
+ setAuthState({
+ status: "Not authenticated",
+ token: "",
+ isError: false,
+ isAuthenticated: false,
+ user: null,
+ });
+ });
+ };
+
+ return {
+ ...authState,
+ handleLogin,
+ handleLogout,
+ };
+}
diff --git a/apps/loom-importer-extension/src/hooks/useWorkspaces.ts b/apps/loom-importer-extension/src/hooks/useWorkspaces.ts
new file mode 100644
index 000000000..2cf42bcef
--- /dev/null
+++ b/apps/loom-importer-extension/src/hooks/useWorkspaces.ts
@@ -0,0 +1,62 @@
+import { useState, useEffect, useMemo } from "react";
+import { CapApi } from "../api/cap";
+import { Workspace } from "../types";
+import { useImportStore } from "../store/importStore";
+import { CapUrls } from "../utils/urls";
+
+export function useWorkspaces(isAuthenticated: boolean) {
+ const [workspaces, setWorkspaces] = useState([]);
+ const { setSelectedWorkspaceId, data } = useImportStore();
+ const selectedWorkspaceId = data.selectedWorkspaceId;
+
+ const [isError, setIsError] = useState(false);
+ const [status, setStatus] = useState("");
+
+ const api = useMemo(() => new CapApi(), []);
+
+ useEffect(() => {
+ let isMounted = true;
+ const fetchWorkspaces = async () => {
+ if (isAuthenticated) {
+ try {
+ const workspaceData = await api.getWorkspaceDetails();
+ if (isMounted) {
+ setWorkspaces(workspaceData.workspaces || []);
+ }
+ } catch (error) {
+ console.error("Error fetching workspaces:", error);
+ if (isMounted) {
+ setIsError(true);
+ }
+ }
+ }
+ };
+
+ fetchWorkspaces();
+
+ return () => {
+ isMounted = false;
+ };
+ }, [isAuthenticated, api]);
+
+ const handleWorkspaceSelect = (workspaceId: string) => {
+ setSelectedWorkspaceId(workspaceId);
+ setIsError(false);
+ setStatus("");
+ };
+
+ const createWorkspace = () => {
+ chrome.tabs.create({
+ url: CapUrls.CREATE_WORKSPACE,
+ });
+ };
+
+ return {
+ workspaces,
+ selectedWorkspaceId,
+ isError,
+ status,
+ handleWorkspaceSelect,
+ createWorkspace,
+ };
+}
diff --git a/apps/loom-importer-extension/src/popup/popup.css b/apps/loom-importer-extension/src/popup/popup.css
new file mode 100644
index 000000000..e00948f6e
--- /dev/null
+++ b/apps/loom-importer-extension/src/popup/popup.css
@@ -0,0 +1,26 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+:root {
+ --primary: #005cb1;
+ --primary-2: #004c93;
+ --primary-3: #003b73;
+ --secondary: #2eb4ff;
+ --secondary-2: #1696e0;
+ --secondary-3: #117ebd;
+ --tertiary: #c5eaff;
+ --tertiary-2: #d3e5ff;
+ --tertiary-3: #e0edff;
+ --filler: #efefef;
+ --filler-2: #e4e4e4;
+ --filler-3: #e2e2e2;
+ --filler-txt: #b3b3b3;
+ --text-primary: #0d1b2a;
+ --text-secondary: #ffffff;
+ --header-height: 80px;
+ --vh100-offset: calc(100vh - var(--header-height));
+ --foreground-rgb: #000000;
+ --background-start-rgb: 214, 219, 220;
+ --background-end-rgb: 255, 255, 255;
+}
diff --git a/apps/loom-importer-extension/src/popup/popup.tsx b/apps/loom-importer-extension/src/popup/popup.tsx
new file mode 100644
index 000000000..3f9694732
--- /dev/null
+++ b/apps/loom-importer-extension/src/popup/popup.tsx
@@ -0,0 +1,153 @@
+import React, { useState } from "react";
+import { createRoot } from "react-dom/client";
+import "./popup.css";
+import { Logo } from "../components/Logo";
+import UserProfile from "../components/UserProfile";
+import WorkspaceSelector from "../components/WorkspaceSelector";
+import LoomImporter from "../components/LoomImporter";
+import EmailSelector from "../components/EmailSelector";
+import { useAuth } from "../hooks/useAuth";
+import {
+ ImportProvider,
+ useImport,
+ ImportStep,
+} from "../context/ImportContext";
+import { useWorkspaces } from "../hooks/useWorkspaces";
+import { getChecklistItemsForStep } from "../utils/importUtils";
+import { useImportStore } from "../store/importStore";
+
+const PopupContent = () => {
+ const {
+ isAuthenticated,
+ user,
+ handleLogin,
+ handleLogout,
+ isError: authError,
+ status: authStatus,
+ } = useAuth();
+
+ const {
+ workspaces,
+ selectedWorkspaceId,
+ handleWorkspaceSelect,
+ createWorkspace,
+ } = useWorkspaces(isAuthenticated);
+
+ const { importState, startImport, sendDataToCap, resetImport } = useImport();
+
+ const { setSelectedUserEmail } = useImportStore();
+
+ const importStarted = importState.currentStep !== ImportStep.IDLE;
+ const importComplete = importState.currentStep === ImportStep.IMPORT_COMPLETE;
+
+ const [importError, setImportError] = useState(null);
+
+ const handleStartImport = async () => {
+ try {
+ const result = await startImport(selectedWorkspaceId);
+ if (!result.success) {
+ setImportError(result.message || "Import failed");
+ }
+ } catch (error) {
+ console.error("Error starting import:", error);
+ setImportError(
+ error instanceof Error ? error.message : "Unknown error starting import"
+ );
+ }
+ };
+
+ const handleEmailSelected = (email: string) => {
+ setSelectedUserEmail(email);
+ };
+
+ const handleSendToCap = async () => {
+ try {
+ const result = await sendDataToCap();
+ if (!result.success) {
+ setImportError(result.message || "Import failed");
+ }
+ } catch (error) {
+ console.error("Error sending to Cap:", error);
+ setImportError(
+ error instanceof Error ? error.message : "Unknown error sending to Cap"
+ );
+ }
+ };
+
+ const checklistItems = getChecklistItemsForStep(importState.currentStep);
+
+ return (
+
+
+
+
+
+ {!isAuthenticated ? (
+
+
+ Login to Cap.so
+
+
+ ) : (
+ <>
+
Import Data
+
+ {(authError || importError || importState.error) && (
+
+ {importError || importState.error || authStatus}
+
+ )}
+
+ {!importStarted && workspaces && workspaces.length > 0 && (
+
+ )}
+
+ {importState.currentStep === ImportStep.PROCESSING_COMPLETE &&
+ importState.data.userEmail === null && (
+
+ )}
+
+
+
+
+
+ >
+ )}
+
+ );
+};
+
+const Popup = () => (
+
+
+
+);
+
+const root = createRoot(document.getElementById("root")!);
+
+root.render(
+
+
+
+);
diff --git a/apps/loom-importer-extension/src/services/loomScraper.ts b/apps/loom-importer-extension/src/services/loomScraper.ts
new file mode 100644
index 000000000..9637d2fd7
--- /dev/null
+++ b/apps/loom-importer-extension/src/services/loomScraper.ts
@@ -0,0 +1,279 @@
+import { LoomExportData, Video, WorkspaceMember } from "../types/loom";
+import { waitForElement } from "../utils/dom";
+import JSConfetti from "js-confetti";
+
+export type LoomPage = "members" | "workspace" | "other";
+
+/**
+ * Detects which Loom page we're currently on
+ */
+export const detectCurrentPage = (): LoomPage => {
+ const url = window.location.href;
+
+ if (url.includes("loom.com/settings/workspace#members")) {
+ return "members";
+ } else if (url.match(/loom\.com\/spaces\/[a-zA-Z0-9-]+/)) {
+ return "workspace";
+ } else {
+ return "other";
+ }
+};
+
+/**
+ * Loads existing import data from storage if available
+ */
+export const loadExistingData = (): Promise => {
+ return new Promise((resolve) => {
+ chrome.storage.local.get(["loomImportData"], (result) => {
+ if (result.loomImportData?.workspaceMembers?.length > 0) {
+ resolve(result.loomImportData);
+ } else {
+ resolve(null);
+ }
+ });
+ });
+};
+
+/**
+ * Scrapes workspace members from the Loom members page
+ */
+export const scrapeWorkspaceMembers = async (): Promise => {
+ const tableElement = await waitForElement('div[role="table"]');
+ if (!tableElement) {
+ throw new Error("Table element not found");
+ }
+
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+
+ const members: WorkspaceMember[] = [];
+ const tableRows = document.querySelectorAll(
+ 'div[role="table"] div[role="row"]'
+ );
+
+ if (!tableRows || tableRows.length === 0) {
+ throw new Error("No table rows found");
+ }
+
+ tableRows.forEach((row) => {
+ if (!row || !row.querySelectorAll) return;
+
+ const cells = row.querySelectorAll('div[role="cell"]');
+ if (!cells || cells.length < 6) return;
+
+ try {
+ const avatarSelector = [
+ 'img[alt^="Avatar for "]',
+ 'span span[aria-label^="Avatar for "]',
+ ].join(", ");
+
+ const avatarElement = cells[1]?.querySelector(avatarSelector);
+ const nameElement = avatarElement
+ ? (
+ avatarElement.getAttribute("alt") ||
+ avatarElement.getAttribute("aria-label")
+ )?.replace("Avatar for ", "") || ""
+ : "";
+
+ const roleElement = cells[2]?.querySelector("div");
+ const dateElement = cells[3]?.querySelector("div");
+ const emailElement = cells[4]?.querySelector("a");
+ const statusElement = cells[5]?.querySelector("div");
+
+ if (
+ nameElement &&
+ roleElement &&
+ dateElement &&
+ emailElement &&
+ statusElement
+ ) {
+ members.push({
+ name: nameElement?.trim() || "",
+ email: emailElement.textContent?.trim() || "",
+ role: roleElement.textContent?.trim() || "",
+ dateJoined: dateElement.textContent?.trim() || "",
+ status: statusElement.textContent?.trim() || "",
+ });
+ }
+ } catch (error) {
+ console.error("Error processing row:", error);
+ }
+ });
+
+ if (members.length === 0) {
+ throw new Error("No members found in table");
+ }
+
+ return members;
+};
+
+/**
+ * Saves workspace members to storage and returns updated data
+ */
+export const saveMembersToStorage = async (
+ currentData: LoomExportData,
+ members: WorkspaceMember[]
+): Promise => {
+ const updatedData = { ...currentData, workspaceMembers: members };
+
+ return new Promise((resolve) => {
+ chrome.storage.local.set({ loomImportData: updatedData }, () => {
+ resolve(updatedData);
+ });
+ });
+};
+
+/**
+ * Sets up video selection checkboxes and returns a cleanup function
+ */
+export const setupVideoSelection = async (
+ onSelectionChange: (hasSelectedVideos: boolean) => void
+): Promise<() => void> => {
+ const videoElement = await waitForElement("article[data-videoid]");
+ if (!videoElement) {
+ throw new Error("No videos found on page");
+ }
+
+ const articles = document.querySelectorAll(
+ "article[data-videoid]"
+ );
+
+ const listeners: { element: HTMLElement; listener: EventListener }[] = [];
+
+ articles.forEach((article) => {
+ const videoId = article.getAttribute("data-videoid");
+ if (!videoId) return;
+
+ const checkbox = document.querySelector(
+ `#bulk-action-${videoId}`
+ );
+ if (!checkbox) return;
+
+ const listener = () => {
+ const anyVideoSelected = Array.from(
+ document.querySelectorAll('input[id^="bulk-action-"]')
+ ).some((cb) => cb.checked);
+
+ onSelectionChange(anyVideoSelected);
+ };
+
+ checkbox.addEventListener("change", listener);
+ listeners.push({ element: checkbox, listener });
+ });
+
+ return () => {
+ listeners.forEach(({ element, listener }) => {
+ element.removeEventListener("change", listener);
+ });
+ };
+};
+
+/**
+ * Gets all selected videos from the page
+ */
+export const getSelectedVideos = (): {
+ id: string;
+ ownerName: string;
+ title: string;
+}[] => {
+ const selectedVideos = document.querySelectorAll(
+ 'input[id^="bulk-action-"]:checked'
+ );
+ const videos: {
+ id: string;
+ ownerName: string;
+ title: string;
+ }[] = [];
+
+ selectedVideos.forEach((checkbox) => {
+ const videoId = checkbox.id.replace("bulk-action-", "");
+ if (!videoId) return;
+
+ const article = document.querySelector(
+ `article[data-videoid="${videoId}"]`
+ );
+ if (!article) return;
+
+ const ownerName =
+ article
+ .querySelector('a[class^="profile-card_textLink_"] span')
+ ?.textContent?.trim() || "";
+
+ const labelText =
+ article.querySelector("a")?.getAttribute("aria-label") || "";
+
+ const title =
+ labelText.replace("Deselect video: ", "") || "Imported from Loom";
+
+ videos.push({ id: videoId, ownerName, title });
+ });
+
+ return videos;
+};
+
+/**
+ * Process the selected videos and match them with workspace members
+ */
+export const processVideos = (
+ rawVideos: { id: string; ownerName: string; title: string }[],
+ workspaceMembers: WorkspaceMember[]
+): Video[] => {
+ return rawVideos.map((video) => {
+ const owner = workspaceMembers.find((member) =>
+ member.name.includes(video.ownerName)
+ ) || { name: video.ownerName, email: "" };
+
+ return {
+ id: video.id,
+ owner: {
+ name: owner.name,
+ email: owner.email,
+ },
+ title: video.title,
+ };
+ });
+};
+
+/**
+ * Save processed videos to storage and send completion message
+ */
+export const saveProcessedVideos = async (
+ currentData: LoomExportData,
+ videos: Video[]
+): Promise => {
+ const updatedData = { ...currentData, videos };
+
+ return new Promise((resolve) => {
+ chrome.storage.local.set({ loomImportData: updatedData }, () => {
+ chrome.runtime.sendMessage({
+ type: "CAP_LOOM_IMPORT_COMPLETE",
+ data: updatedData,
+ });
+
+ const jsConfetti = new JSConfetti();
+ jsConfetti.addConfetti();
+
+ resolve(updatedData);
+ });
+ });
+};
+
+/**
+ * Complete the video import process
+ */
+export const completeVideoImport = async (
+ currentData: LoomExportData
+): Promise => {
+ try {
+ const rawVideos = getSelectedVideos();
+
+ const processedVideos = processVideos(
+ rawVideos,
+ currentData.workspaceMembers
+ );
+
+ return await saveProcessedVideos(currentData, processedVideos);
+ } catch (error) {
+ console.error("Failed to process videos:", error);
+ throw error;
+ }
+};
diff --git a/apps/loom-importer-extension/src/store/importStore.ts b/apps/loom-importer-extension/src/store/importStore.ts
new file mode 100644
index 000000000..cadc2e07b
--- /dev/null
+++ b/apps/loom-importer-extension/src/store/importStore.ts
@@ -0,0 +1,368 @@
+import { create } from "zustand";
+import { persist, createJSONStorage } from "zustand/middleware";
+import { LoomExportData } from "../types/loom";
+import * as LoomScraper from "../services/loomScraper";
+import { CapApi } from "../api/cap";
+
+export enum ImportStep {
+ IDLE = "idle",
+ COLLECTING_MEMBERS = "collecting_members",
+ MEMBERS_COLLECTED = "members_collected",
+ SELECT_WORKSPACE = "select_workspace",
+ SELECTING_VIDEOS = "selecting_videos",
+ VIDEOS_SELECTED = "videos_selected",
+ PROCESSING_VIDEOS = "processing_videos",
+ PROCESSING_COMPLETE = "processing_complete",
+ IMPORT_COMPLETE = "import_complete",
+}
+
+export interface ImportState {
+ currentStep: ImportStep;
+ error: string | null;
+ data: LoomExportData;
+ currentPage: LoomScraper.LoomPage;
+}
+
+interface ImportActions {
+ setCurrentStep: (step: ImportStep) => void;
+ setError: (error: string | null) => void;
+ setData: (data: Partial) => void;
+ setCurrentPage: (page: LoomScraper.LoomPage) => void;
+ setSelectedUserEmail: (email: string) => void;
+ setSelectedWorkspaceId: (id: string) => void;
+ startImport: (
+ workspaceId: string | null
+ ) => Promise<{ success: boolean; message?: string }>;
+ processVideos: () => Promise;
+ sendDataToCap: () => Promise<{ success: boolean; message?: string }>;
+ resetImport: () => void;
+
+ initializePageDetection: () => void;
+ loadExistingData: () => void;
+ setupMemberScraping: () => void;
+ setupWorkspaceDetection: () => void;
+ setupVideoSelection: () => void;
+}
+
+type ImportStore = ImportState & ImportActions;
+
+export const useImportStore = create()(
+ persist(
+ (set, get) => ({
+ currentStep: ImportStep.IDLE,
+ error: null,
+ data: {
+ workspaceMembers: [],
+ videos: [],
+ selectedWorkspaceId: "",
+ userEmail: null,
+ },
+ currentPage: "other",
+
+ setCurrentStep: (step) => set({ currentStep: step }),
+ setError: (error) => set({ error }),
+ setData: (data) =>
+ set((state) => ({
+ data: { ...state.data, ...data },
+ })),
+ setCurrentPage: (page) => set({ currentPage: page }),
+ setSelectedUserEmail: (email) => {
+ set({ data: { ...get().data, userEmail: email } });
+ set({ currentStep: ImportStep.PROCESSING_COMPLETE });
+ },
+ setSelectedWorkspaceId: (id) =>
+ set({ data: { ...get().data, selectedWorkspaceId: id } }),
+
+ initializePageDetection: () => {
+ const page = LoomScraper.detectCurrentPage();
+ set({ currentPage: page });
+ },
+
+ loadExistingData: async () => {
+ const { currentPage } = get();
+ if (currentPage === "workspace" || currentPage === "members") {
+ const data = await LoomScraper.loadExistingData();
+ if (data) {
+ const newStep =
+ data.videos && data.videos.length > 0
+ ? ImportStep.PROCESSING_COMPLETE
+ : data.workspaceMembers && data.workspaceMembers.length > 0
+ ? ImportStep.SELECT_WORKSPACE
+ : ImportStep.MEMBERS_COLLECTED;
+
+ set({ data, currentStep: newStep });
+ }
+ }
+ },
+
+ setupMemberScraping: () => {
+ const { currentPage, currentStep, data } = get();
+
+ if (
+ currentPage === "members" &&
+ currentStep === ImportStep.COLLECTING_MEMBERS
+ ) {
+ const timer = setTimeout(async () => {
+ try {
+ const members = await LoomScraper.scrapeWorkspaceMembers();
+ const updatedData = await LoomScraper.saveMembersToStorage(
+ data,
+ members
+ );
+
+ set({
+ currentStep: ImportStep.MEMBERS_COLLECTED,
+ data: updatedData,
+ error: null,
+ });
+ } catch (error) {
+ console.error("Failed to scrape members:", error);
+
+ set({
+ error: `Failed to get workspace members: ${
+ error instanceof Error ? error.message : "Unknown error"
+ }`,
+ });
+ }
+ }, 3000);
+
+ return () => clearTimeout(timer);
+ }
+
+ return () => {};
+ },
+
+ setupWorkspaceDetection: () => {
+ const { currentPage, currentStep } = get();
+
+ if (
+ currentPage === "workspace" &&
+ currentStep === ImportStep.SELECT_WORKSPACE
+ ) {
+ const checkWorkspaceUrl = () => {
+ const url = window.location.href;
+ const workspacePattern = /https:\/\/www\.loom\.com\/spaces\/[\w-]+/;
+
+ if (workspacePattern.test(url)) {
+ set({
+ currentStep: ImportStep.SELECTING_VIDEOS,
+ });
+ }
+ };
+
+ checkWorkspaceUrl();
+
+ const intervalId = setInterval(checkWorkspaceUrl, 1000);
+
+ return () => clearInterval(intervalId);
+ }
+
+ return () => {};
+ },
+
+ setupVideoSelection: () => {
+ const { currentPage, currentStep } = get();
+
+ if (
+ currentPage === "workspace" &&
+ currentStep === ImportStep.SELECTING_VIDEOS
+ ) {
+ let cleanup: (() => void) | undefined;
+
+ const timer = setTimeout(async () => {
+ try {
+ const cleanupFn = await LoomScraper.setupVideoSelection(
+ (hasSelectedVideos) => {
+ set({
+ currentStep: hasSelectedVideos
+ ? ImportStep.VIDEOS_SELECTED
+ : ImportStep.MEMBERS_COLLECTED,
+ });
+ }
+ );
+
+ cleanup = cleanupFn;
+ } catch (error) {
+ console.error("Failed to setup video selection:", error);
+
+ set({
+ error: `Failed to setup video selection: ${
+ error instanceof Error ? error.message : "Unknown error"
+ }`,
+ });
+ }
+ }, 3000);
+
+ return () => {
+ clearTimeout(timer);
+ if (cleanup) cleanup();
+ };
+ }
+
+ return () => {};
+ },
+
+ startImport: async (workspaceId: string | null) => {
+ if (!workspaceId) {
+ return { success: false, message: "Please select a workspace first" };
+ }
+
+ try {
+ set({
+ currentStep: ImportStep.COLLECTING_MEMBERS,
+ error: null,
+ });
+
+ chrome.storage.local.remove(["loomImportData"], () => {
+ chrome.tabs.create({
+ url: "https://www.loom.com/settings/workspace#members",
+ });
+ });
+
+ return { success: true };
+ } catch (error) {
+ console.error("Import failed:", error);
+
+ set({
+ error: `Import failed: ${
+ error instanceof Error ? error.message : "Unknown error"
+ }`,
+ });
+
+ return {
+ success: false,
+ message: `Import failed: ${
+ error instanceof Error ? error.message : "Unknown error"
+ }`,
+ };
+ }
+ },
+
+ processVideos: async () => {
+ try {
+ set({
+ currentStep: ImportStep.PROCESSING_COMPLETE,
+ error: null,
+ });
+
+ const { data } = get();
+ const updatedData = await LoomScraper.completeVideoImport(data);
+
+ set({
+ data: updatedData,
+ error: null,
+ });
+ } catch (error) {
+ console.error("Failed to process videos:", error);
+
+ set({
+ error: `Failed to process videos: ${
+ error instanceof Error ? error.message : "Unknown error"
+ }`,
+ });
+ }
+ },
+
+ sendDataToCap: async () => {
+ const { data } = get();
+
+ if (!data || !data.videos || data.videos.length === 0) {
+ set({
+ error: "No import data available",
+ });
+
+ return { success: false, message: "No import data available" };
+ }
+
+ if (!data.userEmail) {
+ set({
+ error: "You must select your email address before importing",
+ });
+
+ return {
+ success: false,
+ message: "You must select your email address before importing",
+ };
+ }
+
+ try {
+ const api = new CapApi();
+ const response = await api.sendLoomData({
+ ...data,
+ userEmail: data.userEmail,
+ selectedWorkspaceId: data.selectedWorkspaceId,
+ });
+
+ if (response && response.success) {
+ chrome.storage.local.remove(["loomImportData"]);
+ set({ currentStep: ImportStep.IMPORT_COMPLETE });
+ return { success: true };
+ } else {
+ throw new Error(
+ (response && response.message) || "Failed to send data to Cap.so"
+ );
+ }
+ } catch (error) {
+ console.error("Error sending data to Cap.so:", error);
+
+ set({
+ error: `Error sending data to Cap.so: ${
+ error instanceof Error ? error.message : "Unknown error"
+ }`,
+ });
+
+ return {
+ success: false,
+ message: `Error sending data to Cap.so: ${
+ error instanceof Error ? error.message : "Unknown error"
+ }`,
+ };
+ }
+ },
+
+ resetImport: () => {
+ chrome.storage.local.clear(() => {
+ console.log("Storage cleared");
+
+ set({
+ currentStep: ImportStep.IDLE,
+ error: null,
+ data: {
+ workspaceMembers: [],
+ videos: [],
+ selectedWorkspaceId: "",
+ userEmail: "",
+ },
+ currentPage: LoomScraper.detectCurrentPage(),
+ });
+ });
+ },
+ }),
+ {
+ name: "loom-import-storage",
+ storage: createJSONStorage(() => ({
+ getItem: async (name) => {
+ return new Promise((resolve) => {
+ chrome.storage.local.get([name], (result) => {
+ resolve(result[name] || null);
+ });
+ });
+ },
+ setItem: async (name, value) => {
+ return new Promise((resolve) => {
+ chrome.storage.local.set({ [name]: value }, () => {
+ resolve();
+ });
+ });
+ },
+ removeItem: async (name) => {
+ return new Promise((resolve) => {
+ chrome.storage.local.remove([name], () => {
+ resolve();
+ });
+ });
+ },
+ })),
+ }
+ )
+);
diff --git a/apps/loom-importer-extension/src/types/index.ts b/apps/loom-importer-extension/src/types/index.ts
new file mode 100644
index 000000000..6ef84deb7
--- /dev/null
+++ b/apps/loom-importer-extension/src/types/index.ts
@@ -0,0 +1,41 @@
+import { LoomExportData } from "./loom";
+
+export interface AuthResponse {
+ token: string | null;
+ timestamp?: number;
+}
+
+export interface Workspace {
+ id: string;
+ name: string;
+}
+
+export interface User {
+ name: string;
+ image: string;
+}
+
+export interface ChecklistItem {
+ message: string;
+ status: ChecklistItemStatus;
+}
+
+export type ChecklistItemStatus =
+ | "waiting"
+ | "in-progress"
+ | "complete"
+ | "error";
+
+export interface PopupState {
+ status: string;
+ token: string;
+ isError: boolean;
+ isAuthenticated: boolean;
+ user: User | null;
+ importStarted: boolean;
+ importComplete: boolean;
+ importData: LoomExportData | null;
+ checklistItems: ChecklistItem[];
+ workspaces: Workspace[];
+ selectedWorkspaceId: string | null;
+}
diff --git a/apps/loom-importer-extension/src/types/loom.ts b/apps/loom-importer-extension/src/types/loom.ts
new file mode 100644
index 000000000..eabb774ae
--- /dev/null
+++ b/apps/loom-importer-extension/src/types/loom.ts
@@ -0,0 +1,25 @@
+export interface WorkspaceMember {
+ name: string;
+ email: string;
+ role: string;
+ dateJoined: string;
+ status: string;
+}
+
+export interface VideoOwner {
+ name: string;
+ email: string;
+}
+
+export interface Video {
+ id: string;
+ owner: VideoOwner;
+ title: string;
+}
+
+export interface LoomExportData {
+ workspaceMembers: WorkspaceMember[];
+ videos: Video[];
+ selectedWorkspaceId: string;
+ userEmail: string | null;
+}
diff --git a/apps/loom-importer-extension/src/utils/dom.ts b/apps/loom-importer-extension/src/utils/dom.ts
new file mode 100644
index 000000000..33fc86eb2
--- /dev/null
+++ b/apps/loom-importer-extension/src/utils/dom.ts
@@ -0,0 +1,30 @@
+/**
+ * Waits for a DOM element to appear using a selector
+ */
+export const waitForElement = (
+ selector: string,
+ timeout = 30000
+): Promise => {
+ return new Promise((resolve) => {
+ if (document.querySelector(selector)) {
+ return resolve(document.querySelector(selector));
+ }
+
+ const observer = new MutationObserver(() => {
+ if (document.querySelector(selector)) {
+ resolve(document.querySelector(selector));
+ observer.disconnect();
+ }
+ });
+
+ observer.observe(document.body, {
+ childList: true,
+ subtree: true,
+ });
+
+ setTimeout(() => {
+ observer.disconnect();
+ resolve(document.querySelector(selector));
+ }, timeout);
+ });
+};
diff --git a/apps/loom-importer-extension/src/utils/importUtils.ts b/apps/loom-importer-extension/src/utils/importUtils.ts
new file mode 100644
index 000000000..59c43bfaf
--- /dev/null
+++ b/apps/loom-importer-extension/src/utils/importUtils.ts
@@ -0,0 +1,80 @@
+import { ImportStep } from "../context/ImportContext";
+import {
+ ChecklistItem,
+ ChecklistItemStatus,
+} from "../components/ImportChecklist";
+
+/**
+ * Generates the checklist items based on the current import step
+ * @param currentStep The current import step
+ * @returns Array of checklist items to display
+ */
+export const getChecklistItemsForStep = (
+ currentStep: ImportStep
+): ChecklistItem[] => {
+ const items: ChecklistItem[] = [];
+
+ switch (currentStep) {
+ case ImportStep.IDLE:
+ items.push({
+ message: "Ready to start importing",
+ status: "waiting",
+ });
+ break;
+ case ImportStep.COLLECTING_MEMBERS:
+ items.push({
+ message: "Collecting workspace members...",
+ status: "in-progress",
+ });
+ break;
+ case ImportStep.SELECT_WORKSPACE:
+ items.push({
+ message: "Select a workspace to import from",
+ status: "waiting",
+ });
+ break;
+ case ImportStep.MEMBERS_COLLECTED:
+ items.push(
+ { message: "Workspace members collected", status: "complete" },
+ { message: "Select a workspace to import from", status: "waiting" }
+ );
+ break;
+ case ImportStep.SELECTING_VIDEOS:
+ items.push(
+ { message: "Workspace members collected", status: "complete" },
+ { message: "Select videos to import", status: "in-progress" }
+ );
+ break;
+ case ImportStep.VIDEOS_SELECTED:
+ items.push(
+ { message: "Workspace members collected", status: "complete" },
+ { message: "Videos selected", status: "complete" },
+ { message: "Ready to process videos", status: "waiting" }
+ );
+ break;
+ case ImportStep.PROCESSING_VIDEOS:
+ items.push(
+ { message: "Workspace members collected", status: "complete" },
+ { message: "Videos selected", status: "complete" },
+ { message: "Processing videos...", status: "in-progress" }
+ );
+ break;
+ case ImportStep.PROCESSING_COMPLETE:
+ items.push(
+ { message: "Workspace members collected", status: "complete" },
+ { message: "Videos selected", status: "complete" },
+ { message: "Processing complete!", status: "complete" }
+ );
+ break;
+ case ImportStep.IMPORT_COMPLETE:
+ items.push(
+ { message: "Workspace members collected", status: "complete" },
+ { message: "Videos selected", status: "complete" },
+ { message: "Processing complete!", status: "complete" },
+ { message: "Import complete!", status: "complete" }
+ );
+ break;
+ }
+
+ return items;
+};
diff --git a/apps/loom-importer-extension/src/utils/urls.ts b/apps/loom-importer-extension/src/utils/urls.ts
new file mode 100644
index 000000000..6bb24b9c1
--- /dev/null
+++ b/apps/loom-importer-extension/src/utils/urls.ts
@@ -0,0 +1,39 @@
+/**
+ * URL utilities for Cap server
+ */
+
+/**
+ * Gets the base server URL with proper protocol
+ * @returns The formatted base URL for the Cap server
+ */
+export const getServerBaseUrl = (): string => {
+ // @ts-expect-error
+ const serverUrl = import.meta.env.VITE_SERVER_URL || "https://cap.so";
+ return serverUrl.startsWith("http") ? serverUrl : `https://${serverUrl}`;
+};
+
+/**
+ * Gets the base API URL
+ * @returns The formatted API URL
+ */
+export const getApiBaseUrl = (): string => {
+ return `${getServerBaseUrl()}/api`;
+};
+
+/**
+ * Constructs a URL for a specific Cap route
+ * @param path The path to append to the base URL (should start with /)
+ * @returns The complete URL
+ */
+export const getCapUrl = (path: string): string => {
+ return `${getServerBaseUrl()}${path}`;
+};
+
+/**
+ * Common URL paths used in the application
+ */
+export const CapUrls = {
+ LOGIN: getCapUrl("/login"),
+ DASHBOARD: getCapUrl("/dashboard"),
+ CREATE_WORKSPACE: getCapUrl("/dashboard/caps?createSpace=true"),
+};
diff --git a/apps/loom-importer-extension/src/vite-env.d.ts b/apps/loom-importer-extension/src/vite-env.d.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/loom-importer-extension/tailwind.config.js b/apps/loom-importer-extension/tailwind.config.js
new file mode 100644
index 000000000..ddc397832
--- /dev/null
+++ b/apps/loom-importer-extension/tailwind.config.js
@@ -0,0 +1,23 @@
+const baseConfig = require("@cap/ui/tailwind")("web");
+
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ ...baseConfig,
+ theme: {
+ ...baseConfig.theme,
+ extend: {
+ ...baseConfig.theme?.extend,
+ keyframes: {
+ ...baseConfig.theme?.extend?.keyframes,
+ gentleBounce: {
+ "0%, 100%": { transform: "translateY(0)" },
+ "50%": { transform: "translateY(-4px)" },
+ },
+ },
+ animation: {
+ ...baseConfig.theme?.extend?.animation,
+ "gentle-bounce": "gentleBounce 1.5s ease-in-out infinite",
+ },
+ },
+ },
+};
diff --git a/apps/loom-importer-extension/tsconfig.json b/apps/loom-importer-extension/tsconfig.json
new file mode 100644
index 000000000..b5a395411
--- /dev/null
+++ b/apps/loom-importer-extension/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "typeRoots": ["node_modules/@types"]
+ },
+ "include": ["./src/**/*"],
+ "exclude": ["./node_modules", "./dist", "../../packages"]
+}
diff --git a/apps/loom-importer-extension/vite.config.ts b/apps/loom-importer-extension/vite.config.ts
new file mode 100644
index 000000000..0f26acd16
--- /dev/null
+++ b/apps/loom-importer-extension/vite.config.ts
@@ -0,0 +1,40 @@
+import { defineConfig } from "vite";
+import tsconfigPaths from "vite-tsconfig-paths";
+import react from "@vitejs/plugin-react";
+import { resolve } from "path";
+import hotReloadExtension from "hot-reload-extension-vite";
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [
+ process.env.MODE !== "production"
+ ? react({
+ jsxRuntime: "classic",
+ })
+ : react(),
+ tsconfigPaths(),
+ hotReloadExtension({
+ log: true,
+ backgroundPath: resolve(__dirname, "src/background.ts"),
+ }),
+ ],
+ build: {
+ rollupOptions: {
+ input: {
+ // Main entry and extension scripts
+ main: resolve(__dirname, "index.html"),
+ background: resolve(__dirname, "src/background.ts"),
+ popup: resolve(__dirname, "popup.html"),
+ },
+ output: {
+ entryFileNames: `react/[name].js`,
+ chunkFileNames: `react/[name].js`,
+ assetFileNames: `react/[name].[ext]`,
+ },
+ },
+ // Enable watch mode when running with --watch flag
+ watch: {
+ include: ["src/**", "public/**"],
+ },
+ },
+});
diff --git a/apps/web/app/api/import/loom/route.ts b/apps/web/app/api/import/loom/route.ts
new file mode 100644
index 000000000..9d24687b5
--- /dev/null
+++ b/apps/web/app/api/import/loom/route.ts
@@ -0,0 +1,304 @@
+import { type NextRequest } from "next/server";
+import { getCurrentUser } from "@cap/database/auth/session";
+import {
+ users,
+ spaces,
+ spaceMembers,
+ videos,
+ sharedVideos,
+} from "@cap/database/schema";
+import { db } from "@cap/database";
+import { eq, inArray, or } from "drizzle-orm";
+import { nanoId } from "@cap/database/helpers";
+import { z } from "zod";
+
+export interface WorkspaceMember {
+ name: string;
+ email: string;
+ role: string;
+ dateJoined: string;
+ status: string;
+}
+
+export interface VideoOwner {
+ name: string;
+ email: string;
+}
+
+export interface Video {
+ id: string;
+ owner: VideoOwner;
+ title: string;
+}
+
+export interface LoomExportData {
+ workspaceMembers: WorkspaceMember[];
+ videos: Video[];
+ selectedWorkspaceId: string;
+ userEmail: string;
+}
+
+/**
+ * Creates user accounts for Loom workspace members
+ */
+async function createUsersFromLoomWorkspaceMembers(
+ workspaceMembers: WorkspaceMember[],
+ workspaceId: string
+): Promise {
+ const emails = workspaceMembers.map((member) => member.email);
+
+ const existingUsers = await db
+ .select({ id: users.id, email: users.email })
+ .from(users)
+ .where(inArray(users.email, emails));
+
+ const existingEmails = new Set(existingUsers.map((user) => user.email));
+ const newUserIds: string[] = existingUsers.map((user) => user.id);
+
+ for (const member of workspaceMembers.filter(
+ (m) => !existingEmails.has(m.email)
+ )) {
+ const nameParts = member.name.split(" ");
+ const firstName = nameParts[0] || "";
+ const lastName = nameParts.slice(1).join(" ") || "";
+
+ const userId = nanoId();
+ await db.insert(users).values({
+ id: userId,
+ email: member.email,
+ name: firstName,
+ lastName: lastName,
+ inviteQuota: 1,
+
+ activeSpaceId: workspaceId,
+ });
+
+ newUserIds.push(userId);
+ }
+
+ return newUserIds;
+}
+
+/**
+ * Adds users to the owner's workspace
+ */
+async function addUsersToOwnerWorkspace(
+ userIds: string[],
+ ownerId: string,
+ spaceId: string
+): Promise {
+ const existingMembers = await db
+ .select({ userId: spaceMembers.userId })
+ .from(spaceMembers)
+ .where(eq(spaceMembers.spaceId, spaceId));
+
+ const existingMemberIds = new Set(
+ existingMembers.map((member) => member.userId)
+ );
+
+ for (const userId of userIds.filter(
+ (id) => !existingMemberIds.has(id) && id !== ownerId
+ )) {
+ await db.insert(spaceMembers).values({
+ id: nanoId(),
+ userId: userId,
+ spaceId: spaceId,
+ role: "member",
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ });
+
+ const user = await db
+ .select({ activeSpaceId: users.activeSpaceId })
+ .from(users)
+ .where(eq(users.id, userId))
+ .then((results) => results[0]);
+
+ if (!user?.activeSpaceId) {
+ await db
+ .update(users)
+ .set({ activeSpaceId: spaceId })
+ .where(eq(users.id, userId));
+ }
+ }
+}
+
+/**
+ * Downloads a video from Loom's CDN
+ * This is an empty function as requested, to be implemented later
+ */
+async function downloadVideoFromLoom(videoId: string): Promise<{
+ videoUrl: string;
+ thumbnailUrl: string;
+ metadata: Record;
+}> {
+ // TODO: For cap.so team replace this actual upload to S3 implementation
+
+ return {
+ videoUrl: `https://placehold.co/600x400/EEE/31343C`,
+ thumbnailUrl: `https://placehold.co/600x400/EEE/31343C`,
+ metadata: {
+ originalLoomId: videoId,
+ importedAt: new Date().toISOString(),
+ },
+ };
+}
+
+/**
+ * Imports videos from Loom into Cap.so
+ */
+async function importVideosFromLoom(
+ loomVideos: Video[],
+ ownerId: string,
+ spaceId: string,
+ userEmail: string
+): Promise {
+ for (const loomVideo of loomVideos) {
+ try {
+ const owner = loomVideo.owner;
+
+ let videoOwnerId = ownerId;
+
+ // If the video owner email matches the user's Loom email, assign it directly to the user
+ if (owner && owner.email && owner.email === userEmail) {
+ videoOwnerId = ownerId;
+ }
+ // Otherwise try to find user by email
+ else if (owner && owner.email) {
+ const existingOwner = await db
+ .select({ id: users.id })
+ .from(users)
+ .where(eq(users.email, owner.email))
+ .then((results) => results[0]);
+
+ if (existingOwner) {
+ videoOwnerId = existingOwner.id;
+ }
+ }
+
+ const videoData = await downloadVideoFromLoom(loomVideo.id);
+
+ const videoId = nanoId();
+ await db.insert(videos).values({
+ id: videoId,
+ ownerId: videoOwnerId,
+ name: loomVideo.title,
+ loomVideoId: loomVideo.id,
+ public: true,
+ metadata: videoData.metadata,
+ source: { type: "desktopMP4" },
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ });
+
+ if (videoOwnerId !== ownerId) {
+ await db.insert(sharedVideos).values({
+ id: nanoId(),
+ videoId: videoId,
+ spaceId: spaceId,
+ sharedByUserId: ownerId,
+ sharedAt: new Date(),
+ });
+ }
+ } catch (error) {
+ console.error(`Failed to import Loom video ${loomVideo.id}:`, error);
+ }
+ }
+}
+
+const loomExportSchema = z.object({
+ workspaceMembers: z.array(
+ z.object({
+ name: z.string(),
+ email: z.string().email(),
+ role: z.string(),
+ dateJoined: z.string(),
+ status: z.string(),
+ })
+ ),
+ videos: z.array(
+ z.object({
+ id: z.string(),
+ owner: z.object({
+ name: z.string(),
+ email: z.string().email(),
+ }),
+ title: z.string(),
+ })
+ ),
+ selectedWorkspaceId: z.string(),
+ userEmail: z.string().email(),
+});
+
+export async function POST(request: NextRequest) {
+ try {
+ const user = await getCurrentUser();
+
+ if (!user || !user.id) {
+ console.error("User not found or unauthorized");
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
+ }
+ const body = loomExportSchema.parse(await request.json());
+
+ let targetSpaceId: string;
+
+ const userSpaces = await db
+ .select({ spaceId: spaces.id })
+ .from(spaces)
+ .leftJoin(spaceMembers, eq(spaces.id, spaceMembers.spaceId))
+ .where(or(eq(spaces.ownerId, user.id), eq(spaceMembers.userId, user.id)));
+
+ const hasAccess = userSpaces.some(
+ (space) => space.spaceId === body.selectedWorkspaceId
+ );
+
+ if (hasAccess) {
+ targetSpaceId = body.selectedWorkspaceId;
+ } else {
+ console.warn(
+ `User ${user.id} attempted to import to workspace ${body.selectedWorkspaceId} without access`
+ );
+ return Response.json(
+ { error: "You don't have access to the selected workspace" },
+ { status: 403 }
+ );
+ }
+
+ const userIds = await createUsersFromLoomWorkspaceMembers(
+ body.workspaceMembers,
+ targetSpaceId
+ );
+
+ await addUsersToOwnerWorkspace(userIds, user.id, targetSpaceId);
+
+ await importVideosFromLoom(
+ body.videos,
+ user.id,
+ targetSpaceId,
+ body.userEmail
+ );
+
+ return Response.json(
+ {
+ success: true,
+ usersCreated: userIds.length,
+ videosImported: body.videos.length,
+ spaceId: targetSpaceId,
+ },
+ {
+ status: 200,
+ }
+ );
+ } catch (error) {
+ console.error("Error importing Loom data:", error);
+ return Response.json(
+ {
+ error: "Failed to import data",
+ message: (error as Error).message,
+ },
+ {
+ status: 500,
+ }
+ );
+ }
+}
diff --git a/apps/web/app/api/settings/workspace/details/route.ts b/apps/web/app/api/settings/workspace/details/route.ts
index 074ade52a..df3e48b0b 100644
--- a/apps/web/app/api/settings/workspace/details/route.ts
+++ b/apps/web/app/api/settings/workspace/details/route.ts
@@ -4,6 +4,29 @@ import { spaces } from "@cap/database/schema";
import { db } from "@cap/database";
import { eq } from "drizzle-orm";
+export async function GET() {
+ const user = await getCurrentUser();
+
+ if (!user) {
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
+ }
+
+ try {
+ const userSpaces = await db
+ .select()
+ .from(spaces)
+ .where(eq(spaces.ownerId, user.id));
+
+ return Response.json({ workspaces: userSpaces }, { status: 200 });
+ } catch (error) {
+ console.error("Error fetching user workspaces:", error);
+ return Response.json(
+ { error: "Failed to fetch workspaces" },
+ { status: 500 }
+ );
+ }
+}
+
export async function POST(request: NextRequest) {
const user = await getCurrentUser();
const { workspaceName, allowedEmailDomain, spaceId } = await request.json();
diff --git a/apps/web/app/dashboard/_components/AdminNavbar/AdminNavItems.tsx b/apps/web/app/dashboard/_components/AdminNavbar/AdminNavItems.tsx
index 5d866f318..9749486bc 100644
--- a/apps/web/app/dashboard/_components/AdminNavbar/AdminNavItems.tsx
+++ b/apps/web/app/dashboard/_components/AdminNavbar/AdminNavItems.tsx
@@ -1,5 +1,5 @@
"use client";
-import { usePathname, useRouter } from "next/navigation";
+import { usePathname, useRouter, useSearchParams } from "next/navigation";
import {
Plus,
LogOut,
@@ -115,8 +115,8 @@ export const AdminNavItems = () => {
const navItemClass =
"flex items-center justify-start py-2 px-3 rounded-full outline-none tracking-tight w-full";
-
- const [dialogOpen, setDialogOpen] = useState(false);
+ const searchParams = useSearchParams();
+ const [dialogOpen, setDialogOpen] = useState(searchParams.get('createSpace') === 'true');
return (
diff --git a/packages/database/schema.ts b/packages/database/schema.ts
index 5bff02c02..6f8843178 100644
--- a/packages/database/schema.ts
+++ b/packages/database/schema.ts
@@ -210,6 +210,7 @@ export const videos = mysqlTable(
transcriptionStatus: varchar("transcriptionStatus", { length: 255 }),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow().onUpdateNow(),
+ loomVideoId: varchar("loomVideoId", { length: 255 }).default(""),
source: json("source")
.$type<
{ type: "MediaConvert" } | { type: "local" } | { type: "desktopMP4" }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a9e63eed2..86af15478 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -15,7 +15,7 @@ importers:
devDependencies:
'@turbo/gen':
specifier: ^1.9.7
- version: 1.13.4(@types/node@22.12.0)(typescript@5.7.2)
+ version: 1.13.4(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2)
dotenv-cli:
specifier: latest
version: 8.0.0
@@ -105,7 +105,7 @@ importers:
version: 0.14.7(solid-js@1.9.3)
'@solidjs/start':
specifier: ^1.0.6
- version: 1.0.7(@testing-library/jest-dom@6.5.0)(solid-js@1.9.3)(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(terser@5.34.0)(xml2js@0.6.2))(vite@5.4.8(@types/node@22.12.0)(terser@5.34.0))
+ version: 1.0.7(@testing-library/jest-dom@6.5.0)(solid-js@1.9.3)(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(lightningcss@1.29.2)(terser@5.34.0)(webpack-sources@3.2.3)(xml2js@0.6.2))(vite@5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0))
'@tanstack/solid-query':
specifier: ^5.51.21
version: 5.56.2(solid-js@1.9.3)
@@ -150,7 +150,7 @@ importers:
version: 3.51.0(@types/node@22.12.0)(zod@3.24.1)
'@types/react-tooltip':
specifier: ^4.2.4
- version: 4.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 4.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
cva:
specifier: npm:class-variance-authority@^0.7.0
version: class-variance-authority@0.7.0
@@ -180,16 +180,16 @@ importers:
version: 0.2.3(solid-js@1.9.3)
unplugin-auto-import:
specifier: ^0.18.2
- version: 0.18.3(rollup@4.22.5)
+ version: 0.18.3(rollup@4.22.5)(webpack-sources@3.2.3)
unplugin-icons:
specifier: ^0.19.2
- version: 0.19.3
+ version: 0.19.3(webpack-sources@3.2.3)
uuid:
specifier: ^9.0.1
version: 9.0.1
vinxi:
specifier: ^0.4.1
- version: 0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(terser@5.34.0)(xml2js@0.6.2)
+ version: 0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(lightningcss@1.29.2)(terser@5.34.0)(webpack-sources@3.2.3)(xml2js@0.6.2)
webcodecs:
specifier: ^0.1.0
version: 0.1.0
@@ -220,10 +220,10 @@ importers:
version: 5.7.2
vite:
specifier: ^5.4.3
- version: 5.4.8(@types/node@22.12.0)(terser@5.34.0)
+ version: 5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0)
vite-tsconfig-paths:
specifier: ^5.0.1
- version: 5.0.1(typescript@5.7.2)(vite@5.4.8(@types/node@22.12.0)(terser@5.34.0))
+ version: 5.0.1(typescript@5.7.2)(vite@5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0))
apps/discord-bot:
dependencies:
@@ -248,7 +248,7 @@ importers:
devDependencies:
'@cloudflare/vitest-pool-workers':
specifier: ^0.6.4
- version: 0.6.16(@cloudflare/workers-types@4.20250214.0)(@vitest/runner@2.1.9)(@vitest/snapshot@2.1.9)(vitest@2.1.9(@types/node@22.12.0)(jsdom@25.0.1)(terser@5.34.0))
+ version: 0.6.16(@cloudflare/workers-types@4.20250214.0)(@vitest/runner@2.1.9)(@vitest/snapshot@2.1.9)(vitest@2.1.9(@types/node@22.12.0)(jsdom@25.0.1)(lightningcss@1.29.2)(terser@5.34.0))
'@cloudflare/workers-types':
specifier: ^4.20250214.0
version: 4.20250214.0
@@ -257,11 +257,148 @@ importers:
version: 5.7.2
vitest:
specifier: ~2.1.9
- version: 2.1.9(@types/node@22.12.0)(jsdom@25.0.1)(terser@5.34.0)
+ version: 2.1.9(@types/node@22.12.0)(jsdom@25.0.1)(lightningcss@1.29.2)(terser@5.34.0)
wrangler:
specifier: ^3.109.1
version: 3.109.1(@cloudflare/workers-types@4.20250214.0)
+ apps/loom-importer-extension:
+ dependencies:
+ '@cap/ui':
+ specifier: workspace:^
+ version: link:../../packages/ui
+ '@cap/ui-solid':
+ specifier: workspace:^
+ version: link:../../packages/ui-solid
+ js-confetti:
+ specifier: ^0.12.0
+ version: 0.12.0
+ react:
+ specifier: ^18.2.0
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.3.1(react@18.3.1)
+ react-frame-component:
+ specifier: ^5.2.1
+ version: 5.2.7(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ vite-tsconfig-paths:
+ specifier: ^3.3.17
+ version: 3.6.0(vite@2.9.18)
+ zustand:
+ specifier: ^5.0.3
+ version: 5.0.3(@types/react@18.3.9)(react@18.3.1)(use-sync-external-store@1.2.2(react@18.3.1))
+ devDependencies:
+ '@types/chrome':
+ specifier: ^0.0.176
+ version: 0.0.176
+ '@types/react':
+ specifier: ^18.2.0
+ version: 18.3.9
+ '@types/react-dom':
+ specifier: ^18.2.0
+ version: 18.3.0
+ '@vitejs/plugin-react':
+ specifier: ^1.0.7
+ version: 1.3.2
+ hot-reload-extension-vite:
+ specifier: ^1.0.13
+ version: 1.0.13
+ tailwindcss:
+ specifier: ^3.4.16
+ version: 3.4.16(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@4.9.5))
+ typescript:
+ specifier: ^4.4.4
+ version: 4.9.5
+ vite:
+ specifier: ^2.7.2
+ version: 2.9.18
+
+ apps/old:
+ dependencies:
+ '@cap/ui':
+ specifier: workspace:*
+ version: link:../../packages/ui
+ copy-webpack-plugin:
+ specifier: ^13.0.0
+ version: 13.0.0(webpack@5.98.0)
+ lucide-react:
+ specifier: ^0.294.0
+ version: 0.294.0(react@18.3.1)
+ react:
+ specifier: ^18.2.0
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.3.1(react@18.3.1)
+ stream-http:
+ specifier: ^3.2.0
+ version: 3.2.0
+ url:
+ specifier: ^0.11.4
+ version: 0.11.4
+ devDependencies:
+ '@tailwindcss/forms':
+ specifier: ^0.5.7
+ version: 0.5.10(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)))
+ '@types/chrome':
+ specifier: ^0.0.309
+ version: 0.0.309
+ '@types/node':
+ specifier: ^20.10.4
+ version: 20.16.9
+ '@types/react':
+ specifier: ^18.0.29
+ version: 18.3.9
+ '@types/react-dom':
+ specifier: ^18.0.11
+ version: 18.3.0
+ '@vitejs/plugin-react-swc':
+ specifier: ^3.5.0
+ version: 3.8.0(@swc/helpers@0.5.13)(vite@5.4.14(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0))
+ autoprefixer:
+ specifier: ^10.4.16
+ version: 10.4.20(postcss@8.4.47)
+ css-loader:
+ specifier: ^7.1.2
+ version: 7.1.2(webpack@5.98.0)
+ postcss:
+ specifier: ^8.4.47
+ version: 8.4.47
+ postcss-loader:
+ specifier: ^8.1.1
+ version: 8.1.1(postcss@8.4.47)(typescript@5.7.2)(webpack@5.98.0)
+ prettier:
+ specifier: ^2.2.1
+ version: 2.8.8
+ shell-quote:
+ specifier: ^1.8.1
+ version: 1.8.2
+ style-loader:
+ specifier: ^4.0.0
+ version: 4.0.0(webpack@5.98.0)
+ tailwindcss:
+ specifier: ^3.3.6
+ version: 3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
+ ts-loader:
+ specifier: ^9.5.2
+ version: 9.5.2(typescript@5.7.2)(webpack@5.98.0)
+ typescript:
+ specifier: ^5.0.4
+ version: 5.7.2
+ vite:
+ specifier: ^5.0.7
+ version: 5.4.14(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0)
+ webpack:
+ specifier: ^5.98.0
+ version: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
+ webpack-cli:
+ specifier: ^6.0.1
+ version: 6.0.1(webpack@5.98.0)
+ webpack-merge:
+ specifier: ^6.0.1
+ version: 6.0.1
+
apps/storybook:
dependencies:
'@cap/ui-solid':
@@ -269,7 +406,7 @@ importers:
version: link:../../packages/ui-solid
postcss-pseudo-companion-classes:
specifier: ^0.1.1
- version: 0.1.1(postcss@8.4.47)
+ version: 0.1.1(postcss@8.5.3)
solid-js:
specifier: ^1.9.3
version: 1.9.3
@@ -303,16 +440,16 @@ importers:
version: 1.0.0-beta.2(babel-preset-solid@1.9.0(@babel/core@7.25.2))(solid-js@1.9.3)
storybook-solidjs-vite:
specifier: ^1.0.0-beta.2
- version: 1.0.0-beta.2(storybook@8.3.3)(vite@5.4.8(@types/node@22.12.0)(terser@5.34.0))
+ version: 1.0.0-beta.2(storybook@8.3.3)(vite@5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0))
typescript:
specifier: ^5.7.2
version: 5.7.2
vite:
specifier: ^5.3.4
- version: 5.4.8(@types/node@22.12.0)(terser@5.34.0)
+ version: 5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0)
vite-plugin-solid:
specifier: ^2.10.2
- version: 2.10.2(@testing-library/jest-dom@6.5.0)(solid-js@1.9.3)(vite@5.4.8(@types/node@22.12.0)(terser@5.34.0))
+ version: 2.10.2(@testing-library/jest-dom@6.5.0)(solid-js@1.9.3)(vite@5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0))
apps/tasks:
dependencies:
@@ -357,7 +494,7 @@ importers:
version: 1.10.0
ts-node:
specifier: ^10.9.2
- version: 10.9.2(@types/node@20.16.9)(typescript@5.7.2)
+ version: 10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)
typescript:
specifier: ^5.7.2
version: 5.7.2
@@ -373,7 +510,7 @@ importers:
version: 8.57.1
eslint-config-airbnb-typescript:
specifier: ^18.0.0
- version: 18.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2))(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1))(eslint@8.57.1)
+ version: 18.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2))(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1)
eslint-import-resolver-typescript:
specifier: ^3.6.1
version: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1)
@@ -382,7 +519,7 @@ importers:
version: 2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
jest:
specifier: ^29.7.0
- version: 29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))
+ version: 29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
nodemon:
specifier: ^3.1.0
version: 3.1.7
@@ -391,7 +528,7 @@ importers:
version: 6.3.4
ts-jest:
specifier: ^29.1.2
- version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2)))(typescript@5.7.2)
+ version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)))(typescript@5.7.2)
apps/web:
dependencies:
@@ -644,7 +781,7 @@ importers:
version: 8.4.47
tailwindcss:
specifier: ^3
- version: 3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))
+ version: 3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
typescript:
specifier: ^5.7.2
version: 5.7.2
@@ -653,13 +790,13 @@ importers:
dependencies:
'@vitejs/plugin-react':
specifier: ^4.0.3
- version: 4.3.1(vite@4.5.5(@types/node@20.16.9)(terser@5.34.0))
+ version: 4.3.1(vite@4.5.5(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0))
vite:
specifier: ^4.4.5
- version: 4.5.5(@types/node@20.16.9)(terser@5.34.0)
+ version: 4.5.5(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0)
vite-tsconfig-paths:
specifier: ^4.2.0
- version: 4.3.2(typescript@5.7.2)(vite@4.5.5(@types/node@20.16.9)(terser@5.34.0))
+ version: 4.3.2(typescript@5.7.2)(vite@4.5.5(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0))
devDependencies:
'@types/node':
specifier: ^20.4.5
@@ -693,7 +830,7 @@ importers:
version: 4.6.2(eslint@8.57.1)
eslint-plugin-tailwindcss:
specifier: ^3.12.0
- version: 3.17.4(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2)))
+ version: 3.17.4(tailwindcss@4.0.13)
eslint-utils:
specifier: ^3.0.0
version: 3.0.0(eslint@8.57.1)
@@ -801,7 +938,7 @@ importers:
version: link:../utils
'@kobalte/tailwindcss':
specifier: ^0.9.0
- version: 0.9.0(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2)))
+ version: 0.9.0(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)))
'@radix-ui/react-dialog':
specifier: ^1.0.5
version: 1.1.1(@types/react-dom@19.0.4(@types/react@18.3.9))(@types/react@18.3.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -825,7 +962,7 @@ importers:
version: 1.1.0(@types/react-dom@19.0.4(@types/react@18.3.9))(@types/react@18.3.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tailwindcss/typography':
specifier: ^0.5.9
- version: 0.5.15(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2)))
+ version: 0.5.15(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)))
class-variance-authority:
specifier: ^0.7.0
version: 0.7.0
@@ -856,7 +993,7 @@ importers:
version: 19.0.4(@types/react@18.3.9)
'@vitejs/plugin-react':
specifier: ^4.0.3
- version: 4.3.1(vite@4.5.5(@types/node@20.16.9)(terser@5.34.0))
+ version: 4.3.1(vite@4.5.5(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0))
autoprefixer:
specifier: ^10.4.16
version: 10.4.20(postcss@8.4.47)
@@ -877,13 +1014,13 @@ importers:
version: 6.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
tailwind-scrollbar:
specifier: ^3.1.0
- version: 3.1.0(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2)))
+ version: 3.1.0(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)))
tailwindcss:
specifier: ^3.3.3
- version: 3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))
+ version: 3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
tailwindcss-animate:
specifier: ^1.0.6
- version: 1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2)))
+ version: 1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)))
tauri-plugin-context-menu:
specifier: ^0.5.0
version: 0.5.0
@@ -898,10 +1035,10 @@ importers:
version: 5.7.2
vite:
specifier: ^4.4.4
- version: 4.5.5(@types/node@20.16.9)(terser@5.34.0)
+ version: 4.5.5(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0)
vite-tsconfig-paths:
specifier: ^4.2.1
- version: 4.3.2(typescript@5.7.2)(vite@4.5.5(@types/node@20.16.9)(terser@5.34.0))
+ version: 4.3.2(typescript@5.7.2)(vite@4.5.5(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0))
packages/ui-solid:
dependencies:
@@ -916,7 +1053,7 @@ importers:
version: 1.9.3
tailwindcss:
specifier: ^3.4.10
- version: 3.4.13(ts-node@10.9.2(@types/node@22.12.0)(typescript@5.7.2))
+ version: 3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2))
devDependencies:
'@fontsource/geist-sans':
specifier: ^5.0.3
@@ -926,10 +1063,10 @@ importers:
version: 2.2.253
'@kobalte/tailwindcss':
specifier: ^0.9.0
- version: 0.9.0(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@22.12.0)(typescript@5.7.2)))
+ version: 0.9.0(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2)))
'@tailwindcss/typography':
specifier: ^0.5.9
- version: 0.5.15(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@22.12.0)(typescript@5.7.2)))
+ version: 0.5.15(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2)))
autoprefixer:
specifier: ^10.4.20
version: 10.4.20(postcss@8.4.47)
@@ -941,19 +1078,19 @@ importers:
version: 1.0.0-beta.2(babel-preset-solid@1.9.0(@babel/core@7.25.2))(solid-js@1.9.3)
tailwind-scrollbar:
specifier: ^3.1.0
- version: 3.1.0(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@22.12.0)(typescript@5.7.2)))
+ version: 3.1.0(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2)))
tailwindcss-animate:
specifier: ^1.0.6
- version: 1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@22.12.0)(typescript@5.7.2)))
+ version: 1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2)))
unplugin-auto-import:
specifier: ^0.18.2
- version: 0.18.3(rollup@4.22.5)
+ version: 0.18.3(rollup@4.22.5)(webpack-sources@3.2.3)
unplugin-fonts:
specifier: ^1.1.1
- version: 1.1.1(vite@5.4.8(@types/node@22.12.0)(terser@5.34.0))
+ version: 1.1.1(vite@5.4.14(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0))(webpack-sources@3.2.3)
unplugin-icons:
specifier: ^0.19.2
- version: 0.19.3
+ version: 0.19.3(webpack-sources@3.2.3)
packages/utils:
dependencies:
@@ -1207,6 +1344,10 @@ packages:
resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==}
engines: {node: '>=6.9.0'}
+ '@babel/code-frame@7.26.2':
+ resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
+ engines: {node: '>=6.9.0'}
+
'@babel/compat-data@7.25.4':
resolution: {integrity: sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==}
engines: {node: '>=6.9.0'}
@@ -1219,6 +1360,14 @@ packages:
resolution: {integrity: sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==}
engines: {node: '>=6.9.0'}
+ '@babel/generator@7.26.10':
+ resolution: {integrity: sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-annotate-as-pure@7.25.9':
+ resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-compilation-targets@7.25.2':
resolution: {integrity: sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==}
engines: {node: '>=6.9.0'}
@@ -1231,6 +1380,10 @@ packages:
resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-module-imports@7.25.9':
+ resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-module-transforms@7.25.2':
resolution: {integrity: sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==}
engines: {node: '>=6.9.0'}
@@ -1241,6 +1394,10 @@ packages:
resolution: {integrity: sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-plugin-utils@7.26.5':
+ resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-simple-access@7.24.7':
resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==}
engines: {node: '>=6.9.0'}
@@ -1249,10 +1406,18 @@ packages:
resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-string-parser@7.25.9':
+ resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-validator-identifier@7.24.7':
resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-validator-identifier@7.25.9':
+ resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-validator-option@7.24.8':
resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==}
engines: {node: '>=6.9.0'}
@@ -1270,6 +1435,11 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
+ '@babel/parser@7.26.10':
+ resolution: {integrity: sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
'@babel/plugin-syntax-async-generators@7.8.4':
resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
peerDependencies:
@@ -1313,6 +1483,12 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
+ '@babel/plugin-syntax-jsx@7.25.9':
+ resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
'@babel/plugin-syntax-logical-assignment-operators@7.10.4':
resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==}
peerDependencies:
@@ -1361,6 +1537,12 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
+ '@babel/plugin-transform-react-jsx-development@7.25.9':
+ resolution: {integrity: sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
'@babel/plugin-transform-react-jsx-self@7.24.7':
resolution: {integrity: sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==}
engines: {node: '>=6.9.0'}
@@ -1373,6 +1555,12 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
+ '@babel/plugin-transform-react-jsx@7.25.9':
+ resolution: {integrity: sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
'@babel/runtime-corejs3@7.25.6':
resolution: {integrity: sha512-Gz0Nrobx8szge6kQQ5Z5MX9L3ObqNwCQY1PSwSNzreFL7aHGxv8Fp2j3ETV6/wWdbiV+mW6OSm8oQhg3Tcsniw==}
engines: {node: '>=6.9.0'}
@@ -1385,14 +1573,26 @@ packages:
resolution: {integrity: sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==}
engines: {node: '>=6.9.0'}
+ '@babel/template@7.26.9':
+ resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==}
+ engines: {node: '>=6.9.0'}
+
'@babel/traverse@7.25.6':
resolution: {integrity: sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==}
engines: {node: '>=6.9.0'}
+ '@babel/traverse@7.26.10':
+ resolution: {integrity: sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==}
+ engines: {node: '>=6.9.0'}
+
'@babel/types@7.25.6':
resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==}
engines: {node: '>=6.9.0'}
+ '@babel/types@7.26.10':
+ resolution: {integrity: sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==}
+ engines: {node: '>=6.9.0'}
+
'@bcoe/v8-coverage@0.2.3':
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
@@ -1494,6 +1694,9 @@ packages:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
+ '@cush/relative@1.0.0':
+ resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
+
'@deepgram/captions@1.2.0':
resolution: {integrity: sha512-8B1C/oTxTxyHlSFubAhNRgCbQ2SQ5wwvtlByn8sDYZvdDtdn/VE2yEPZ4BvUnrKWmsbTQY6/ooLV+9Ka2qmDSQ==}
engines: {node: '>=18.0.0'}
@@ -1508,6 +1711,10 @@ packages:
'@deno/shim-deno@0.19.2':
resolution: {integrity: sha512-q3VTHl44ad8T2Tw2SpeAvghdGOjlnLPDNO2cpOxwMrBE/PVas6geWpbpIgrM+czOCH0yejp0yi8OaTuB+NU40Q==}
+ '@discoveryjs/json-ext@0.6.3':
+ resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==}
+ engines: {node: '>=14.17.0'}
+
'@effect-ts/core@0.60.5':
resolution: {integrity: sha512-qi1WrtJA90XLMnj2hnUszW9Sx4dXP03ZJtCc5DiUBIOhF4Vw7plfb65/bdBySPoC9s7zy995TdUX1XBSxUkl5w==}
@@ -2001,6 +2208,12 @@ packages:
cpu: [ia32]
os: [linux]
+ '@esbuild/linux-loong64@0.14.54':
+ resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+
'@esbuild/linux-loong64@0.16.4':
resolution: {integrity: sha512-peDrrUuxbZ9Jw+DwLCh/9xmZAk0p0K1iY5d2IcwmnN+B87xw7kujOkig6ZRcZqgrXgeRGurRHn0ENMAjjD5DEg==}
engines: {node: '>=12'}
@@ -4655,10 +4868,10 @@ packages:
react-dom:
optional: true
- '@storybook/builder-vite@9.0.0-alpha.2':
- resolution: {integrity: sha512-j+yUaBzLrssR6dPkXj/IYubFgr3JYUovW5tneiNAJigFIcH9xNQ08m4z6pYXGKUYOqcvVLm5S5o8STg/BQjZ8Q==}
+ '@storybook/builder-vite@9.0.0-alpha.4':
+ resolution: {integrity: sha512-TxCLKB+nHkoooKzcJN8QKM2D8l9A8t5/qPQkStkadP5xdkoMC9CHYuq0Anu55mtbzZQnuDtS09lT3laGqswRuw==}
peerDependencies:
- storybook: ^9.0.0-alpha.2
+ storybook: ^9.0.0-alpha.4
vite: ^4.0.0 || ^5.0.0 || ^6.0.0
'@storybook/core@8.3.3':
@@ -4669,10 +4882,10 @@ packages:
peerDependencies:
storybook: ^8.3.3
- '@storybook/csf-plugin@9.0.0-alpha.2':
- resolution: {integrity: sha512-AAyHIaL6NIalFKI7gBzad85jg5vZ6RBzboQXup3qxEz4d0BrPEJva9XQf63F90SP/I4kaBQA9gURXJX8ZJ86bg==}
+ '@storybook/csf-plugin@9.0.0-alpha.4':
+ resolution: {integrity: sha512-Zq65uk267XRMr/DeAoOj1Yom6tuMPAOJo+GRw6qglX3NPxjc5eheGkFbT74P4CGXxYX9D5jDe9hY4CGBLNkACw==}
peerDependencies:
- storybook: ^9.0.0-alpha.2
+ storybook: ^9.0.0-alpha.4
'@storybook/csf@0.1.11':
resolution: {integrity: sha512-dHYFQH3mA+EtnCkHXzicbLgsvzYjcDJ1JWsogbItZogkPHgSJM/Wr71uMkcvw8v9mmCyP4NpXJuu6bPoVsOnzg==}
@@ -4717,6 +4930,75 @@ packages:
resolution: {integrity: sha512-pKS3wZnJoL1iTyGBXAvCwduNNeghJHY6QSRSNNvpYnrrQrLZ6Owsazjyynu0e0ObRgks0i7Rv+pe2M7/MBTZpQ==}
engines: {node: '>=12.16'}
+ '@swc/core-darwin-arm64@1.11.8':
+ resolution: {integrity: sha512-rrSsunyJWpHN+5V1zumndwSSifmIeFQBK9i2RMQQp15PgbgUNxHK5qoET1n20pcUrmZeT6jmJaEWlQchkV//Og==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@swc/core-darwin-x64@1.11.8':
+ resolution: {integrity: sha512-44goLqQuuo0HgWnG8qC+ZFw/qnjCVVeqffhzFr9WAXXotogVaxM8ze6egE58VWrfEc8me8yCcxOYL9RbtjhS/Q==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@swc/core-linux-arm-gnueabihf@1.11.8':
+ resolution: {integrity: sha512-Mzo8umKlhTWwF1v8SLuTM1z2A+P43UVhf4R8RZDhzIRBuB2NkeyE+c0gexIOJBuGSIATryuAF4O4luDu727D1w==}
+ engines: {node: '>=10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@swc/core-linux-arm64-gnu@1.11.8':
+ resolution: {integrity: sha512-EyhO6U+QdoGYC1MeHOR0pyaaSaKYyNuT4FQNZ1eZIbnuueXpuICC7iNmLIOfr3LE5bVWcZ7NKGVPlM2StJEcgA==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@swc/core-linux-arm64-musl@1.11.8':
+ resolution: {integrity: sha512-QU6wOkZnS6/QuBN1MHD6G2BgFxB0AclvTVGbqYkRA7MsVkcC29PffESqzTXnypzB252/XkhQjoB2JIt9rPYf6A==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@swc/core-linux-x64-gnu@1.11.8':
+ resolution: {integrity: sha512-r72onUEIU1iJi9EUws3R28pztQ/eM3EshNpsPRBfuLwKy+qn3et55vXOyDhIjGCUph5Eg2Yn8H3h6MTxDdLd+w==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@swc/core-linux-x64-musl@1.11.8':
+ resolution: {integrity: sha512-294k8cLpO103++f4ZUEDr3vnBeUfPitW6G0a3qeVZuoXFhFgaW7ANZIWknUc14WiLOMfMecphJAEiy9C8OeYSw==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@swc/core-win32-arm64-msvc@1.11.8':
+ resolution: {integrity: sha512-EbjOzQ+B85rumHyeesBYxZ+hq3ZQn+YAAT1ZNE9xW1/8SuLoBmHy/K9YniRGVDq/2NRmp5kI5+5h5TX0asIS9A==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@swc/core-win32-ia32-msvc@1.11.8':
+ resolution: {integrity: sha512-Z+FF5kgLHfQWIZ1KPdeInToXLzbY0sMAashjd/igKeP1Lz0qKXVAK+rpn6ASJi85Fn8wTftCGCyQUkRVn0bTDg==}
+ engines: {node: '>=10'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@swc/core-win32-x64-msvc@1.11.8':
+ resolution: {integrity: sha512-j6B6N0hChCeAISS6xp/hh6zR5CSCr037BAjCxNLsT8TGe5D+gYZ57heswUWXRH8eMKiRDGiLCYpPB2pkTqxCSw==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@swc/core@1.11.8':
+ resolution: {integrity: sha512-UAL+EULxrc0J73flwYHfu29mO8CONpDJiQv1QPDXsyCvDUcEhqAqUROVTgC+wtJCFFqMQdyr4stAA5/s0KSOmA==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@swc/helpers': '*'
+ peerDependenciesMeta:
+ '@swc/helpers':
+ optional: true
+
'@swc/counter@0.1.3':
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
@@ -4726,6 +5008,9 @@ packages:
'@swc/helpers@0.5.5':
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
+ '@swc/types@0.1.19':
+ resolution: {integrity: sha512-WkAZaAfj44kh/UFdAQcrMP1I0nwRqpt27u+08LMBYMqmQfwwMofYoMh/48NGkMMRfC4ynpfwRbJuu8ErfNloeA==}
+
'@t3-oss/env-core@0.12.0':
resolution: {integrity: sha512-lOPj8d9nJJTt81mMuN9GMk8x5veOt7q9m11OSnCBJhwp1QrL/qR+M8Y467ULBSm9SunosryWNbmQQbgoiMgcdw==}
peerDependencies:
@@ -4754,6 +5039,11 @@ packages:
zod:
optional: true
+ '@tailwindcss/forms@0.5.10':
+ resolution: {integrity: sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1'
+
'@tailwindcss/typography@0.5.15':
resolution: {integrity: sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==}
peerDependencies:
@@ -5042,6 +5332,12 @@ packages:
'@types/braces@3.0.4':
resolution: {integrity: sha512-0WR3b8eaISjEW7RpZnclONaLFDf7buaowRHdqLp4vLj54AsSAYWfh3DRbfiYJY9XDxMgx1B4sE1Afw2PGpuHOA==}
+ '@types/chrome@0.0.176':
+ resolution: {integrity: sha512-LOveFOMIUhMJjvRzZv5whGBpncP/gdJ4hcxeAqg94wGi6CyKaCmLgFSofgItf85GuLTl/0BQ6J/Y1e8BqZWfEg==}
+
+ '@types/chrome@0.0.309':
+ resolution: {integrity: sha512-ZFADzcp8b+roUrux68U8pti4cmNOLJXWkShk8lfxj9SBcjYqpJt7NypBprSJUJDJVakGZgd2Tt00QePIGh7oPA==}
+
'@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
@@ -5069,6 +5365,12 @@ packages:
'@types/dom-webcodecs@0.1.11':
resolution: {integrity: sha512-yPEZ3z7EohrmOxbk/QTAa0yonMFkNkjnVXqbGb7D4rMr+F1dGQ8ZUFxXkyLLJuiICPejZ0AZE9Rrk9wUCczx4A==}
+ '@types/eslint-scope@3.7.7':
+ resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
+
+ '@types/eslint@9.6.1':
+ resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==}
+
'@types/estree-jsx@1.0.5':
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
@@ -5084,6 +5386,12 @@ packages:
'@types/file-saver@2.0.7':
resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==}
+ '@types/filesystem@0.0.36':
+ resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==}
+
+ '@types/filewriter@0.0.33':
+ resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==}
+
'@types/fluent-ffmpeg@2.1.26':
resolution: {integrity: sha512-0JVF3wdQG+pN0ImwWD0bNgJiKF2OHg/7CDBHw5UIbRTvlnkgGHK6V5doE54ltvhud4o31/dEiHm23CAlxFiUQg==}
@@ -5093,6 +5401,9 @@ packages:
'@types/graceful-fs@4.1.9':
resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
+ '@types/har-format@1.2.16':
+ resolution: {integrity: sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==}
+
'@types/hast@2.3.10':
resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==}
@@ -5432,6 +5743,15 @@ packages:
peerDependencies:
vinxi: ^0.4.3
+ '@vitejs/plugin-react-swc@3.8.0':
+ resolution: {integrity: sha512-T4sHPvS+DIqDP51ifPqa9XIRAz/kIvIi8oXcnOZZgHmMotgmmdxe/DD5tMFlt5nuIRzT0/QuiwmKlH0503Aapw==}
+ peerDependencies:
+ vite: ^4 || ^5 || ^6
+
+ '@vitejs/plugin-react@1.3.2':
+ resolution: {integrity: sha512-aurBNmMo0kz1O4qRoY+FM4epSA39y3ShWGuqfLRA/3z0oEJAdtoSfgA3aO98/PCCHAqMaduLxIxErWrVKIFzXA==}
+ engines: {node: '>=12.0.0'}
+
'@vitejs/plugin-react@4.3.1':
resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -5485,6 +5805,76 @@ packages:
'@vitest/utils@2.1.9':
resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==}
+ '@webassemblyjs/ast@1.14.1':
+ resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
+
+ '@webassemblyjs/floating-point-hex-parser@1.13.2':
+ resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==}
+
+ '@webassemblyjs/helper-api-error@1.13.2':
+ resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==}
+
+ '@webassemblyjs/helper-buffer@1.14.1':
+ resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==}
+
+ '@webassemblyjs/helper-numbers@1.13.2':
+ resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==}
+
+ '@webassemblyjs/helper-wasm-bytecode@1.13.2':
+ resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==}
+
+ '@webassemblyjs/helper-wasm-section@1.14.1':
+ resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==}
+
+ '@webassemblyjs/ieee754@1.13.2':
+ resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==}
+
+ '@webassemblyjs/leb128@1.13.2':
+ resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==}
+
+ '@webassemblyjs/utf8@1.13.2':
+ resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==}
+
+ '@webassemblyjs/wasm-edit@1.14.1':
+ resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==}
+
+ '@webassemblyjs/wasm-gen@1.14.1':
+ resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==}
+
+ '@webassemblyjs/wasm-opt@1.14.1':
+ resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==}
+
+ '@webassemblyjs/wasm-parser@1.14.1':
+ resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==}
+
+ '@webassemblyjs/wast-printer@1.14.1':
+ resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==}
+
+ '@webpack-cli/configtest@3.0.1':
+ resolution: {integrity: sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==}
+ engines: {node: '>=18.12.0'}
+ peerDependencies:
+ webpack: ^5.82.0
+ webpack-cli: 6.x.x
+
+ '@webpack-cli/info@3.0.1':
+ resolution: {integrity: sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==}
+ engines: {node: '>=18.12.0'}
+ peerDependencies:
+ webpack: ^5.82.0
+ webpack-cli: 6.x.x
+
+ '@webpack-cli/serve@3.0.1':
+ resolution: {integrity: sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==}
+ engines: {node: '>=18.12.0'}
+ peerDependencies:
+ webpack: ^5.82.0
+ webpack-cli: 6.x.x
+ webpack-dev-server: '*'
+ peerDependenciesMeta:
+ webpack-dev-server:
+ optional: true
+
'@workos-inc/node@7.34.0':
resolution: {integrity: sha512-YxUnNQbu40eDrTk+MCsDqwRemp7gH7AhhDRlYY2Oj4i5fvP0nnrg/jxZoKxjO/AayYiVb8tv2quBZzlUXR3WmA==}
engines: {node: '>=16'}
@@ -5493,6 +5883,12 @@ packages:
resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==}
engines: {node: '>=10.0.0'}
+ '@xtuc/ieee754@1.2.0':
+ resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
+
+ '@xtuc/long@4.2.2':
+ resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
+
abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
@@ -5540,11 +5936,6 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
- acorn@8.12.1:
- resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==}
- engines: {node: '>=0.4.0'}
- hasBin: true
-
acorn@8.14.0:
resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
engines: {node: '>=0.4.0'}
@@ -5572,6 +5963,19 @@ packages:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'}
+ ajv-formats@2.1.1:
+ resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
+ peerDependencies:
+ ajv: ^8.0.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+
+ ajv-keywords@5.1.0:
+ resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==}
+ peerDependencies:
+ ajv: ^8.8.2
+
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
@@ -5930,6 +6334,9 @@ packages:
buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
+ builtin-status-codes@3.0.0:
+ resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==}
+
busboy@1.6.0:
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
engines: {node: '>=10.16.0'}
@@ -6071,6 +6478,10 @@ packages:
'@chromatic-com/playwright':
optional: true
+ chrome-trace-event@1.0.4:
+ resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==}
+ engines: {node: '>=6.0'}
+
ci-info@3.9.0:
resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
engines: {node: '>=8'}
@@ -6131,6 +6542,10 @@ packages:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
+ clone-deep@4.0.1:
+ resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==}
+ engines: {node: '>=6'}
+
clone@1.0.4:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
@@ -6191,6 +6606,9 @@ packages:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
+ colorette@2.0.20:
+ resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
+
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
@@ -6202,6 +6620,10 @@ packages:
resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
engines: {node: '>=14'}
+ commander@12.1.0:
+ resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
+ engines: {node: '>=18'}
+
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@@ -6296,6 +6718,12 @@ packages:
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
engines: {node: '>=12.13'}
+ copy-webpack-plugin@13.0.0:
+ resolution: {integrity: sha512-FgR/h5a6hzJqATDGd9YG41SeDViH+0bkHn6WNXCi5zKAZkeESeSxLySSsFLHqLEVCh0E+rITmCf0dusXWYukeQ==}
+ engines: {node: '>= 18.12.0'}
+ peerDependencies:
+ webpack: ^5.1.0
+
core-js-pure@3.38.1:
resolution: {integrity: sha512-BY8Etc1FZqdw1glX0XNOq2FDwfrg/VGqoZOZCdaL+UmdaqDwQwYXkMJT4t6In+zfEfOJDcM9T0KdbBeJg8KKCQ==}
@@ -6309,6 +6737,15 @@ packages:
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
engines: {node: '>= 0.10'}
+ cosmiconfig@9.0.0:
+ resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ typescript: '>=4.9.5'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
crc-32@1.2.2:
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
engines: {node: '>=0.8'}
@@ -6354,6 +6791,18 @@ packages:
uWebSockets.js:
optional: true
+ css-loader@7.1.2:
+ resolution: {integrity: sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==}
+ engines: {node: '>= 18.12.0'}
+ peerDependencies:
+ '@rspack/core': 0.x || 1.x
+ webpack: ^5.27.0
+ peerDependenciesMeta:
+ '@rspack/core':
+ optional: true
+ webpack:
+ optional: true
+
css.escape@1.5.1:
resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
@@ -6804,10 +7253,19 @@ packages:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
+ env-paths@2.2.1:
+ resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
+ engines: {node: '>=6'}
+
env-paths@3.0.0:
resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ envinfo@7.14.0:
+ resolution: {integrity: sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==}
+ engines: {node: '>=4'}
+ hasBin: true
+
error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
@@ -6871,33 +7329,158 @@ packages:
esast-util-from-js@2.0.1:
resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==}
- esbuild-register@3.6.0:
- resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==}
- peerDependencies:
- esbuild: '>=0.12 <1'
+ esbuild-android-64@0.14.54:
+ resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
- esbuild@0.16.4:
- resolution: {integrity: sha512-qQrPMQpPTWf8jHugLWHoGqZjApyx3OEm76dlTXobHwh/EBbavbRdjXdYi/GWr43GyN0sfpap14GPkb05NH3ROA==}
+ esbuild-android-arm64@0.14.54:
+ resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==}
engines: {node: '>=12'}
- hasBin: true
+ cpu: [arm64]
+ os: [android]
- esbuild@0.17.19:
- resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==}
+ esbuild-darwin-64@0.14.54:
+ resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==}
engines: {node: '>=12'}
- hasBin: true
+ cpu: [x64]
+ os: [darwin]
- esbuild@0.18.20:
- resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
+ esbuild-darwin-arm64@0.14.54:
+ resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==}
engines: {node: '>=12'}
- hasBin: true
+ cpu: [arm64]
+ os: [darwin]
- esbuild@0.19.12:
- resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==}
+ esbuild-freebsd-64@0.14.54:
+ resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==}
engines: {node: '>=12'}
- hasBin: true
+ cpu: [x64]
+ os: [freebsd]
- esbuild@0.20.2:
- resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==}
+ esbuild-freebsd-arm64@0.14.54:
+ resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ esbuild-linux-32@0.14.54:
+ resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+
+ esbuild-linux-64@0.14.54:
+ resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+
+ esbuild-linux-arm64@0.14.54:
+ resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+
+ esbuild-linux-arm@0.14.54:
+ resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+
+ esbuild-linux-mips64le@0.14.54:
+ resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+
+ esbuild-linux-ppc64le@0.14.54:
+ resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+
+ esbuild-linux-riscv64@0.14.54:
+ resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+
+ esbuild-linux-s390x@0.14.54:
+ resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+
+ esbuild-netbsd-64@0.14.54:
+ resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+
+ esbuild-openbsd-64@0.14.54:
+ resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+
+ esbuild-register@3.6.0:
+ resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==}
+ peerDependencies:
+ esbuild: '>=0.12 <1'
+
+ esbuild-sunos-64@0.14.54:
+ resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+
+ esbuild-windows-32@0.14.54:
+ resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+
+ esbuild-windows-64@0.14.54:
+ resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+
+ esbuild-windows-arm64@0.14.54:
+ resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+
+ esbuild@0.14.54:
+ resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ esbuild@0.16.4:
+ resolution: {integrity: sha512-qQrPMQpPTWf8jHugLWHoGqZjApyx3OEm76dlTXobHwh/EBbavbRdjXdYi/GWr43GyN0sfpap14GPkb05NH3ROA==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ esbuild@0.17.19:
+ resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ esbuild@0.18.20:
+ resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ esbuild@0.19.12:
+ resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ esbuild@0.20.2:
+ resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==}
engines: {node: '>=12'}
hasBin: true
@@ -7298,6 +7881,10 @@ packages:
resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==}
hasBin: true
+ fastest-levenshtein@1.0.16:
+ resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==}
+ engines: {node: '>= 4.9.1'}
+
fastq@1.17.1:
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
@@ -7310,6 +7897,14 @@ packages:
fd-slicer@1.1.0:
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
+ fdir@6.4.3:
+ resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
fflate@0.4.8:
resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==}
@@ -7354,6 +7949,10 @@ packages:
resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
engines: {node: ^10.12.0 || >=12.0.0}
+ flat@5.0.2:
+ resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
+ hasBin: true
+
flatted@3.3.1:
resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
@@ -7541,6 +8140,9 @@ packages:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
engines: {node: '>=10.13.0'}
+ glob-regex@0.3.2:
+ resolution: {integrity: sha512-m5blUd3/OqDTWwzBBtWBPrGlAzatRywHameHeekAZyZrskYouOGdNB8T/q6JucucvJXtOuyHIn0/Yia7iDasDw==}
+
glob-to-regexp@0.4.1:
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
@@ -7749,6 +8351,9 @@ packages:
hosted-git-info@2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
+ hot-reload-extension-vite@1.0.13:
+ resolution: {integrity: sha512-bX9Bcvhay+xwVinLb/NdoA85NDAixbZ4xcmOyibvHjDjIzw1qllEGfN4LnoAi1jSfH0Mv5Sf5WrbplJS6IaMmg==}
+
html-encoding-sniffer@4.0.0:
resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
engines: {node: '>=18'}
@@ -7822,6 +8427,12 @@ packages:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
+ icss-utils@5.1.0:
+ resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==}
+ engines: {node: ^10 || ^12 || >= 14}
+ peerDependencies:
+ postcss: ^8.1.0
+
ieee754@1.1.13:
resolution: {integrity: sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==}
@@ -7899,6 +8510,10 @@ packages:
resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==}
engines: {node: '>= 0.10'}
+ interpret@3.1.1:
+ resolution: {integrity: sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==}
+ engines: {node: '>=10.13.0'}
+
invariant@2.2.4:
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
@@ -8090,6 +8705,10 @@ packages:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
+ is-plain-object@2.0.4:
+ resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==}
+ engines: {node: '>=0.10.0'}
+
is-plain-object@5.0.0:
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
engines: {node: '>=0.10.0'}
@@ -8192,6 +8811,10 @@ packages:
resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==}
engines: {node: '>=16'}
+ isobject@3.0.1:
+ resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
+ engines: {node: '>=0.10.0'}
+
istanbul-lib-coverage@3.2.2:
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
engines: {node: '>=8'}
@@ -8346,6 +8969,10 @@ packages:
resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-worker@27.5.1:
+ resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
+ engines: {node: '>= 10.13.0'}
+
jest-worker@29.7.0:
resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -8364,8 +8991,8 @@ packages:
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
hasBin: true
- jiti@2.0.0:
- resolution: {integrity: sha512-CJ7e7Abb779OTRv3lomfp7Mns/Sy1+U4pcAx5VbjxCZD5ZM/VJaXPpPjNKjtSvWQy/H86E49REXR34dl1JEz9w==}
+ jiti@2.4.2:
+ resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
hasBin: true
jju@1.4.0:
@@ -8389,6 +9016,9 @@ packages:
engines: {node: '>=14'}
hasBin: true
+ js-confetti@0.12.0:
+ resolution: {integrity: sha512-1R0Akxn3Zn82pMqW65N1V2NwKkZJ75bvBN/VAb36Ya0YHwbaSiAJZVRr/19HBxH/O8x2x01UFAbYI18VqlDN6g==}
+
js-cookie@3.0.5:
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
engines: {node: '>=14'}
@@ -8431,6 +9061,11 @@ packages:
engines: {node: '>=4'}
hasBin: true
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
@@ -8522,12 +9157,76 @@ packages:
lie@3.3.0:
resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
+ lightningcss-darwin-arm64@1.29.2:
+ resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.29.2:
+ resolution: {integrity: sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.29.2:
+ resolution: {integrity: sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.29.2:
+ resolution: {integrity: sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.29.2:
+ resolution: {integrity: sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.29.2:
+ resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.29.2:
+ resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.29.2:
+ resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.29.2:
+ resolution: {integrity: sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.29.2:
+ resolution: {integrity: sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.29.2:
+ resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==}
+ engines: {node: '>= 12.0.0'}
+
lilconfig@2.1.0:
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
engines: {node: '>=10'}
- lilconfig@3.1.2:
- resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==}
+ lilconfig@3.1.3:
+ resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
engines: {node: '>=14'}
lines-and-columns@1.2.4:
@@ -8540,6 +9239,10 @@ packages:
load-script@1.0.0:
resolution: {integrity: sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==}
+ loader-runner@4.3.0:
+ resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
+ engines: {node: '>=6.11.5'}
+
local-pkg@0.5.0:
resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
engines: {node: '>=14'}
@@ -9081,6 +9784,10 @@ packages:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
+ mini-svg-data-uri@1.4.4:
+ resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
+ hasBin: true
+
miniflare@3.20250204.1:
resolution: {integrity: sha512-B4PQi/Ai4d0ZTWahQwsFe5WAfr1j8ISMYxJZTc56g2/btgbX+Go099LmojAZY/fMRLhIYsglcStW8SeW3f/afA==}
engines: {node: '>=16.13'}
@@ -9204,6 +9911,11 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
+ nanoid@3.3.9:
+ resolution: {integrity: sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
nanoid@5.0.7:
resolution: {integrity: sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==}
engines: {node: ^18 || >=20}
@@ -9653,10 +10365,17 @@ packages:
picocolors@1.1.0:
resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==}
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
+ picomatch@4.0.2:
+ resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
+ engines: {node: '>=12'}
+
pify@2.3.0:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
@@ -9718,6 +10437,43 @@ packages:
ts-node:
optional: true
+ postcss-loader@8.1.1:
+ resolution: {integrity: sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==}
+ engines: {node: '>= 18.12.0'}
+ peerDependencies:
+ '@rspack/core': 0.x || 1.x
+ postcss: ^7.0.0 || ^8.0.1
+ webpack: ^5.0.0
+ peerDependenciesMeta:
+ '@rspack/core':
+ optional: true
+ webpack:
+ optional: true
+
+ postcss-modules-extract-imports@3.1.0:
+ resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==}
+ engines: {node: ^10 || ^12 || >= 14}
+ peerDependencies:
+ postcss: ^8.1.0
+
+ postcss-modules-local-by-default@4.2.0:
+ resolution: {integrity: sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==}
+ engines: {node: ^10 || ^12 || >= 14}
+ peerDependencies:
+ postcss: ^8.1.0
+
+ postcss-modules-scope@3.2.1:
+ resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==}
+ engines: {node: ^10 || ^12 || >= 14}
+ peerDependencies:
+ postcss: ^8.1.0
+
+ postcss-modules-values@4.0.0:
+ resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==}
+ engines: {node: ^10 || ^12 || >= 14}
+ peerDependencies:
+ postcss: ^8.1.0
+
postcss-nested@6.2.0:
resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
engines: {node: '>=12.0'}
@@ -9738,6 +10494,10 @@ packages:
resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
engines: {node: '>=4'}
+ postcss-selector-parser@7.1.0:
+ resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==}
+ engines: {node: '>=4'}
+
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
@@ -9749,6 +10509,10 @@ packages:
resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==}
engines: {node: ^10 || ^12 || >=14}
+ postcss@8.5.3:
+ resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
+ engines: {node: ^10 || ^12 || >=14}
+
posthog-js@1.215.3:
resolution: {integrity: sha512-vTk8/gyjbKP7EbDxWzo/GBCK7Ok7M6RTqEWOzRgIxCPf/KA5faFi5z1T4cRR1oPgcDqLeB1ZGa04Za/cPEHxgA==}
@@ -9839,6 +10603,9 @@ packages:
punycode@1.3.2:
resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==}
+ punycode@1.4.1:
+ resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==}
+
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -9921,6 +10688,11 @@ packages:
peerDependencies:
react: ^18.3.1
+ react-dom@19.0.0:
+ resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==}
+ peerDependencies:
+ react: ^19.0.0
+
react-draggable@4.4.6:
resolution: {integrity: sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==}
peerDependencies:
@@ -9935,6 +10707,13 @@ packages:
react-fast-compare@3.2.2:
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
+ react-frame-component@5.2.7:
+ resolution: {integrity: sha512-ROjHtSLoSVYUBfTieazj/nL8jIX9rZFmHC0yXEU+dx6Y82OcBEGgU9o7VyHMrBFUN9FuQ849MtIPNNLsb4krbg==}
+ peerDependencies:
+ prop-types: ^15.5.9
+ react: '>= 16.3'
+ react-dom: '>= 16.3'
+
react-hls-player@3.0.7:
resolution: {integrity: sha512-i5QWNyLmaUhV/mgnpljRJT0CBfJnylClV/bne8aiXO3ZqU0+D3U/jtTDwdXM4i5qHhyFy9lemyZ179IgadKd0Q==}
peerDependencies:
@@ -9976,6 +10755,10 @@ packages:
react-promise-suspense@0.3.4:
resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==}
+ react-refresh@0.13.0:
+ resolution: {integrity: sha512-XP8A9BT0CpRBD+NYLLeIhld/RqG9+gktUjW1FkE+Vm7OCinbG1SshcK5tb9ls4kzvjZr9mOQc7HYgBngEyPAXg==}
+ engines: {node: '>=0.10.0'}
+
react-refresh@0.14.2:
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
engines: {node: '>=0.10.0'}
@@ -10060,6 +10843,10 @@ packages:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
+ react@19.0.0:
+ resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
+ engines: {node: '>=0.10.0'}
+
read-cache@1.0.0:
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
@@ -10097,6 +10884,10 @@ packages:
resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
engines: {node: '>= 0.10'}
+ rechoir@0.8.0:
+ resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==}
+ engines: {node: '>= 10.13.0'}
+
recma-build-jsx@1.0.0:
resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==}
@@ -10109,6 +10900,9 @@ packages:
recma-stringify@1.0.0:
resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==}
+ recrawl-sync@2.2.3:
+ resolution: {integrity: sha512-vSaTR9t+cpxlskkdUFrsEpnf67kSmPk66yAGT1fZPrDudxQjoMzPgQhSMImQ0pAw5k0NPirefQfhopSjhdUtpQ==}
+
redent@3.0.0:
resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
engines: {node: '>=8'}
@@ -10261,6 +11055,11 @@ packages:
rollup-pluginutils@2.8.2:
resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==}
+ rollup@2.77.3:
+ resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==}
+ engines: {node: '>=10.0.0'}
+ hasBin: true
+
rollup@3.29.5:
resolution: {integrity: sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==}
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
@@ -10319,6 +11118,13 @@ packages:
scheduler@0.23.2:
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
+ scheduler@0.25.0:
+ resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==}
+
+ schema-utils@4.3.0:
+ resolution: {integrity: sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==}
+ engines: {node: '>= 10.13.0'}
+
scule@1.3.0:
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
@@ -10392,6 +11198,10 @@ packages:
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+ shallow-clone@3.0.1:
+ resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==}
+ engines: {node: '>=8'}
+
sharp@0.33.5:
resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -10404,6 +11214,10 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
+ shell-quote@1.8.2:
+ resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==}
+ engines: {node: '>= 0.4'}
+
shelljs@0.8.5:
resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==}
engines: {node: '>=4'}
@@ -10613,6 +11427,9 @@ packages:
resolution: {integrity: sha512-FG2KAVQN54T9R6voudiEftehtkXtLO+YVGP2gBPfacEdDQjY++ld7kTbHzpTT/bpCDx7Yq3dqOegLm9arVJfYw==}
hasBin: true
+ stream-http@3.2.0:
+ resolution: {integrity: sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==}
+
streamsearch@1.1.0:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'}
@@ -10716,6 +11533,12 @@ packages:
strnum@1.0.5:
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
+ style-loader@4.0.0:
+ resolution: {integrity: sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==}
+ engines: {node: '>= 18.12.0'}
+ peerDependencies:
+ webpack: ^5.27.0
+
style-to-object@0.3.0:
resolution: {integrity: sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==}
@@ -10816,6 +11639,14 @@ packages:
engines: {node: '>=14.0.0'}
hasBin: true
+ tailwindcss@3.4.16:
+ resolution: {integrity: sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+
+ tailwindcss@4.0.13:
+ resolution: {integrity: sha512-gbvFrB0fOsTv/OugXWi2PtflJ4S6/ctu6Mmn3bCftmLY/6xRsQVEJPgIIpABwpZ52DpONkCA3bEj5b54MHxF2Q==}
+
tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'}
@@ -10842,6 +11673,22 @@ packages:
peerDependencies:
solid-js: ^1.8
+ terser-webpack-plugin@5.3.14:
+ resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==}
+ engines: {node: '>= 10.13.0'}
+ peerDependencies:
+ '@swc/core': '*'
+ esbuild: '*'
+ uglify-js: '*'
+ webpack: ^5.1.0
+ peerDependenciesMeta:
+ '@swc/core':
+ optional: true
+ esbuild:
+ optional: true
+ uglify-js:
+ optional: true
+
terser@5.34.0:
resolution: {integrity: sha512-y5NUX+U9HhVsK/zihZwoq4r9dICLyV2jXGOriDAVOeKhq3LKVjgJbGO90FisozXLlJfvjHqgckGmJFBb9KYoWQ==}
engines: {node: '>=10'}
@@ -10892,6 +11739,10 @@ packages:
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
+ tinyglobby@0.2.12:
+ resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
+ engines: {node: '>=12.0.0'}
+
tinygradient@1.1.5:
resolution: {integrity: sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw==}
@@ -11012,6 +11863,13 @@ packages:
esbuild:
optional: true
+ ts-loader@9.5.2:
+ resolution: {integrity: sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ typescript: '*'
+ webpack: ^5.0.0
+
ts-node@10.9.2:
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
hasBin: true
@@ -11045,6 +11903,10 @@ packages:
tsconfig-paths@3.15.0:
resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
+ tsconfig-paths@4.2.0:
+ resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==}
+ engines: {node: '>=6'}
+
tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
@@ -11172,6 +12034,11 @@ packages:
resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==}
engines: {node: '>= 0.4'}
+ typescript@4.9.5:
+ resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
+ engines: {node: '>=4.2.0'}
+ hasBin: true
+
typescript@5.7.2:
resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==}
engines: {node: '>=14.17'}
@@ -11421,6 +12288,10 @@ packages:
url@0.10.3:
resolution: {integrity: sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==}
+ url@0.11.4:
+ resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==}
+ engines: {node: '>= 0.4'}
+
urlpattern-polyfill@8.0.2:
resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==}
@@ -11557,6 +12428,11 @@ packages:
'@testing-library/jest-dom':
optional: true
+ vite-tsconfig-paths@3.6.0:
+ resolution: {integrity: sha512-UfsPYonxLqPD633X8cWcPFVuYzx/CMNHAjZTasYwX69sXpa4gNmQkR0XCjj82h7zhLGdTWagMjC1qfb9S+zv0A==}
+ peerDependencies:
+ vite: '>2.0.0-0'
+
vite-tsconfig-paths@4.3.2:
resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==}
peerDependencies:
@@ -11573,6 +12449,22 @@ packages:
vite:
optional: true
+ vite@2.9.18:
+ resolution: {integrity: sha512-sAOqI5wNM9QvSEE70W3UGMdT8cyEn0+PmJMTFvTB8wB0YbYUWw3gUbY62AOyrXosGieF2htmeLATvNxpv/zNyQ==}
+ engines: {node: '>=12.2.0'}
+ hasBin: true
+ peerDependencies:
+ less: '*'
+ sass: '*'
+ stylus: '*'
+ peerDependenciesMeta:
+ less:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+
vite@4.5.5:
resolution: {integrity: sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -11601,8 +12493,8 @@ packages:
terser:
optional: true
- vite@5.4.8:
- resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==}
+ vite@5.4.14:
+ resolution: {integrity: sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
@@ -11632,8 +12524,39 @@ packages:
terser:
optional: true
- vitefu@0.2.5:
- resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==}
+ vite@5.4.8:
+ resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || >=20.0.0
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+
+ vitefu@0.2.5:
+ resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==}
peerDependencies:
vite: ^3.0.0 || ^4.0.0 || ^5.0.0
peerDependenciesMeta:
@@ -11672,6 +12595,10 @@ packages:
walker@1.0.8:
resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
+ watchpack@2.4.2:
+ resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==}
+ engines: {node: '>=10.13.0'}
+
wcwidth@1.0.1:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
@@ -11699,9 +12626,41 @@ packages:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'}
+ webpack-cli@6.0.1:
+ resolution: {integrity: sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==}
+ engines: {node: '>=18.12.0'}
+ hasBin: true
+ peerDependencies:
+ webpack: ^5.82.0
+ webpack-bundle-analyzer: '*'
+ webpack-dev-server: '*'
+ peerDependenciesMeta:
+ webpack-bundle-analyzer:
+ optional: true
+ webpack-dev-server:
+ optional: true
+
+ webpack-merge@6.0.1:
+ resolution: {integrity: sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==}
+ engines: {node: '>=18.0.0'}
+
+ webpack-sources@3.2.3:
+ resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
+ engines: {node: '>=10.13.0'}
+
webpack-virtual-modules@0.6.2:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
+ webpack@5.98.0:
+ resolution: {integrity: sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==}
+ engines: {node: '>=10.13.0'}
+ hasBin: true
+ peerDependencies:
+ webpack-cli: '*'
+ peerDependenciesMeta:
+ webpack-cli:
+ optional: true
+
whatwg-encoding@3.1.1:
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
engines: {node: '>=18'}
@@ -11758,6 +12717,9 @@ packages:
resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==}
engines: {node: '>=12'}
+ wildcard@2.0.1:
+ resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==}
+
word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
@@ -11826,6 +12788,10 @@ packages:
xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+ xtend@4.0.2:
+ resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
+ engines: {node: '>=0.4'}
+
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@@ -11836,11 +12802,6 @@ packages:
yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
- yaml@2.5.1:
- resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==}
- engines: {node: '>= 14'}
- hasBin: true
-
yaml@2.7.0:
resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==}
engines: {node: '>= 14'}
@@ -11882,6 +12843,24 @@ packages:
zod@3.24.1:
resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==}
+ zustand@5.0.3:
+ resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
+ engines: {node: '>=12.20.0'}
+ peerDependencies:
+ '@types/react': '>=18.0.0'
+ immer: '>=9.0.6'
+ react: '>=18.0.0'
+ use-sync-external-store: '>=1.2.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+ use-sync-external-store:
+ optional: true
+
zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
@@ -12532,7 +13511,13 @@ snapshots:
'@babel/code-frame@7.24.7':
dependencies:
'@babel/highlight': 7.24.7
- picocolors: 1.1.0
+ picocolors: 1.1.1
+
+ '@babel/code-frame@7.26.2':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.25.9
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
'@babel/compat-data@7.25.4': {}
@@ -12563,6 +13548,18 @@ snapshots:
'@jridgewell/trace-mapping': 0.3.25
jsesc: 2.5.2
+ '@babel/generator@7.26.10':
+ dependencies:
+ '@babel/parser': 7.26.10
+ '@babel/types': 7.26.10
+ '@jridgewell/gen-mapping': 0.3.5
+ '@jridgewell/trace-mapping': 0.3.25
+ jsesc: 3.1.0
+
+ '@babel/helper-annotate-as-pure@7.25.9':
+ dependencies:
+ '@babel/types': 7.26.10
+
'@babel/helper-compilation-targets@7.25.2':
dependencies:
'@babel/compat-data': 7.25.4
@@ -12582,6 +13579,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@babel/helper-module-imports@7.25.9':
+ dependencies:
+ '@babel/traverse': 7.26.10
+ '@babel/types': 7.26.10
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/helper-module-transforms@7.25.2(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
@@ -12594,6 +13598,8 @@ snapshots:
'@babel/helper-plugin-utils@7.24.8': {}
+ '@babel/helper-plugin-utils@7.26.5': {}
+
'@babel/helper-simple-access@7.24.7':
dependencies:
'@babel/traverse': 7.25.6
@@ -12603,8 +13609,12 @@ snapshots:
'@babel/helper-string-parser@7.24.8': {}
+ '@babel/helper-string-parser@7.25.9': {}
+
'@babel/helper-validator-identifier@7.24.7': {}
+ '@babel/helper-validator-identifier@7.25.9': {}
+
'@babel/helper-validator-option@7.24.8': {}
'@babel/helpers@7.25.6':
@@ -12617,12 +13627,16 @@ snapshots:
'@babel/helper-validator-identifier': 7.24.7
chalk: 2.4.2
js-tokens: 4.0.0
- picocolors: 1.1.0
+ picocolors: 1.1.1
'@babel/parser@7.25.6':
dependencies:
'@babel/types': 7.25.6
+ '@babel/parser@7.26.10':
+ dependencies:
+ '@babel/types': 7.26.10
+
'@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
@@ -12663,6 +13677,11 @@ snapshots:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.24.8
+ '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.25.2)':
+ dependencies:
+ '@babel/core': 7.25.2
+ '@babel/helper-plugin-utils': 7.26.5
+
'@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
@@ -12708,6 +13727,13 @@ snapshots:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.24.8
+ '@babel/plugin-transform-react-jsx-development@7.25.9(@babel/core@7.25.2)':
+ dependencies:
+ '@babel/core': 7.25.2
+ '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.25.2)
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
@@ -12718,6 +13744,17 @@ snapshots:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.24.8
+ '@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.25.2)':
+ dependencies:
+ '@babel/core': 7.25.2
+ '@babel/helper-annotate-as-pure': 7.25.9
+ '@babel/helper-module-imports': 7.25.9
+ '@babel/helper-plugin-utils': 7.26.5
+ '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.25.2)
+ '@babel/types': 7.26.10
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/runtime-corejs3@7.25.6':
dependencies:
core-js-pure: 3.38.1
@@ -12733,6 +13770,12 @@ snapshots:
'@babel/parser': 7.25.6
'@babel/types': 7.25.6
+ '@babel/template@7.26.9':
+ dependencies:
+ '@babel/code-frame': 7.26.2
+ '@babel/parser': 7.26.10
+ '@babel/types': 7.26.10
+
'@babel/traverse@7.25.6':
dependencies:
'@babel/code-frame': 7.24.7
@@ -12745,12 +13788,29 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@babel/traverse@7.26.10':
+ dependencies:
+ '@babel/code-frame': 7.26.2
+ '@babel/generator': 7.26.10
+ '@babel/parser': 7.26.10
+ '@babel/template': 7.26.9
+ '@babel/types': 7.26.10
+ debug: 4.3.7(supports-color@5.5.0)
+ globals: 11.12.0
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/types@7.25.6':
dependencies:
'@babel/helper-string-parser': 7.24.8
'@babel/helper-validator-identifier': 7.24.7
to-fast-properties: 2.0.0
+ '@babel/types@7.26.10':
+ dependencies:
+ '@babel/helper-string-parser': 7.25.9
+ '@babel/helper-validator-identifier': 7.25.9
+
'@bcoe/v8-coverage@0.2.3': {}
'@chromatic-com/storybook@1.9.0(react@18.3.1)':
@@ -12769,7 +13829,7 @@ snapshots:
dependencies:
mime: 3.0.0
- '@cloudflare/vitest-pool-workers@0.6.16(@cloudflare/workers-types@4.20250214.0)(@vitest/runner@2.1.9)(@vitest/snapshot@2.1.9)(vitest@2.1.9(@types/node@22.12.0)(jsdom@25.0.1)(terser@5.34.0))':
+ '@cloudflare/vitest-pool-workers@0.6.16(@cloudflare/workers-types@4.20250214.0)(@vitest/runner@2.1.9)(@vitest/snapshot@2.1.9)(vitest@2.1.9(@types/node@22.12.0)(jsdom@25.0.1)(lightningcss@1.29.2)(terser@5.34.0))':
dependencies:
'@vitest/runner': 2.1.9
'@vitest/snapshot': 2.1.9
@@ -12779,7 +13839,7 @@ snapshots:
esbuild: 0.17.19
miniflare: 3.20250204.1
semver: 7.6.3
- vitest: 2.1.9(@types/node@22.12.0)(jsdom@25.0.1)(terser@5.34.0)
+ vitest: 2.1.9(@types/node@22.12.0)(jsdom@25.0.1)(lightningcss@1.29.2)(terser@5.34.0)
wrangler: 3.109.1(@cloudflare/workers-types@4.20250214.0)
zod: 3.24.1
transitivePeerDependencies:
@@ -12922,6 +13982,8 @@ snapshots:
dependencies:
'@jridgewell/trace-mapping': 0.3.9
+ '@cush/relative@1.0.0': {}
+
'@deepgram/captions@1.2.0':
dependencies:
dayjs: 1.11.13
@@ -12945,6 +14007,8 @@ snapshots:
'@deno/shim-deno-test': 0.5.0
which: 4.0.0
+ '@discoveryjs/json-ext@0.6.3': {}
+
'@effect-ts/core@0.60.5':
dependencies:
'@effect-ts/system': 0.57.5
@@ -13224,6 +14288,9 @@ snapshots:
'@esbuild/linux-ia32@0.23.1':
optional: true
+ '@esbuild/linux-loong64@0.14.54':
+ optional: true
+
'@esbuild/linux-loong64@0.16.4':
optional: true
@@ -13770,7 +14837,7 @@ snapshots:
jest-util: 29.7.0
slash: 3.0.0
- '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))':
+ '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))':
dependencies:
'@jest/console': 29.7.0
'@jest/reporters': 29.7.0
@@ -13784,7 +14851,7 @@ snapshots:
exit: 0.1.2
graceful-fs: 4.2.11
jest-changed-files: 29.7.0
- jest-config: 29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))
+ jest-config: 29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
jest-haste-map: 29.7.0
jest-message-util: 29.7.0
jest-regex-util: 29.6.3
@@ -13985,13 +15052,13 @@ snapshots:
solid-presence: 0.1.8(solid-js@1.9.3)
solid-prevent-scroll: 0.1.10(solid-js@1.9.3)
- '@kobalte/tailwindcss@0.9.0(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2)))':
+ '@kobalte/tailwindcss@0.9.0(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)))':
dependencies:
- tailwindcss: 3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))
+ tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
- '@kobalte/tailwindcss@0.9.0(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@22.12.0)(typescript@5.7.2)))':
+ '@kobalte/tailwindcss@0.9.0(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2)))':
dependencies:
- tailwindcss: 3.4.13(ts-node@10.9.2(@types/node@22.12.0)(typescript@5.7.2))
+ tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2))
'@kobalte/utils@0.9.1(solid-js@1.9.3)':
dependencies:
@@ -14012,7 +15079,7 @@ snapshots:
'@manypkg/tools@1.1.2':
dependencies:
- fast-glob: 3.3.2
+ fast-glob: 3.3.3
jju: 1.4.0
js-yaml: 4.1.0
@@ -15797,11 +16864,11 @@ snapshots:
dependencies:
solid-js: 1.9.3
- '@solidjs/start@1.0.7(@testing-library/jest-dom@6.5.0)(solid-js@1.9.3)(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(terser@5.34.0)(xml2js@0.6.2))(vite@5.4.8(@types/node@22.12.0)(terser@5.34.0))':
+ '@solidjs/start@1.0.7(@testing-library/jest-dom@6.5.0)(solid-js@1.9.3)(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(lightningcss@1.29.2)(terser@5.34.0)(webpack-sources@3.2.3)(xml2js@0.6.2))(vite@5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0))':
dependencies:
- '@vinxi/plugin-directives': 0.4.3(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(terser@5.34.0)(xml2js@0.6.2))
- '@vinxi/server-components': 0.4.3(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(terser@5.34.0)(xml2js@0.6.2))
- '@vinxi/server-functions': 0.4.3(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(terser@5.34.0)(xml2js@0.6.2))
+ '@vinxi/plugin-directives': 0.4.3(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(lightningcss@1.29.2)(terser@5.34.0)(webpack-sources@3.2.3)(xml2js@0.6.2))
+ '@vinxi/server-components': 0.4.3(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(lightningcss@1.29.2)(terser@5.34.0)(webpack-sources@3.2.3)(xml2js@0.6.2))
+ '@vinxi/server-functions': 0.4.3(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(lightningcss@1.29.2)(terser@5.34.0)(webpack-sources@3.2.3)(xml2js@0.6.2))
defu: 6.1.4
error-stack-parser: 2.1.4
glob: 10.4.5
@@ -15812,7 +16879,7 @@ snapshots:
shikiji: 0.9.19
source-map-js: 1.2.1
terracotta: 1.0.6(solid-js@1.9.3)
- vite-plugin-solid: 2.10.2(@testing-library/jest-dom@6.5.0)(solid-js@1.9.3)(vite@5.4.8(@types/node@22.12.0)(terser@5.34.0))
+ vite-plugin-solid: 2.10.2(@testing-library/jest-dom@6.5.0)(solid-js@1.9.3)(vite@5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0))
transitivePeerDependencies:
- '@testing-library/jest-dom'
- bufferutil
@@ -15942,13 +17009,13 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
- '@storybook/builder-vite@9.0.0-alpha.2(storybook@8.3.3)(vite@5.4.8(@types/node@22.12.0)(terser@5.34.0))':
+ '@storybook/builder-vite@9.0.0-alpha.4(storybook@8.3.3)(vite@5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0))':
dependencies:
- '@storybook/csf-plugin': 9.0.0-alpha.2(storybook@8.3.3)
+ '@storybook/csf-plugin': 9.0.0-alpha.4(storybook@8.3.3)
browser-assert: 1.2.1
storybook: 8.3.3
ts-dedent: 2.2.0
- vite: 5.4.8(@types/node@22.12.0)(terser@5.34.0)
+ vite: 5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0)
'@storybook/core@8.3.3':
dependencies:
@@ -15975,7 +17042,7 @@ snapshots:
storybook: 8.3.3
unplugin: 1.16.1
- '@storybook/csf-plugin@9.0.0-alpha.2(storybook@8.3.3)':
+ '@storybook/csf-plugin@9.0.0-alpha.4(storybook@8.3.3)':
dependencies:
storybook: 8.3.3
unplugin: 1.16.1
@@ -16029,17 +17096,68 @@ snapshots:
'@stripe/stripe-js@3.5.0': {}
+ '@swc/core-darwin-arm64@1.11.8':
+ optional: true
+
+ '@swc/core-darwin-x64@1.11.8':
+ optional: true
+
+ '@swc/core-linux-arm-gnueabihf@1.11.8':
+ optional: true
+
+ '@swc/core-linux-arm64-gnu@1.11.8':
+ optional: true
+
+ '@swc/core-linux-arm64-musl@1.11.8':
+ optional: true
+
+ '@swc/core-linux-x64-gnu@1.11.8':
+ optional: true
+
+ '@swc/core-linux-x64-musl@1.11.8':
+ optional: true
+
+ '@swc/core-win32-arm64-msvc@1.11.8':
+ optional: true
+
+ '@swc/core-win32-ia32-msvc@1.11.8':
+ optional: true
+
+ '@swc/core-win32-x64-msvc@1.11.8':
+ optional: true
+
+ '@swc/core@1.11.8(@swc/helpers@0.5.13)':
+ dependencies:
+ '@swc/counter': 0.1.3
+ '@swc/types': 0.1.19
+ optionalDependencies:
+ '@swc/core-darwin-arm64': 1.11.8
+ '@swc/core-darwin-x64': 1.11.8
+ '@swc/core-linux-arm-gnueabihf': 1.11.8
+ '@swc/core-linux-arm64-gnu': 1.11.8
+ '@swc/core-linux-arm64-musl': 1.11.8
+ '@swc/core-linux-x64-gnu': 1.11.8
+ '@swc/core-linux-x64-musl': 1.11.8
+ '@swc/core-win32-arm64-msvc': 1.11.8
+ '@swc/core-win32-ia32-msvc': 1.11.8
+ '@swc/core-win32-x64-msvc': 1.11.8
+ '@swc/helpers': 0.5.13
+
'@swc/counter@0.1.3': {}
'@swc/helpers@0.5.13':
dependencies:
- tslib: 2.8.0
+ tslib: 2.8.1
'@swc/helpers@0.5.5':
dependencies:
'@swc/counter': 0.1.3
tslib: 2.8.0
+ '@swc/types@0.1.19':
+ dependencies:
+ '@swc/counter': 0.1.3
+
'@t3-oss/env-core@0.12.0(typescript@5.7.2)(valibot@1.0.0-rc.1(typescript@5.7.2))(zod@3.24.1)':
optionalDependencies:
typescript: 5.7.2
@@ -16054,21 +17172,26 @@ snapshots:
valibot: 1.0.0-rc.1(typescript@5.7.2)
zod: 3.24.1
- '@tailwindcss/typography@0.5.15(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2)))':
+ '@tailwindcss/forms@0.5.10(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)))':
+ dependencies:
+ mini-svg-data-uri: 1.4.4
+ tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
+
+ '@tailwindcss/typography@0.5.15(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)))':
dependencies:
lodash.castarray: 4.4.0
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
postcss-selector-parser: 6.0.10
- tailwindcss: 3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))
+ tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
- '@tailwindcss/typography@0.5.15(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@22.12.0)(typescript@5.7.2)))':
+ '@tailwindcss/typography@0.5.15(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2)))':
dependencies:
lodash.castarray: 4.4.0
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
postcss-selector-parser: 6.0.10
- tailwindcss: 3.4.13(ts-node@10.9.2(@types/node@22.12.0)(typescript@5.7.2))
+ tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2))
'@tanstack/query-core@5.56.2': {}
@@ -16295,7 +17418,7 @@ snapshots:
'@tsconfig/node16@1.0.4': {}
- '@turbo/gen@1.13.4(@types/node@22.12.0)(typescript@5.7.2)':
+ '@turbo/gen@1.13.4(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2)':
dependencies:
'@turbo/workspaces': 1.13.4
chalk: 2.4.2
@@ -16305,7 +17428,7 @@ snapshots:
minimatch: 9.0.5
node-plop: 0.26.3
proxy-agent: 6.4.0
- ts-node: 10.9.2(@types/node@22.12.0)(typescript@5.7.2)
+ ts-node: 10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2)
update-check: 1.5.4
validate-npm-package-name: 5.0.1
transitivePeerDependencies:
@@ -16368,6 +17491,16 @@ snapshots:
'@types/braces@3.0.4': {}
+ '@types/chrome@0.0.176':
+ dependencies:
+ '@types/filesystem': 0.0.36
+ '@types/har-format': 1.2.16
+
+ '@types/chrome@0.0.309':
+ dependencies:
+ '@types/filesystem': 0.0.36
+ '@types/har-format': 1.2.16
+
'@types/connect@3.4.38':
dependencies:
'@types/node': 20.16.9
@@ -16397,6 +17530,16 @@ snapshots:
'@types/dom-webcodecs@0.1.11': {}
+ '@types/eslint-scope@3.7.7':
+ dependencies:
+ '@types/eslint': 9.6.1
+ '@types/estree': 1.0.6
+
+ '@types/eslint@9.6.1':
+ dependencies:
+ '@types/estree': 1.0.6
+ '@types/json-schema': 7.0.15
+
'@types/estree-jsx@1.0.5':
dependencies:
'@types/estree': 1.0.6
@@ -16419,6 +17562,12 @@ snapshots:
'@types/file-saver@2.0.7': {}
+ '@types/filesystem@0.0.36':
+ dependencies:
+ '@types/filewriter': 0.0.33
+
+ '@types/filewriter@0.0.33': {}
+
'@types/fluent-ffmpeg@2.1.26':
dependencies:
'@types/node': 20.16.9
@@ -16432,6 +17581,8 @@ snapshots:
dependencies:
'@types/node': 20.16.9
+ '@types/har-format@1.2.16': {}
+
'@types/hast@2.3.10':
dependencies:
'@types/unist': 2.0.11
@@ -16556,9 +17707,9 @@ snapshots:
dependencies:
'@types/react': 18.3.9
- '@types/react-tooltip@4.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@types/react-tooltip@4.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
- react-tooltip: 5.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react-tooltip: 5.28.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
transitivePeerDependencies:
- react
- react-dom
@@ -16866,49 +18017,69 @@ snapshots:
transitivePeerDependencies:
- uWebSockets.js
- '@vinxi/plugin-directives@0.4.3(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(terser@5.34.0)(xml2js@0.6.2))':
+ '@vinxi/plugin-directives@0.4.3(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(lightningcss@1.29.2)(terser@5.34.0)(webpack-sources@3.2.3)(xml2js@0.6.2))':
dependencies:
'@babel/parser': 7.25.6
- acorn: 8.12.1
- acorn-jsx: 5.3.2(acorn@8.12.1)
+ acorn: 8.14.0
+ acorn-jsx: 5.3.2(acorn@8.14.0)
acorn-loose: 8.4.0
- acorn-typescript: 1.4.13(acorn@8.12.1)
+ acorn-typescript: 1.4.13(acorn@8.14.0)
astring: 1.9.0
magicast: 0.2.11
recast: 0.23.9
tslib: 2.8.0
- vinxi: 0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(terser@5.34.0)(xml2js@0.6.2)
+ vinxi: 0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(lightningcss@1.29.2)(terser@5.34.0)(webpack-sources@3.2.3)(xml2js@0.6.2)
- '@vinxi/server-components@0.4.3(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(terser@5.34.0)(xml2js@0.6.2))':
+ '@vinxi/server-components@0.4.3(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(lightningcss@1.29.2)(terser@5.34.0)(webpack-sources@3.2.3)(xml2js@0.6.2))':
dependencies:
- '@vinxi/plugin-directives': 0.4.3(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(terser@5.34.0)(xml2js@0.6.2))
- acorn: 8.12.1
+ '@vinxi/plugin-directives': 0.4.3(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(lightningcss@1.29.2)(terser@5.34.0)(webpack-sources@3.2.3)(xml2js@0.6.2))
+ acorn: 8.14.0
acorn-loose: 8.4.0
- acorn-typescript: 1.4.13(acorn@8.12.1)
+ acorn-typescript: 1.4.13(acorn@8.14.0)
astring: 1.9.0
magicast: 0.2.11
recast: 0.23.9
- vinxi: 0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(terser@5.34.0)(xml2js@0.6.2)
+ vinxi: 0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(lightningcss@1.29.2)(terser@5.34.0)(webpack-sources@3.2.3)(xml2js@0.6.2)
- '@vinxi/server-functions@0.4.3(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(terser@5.34.0)(xml2js@0.6.2))':
+ '@vinxi/server-functions@0.4.3(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(lightningcss@1.29.2)(terser@5.34.0)(webpack-sources@3.2.3)(xml2js@0.6.2))':
dependencies:
- '@vinxi/plugin-directives': 0.4.3(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(terser@5.34.0)(xml2js@0.6.2))
- acorn: 8.12.1
+ '@vinxi/plugin-directives': 0.4.3(vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(lightningcss@1.29.2)(terser@5.34.0)(webpack-sources@3.2.3)(xml2js@0.6.2))
+ acorn: 8.14.0
acorn-loose: 8.4.0
- acorn-typescript: 1.4.13(acorn@8.12.1)
+ acorn-typescript: 1.4.13(acorn@8.14.0)
astring: 1.9.0
magicast: 0.2.11
recast: 0.23.9
- vinxi: 0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(terser@5.34.0)(xml2js@0.6.2)
+ vinxi: 0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(lightningcss@1.29.2)(terser@5.34.0)(webpack-sources@3.2.3)(xml2js@0.6.2)
+
+ '@vitejs/plugin-react-swc@3.8.0(@swc/helpers@0.5.13)(vite@5.4.14(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0))':
+ dependencies:
+ '@swc/core': 1.11.8(@swc/helpers@0.5.13)
+ vite: 5.4.14(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0)
+ transitivePeerDependencies:
+ - '@swc/helpers'
+
+ '@vitejs/plugin-react@1.3.2':
+ dependencies:
+ '@babel/core': 7.25.2
+ '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.25.2)
+ '@babel/plugin-transform-react-jsx-development': 7.25.9(@babel/core@7.25.2)
+ '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2)
+ '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2)
+ '@rollup/pluginutils': 4.2.1
+ react-refresh: 0.13.0
+ resolve: 1.22.8
+ transitivePeerDependencies:
+ - supports-color
- '@vitejs/plugin-react@4.3.1(vite@4.5.5(@types/node@20.16.9)(terser@5.34.0))':
+ '@vitejs/plugin-react@4.3.1(vite@4.5.5(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0))':
dependencies:
'@babel/core': 7.25.2
'@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2)
'@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2)
'@types/babel__core': 7.20.5
react-refresh: 0.14.2
- vite: 4.5.5(@types/node@20.16.9)(terser@5.34.0)
+ vite: 4.5.5(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0)
transitivePeerDependencies:
- supports-color
@@ -16926,13 +18097,13 @@ snapshots:
chai: 5.2.0
tinyrainbow: 1.2.0
- '@vitest/mocker@2.1.9(vite@5.4.8(@types/node@22.12.0)(terser@5.34.0))':
+ '@vitest/mocker@2.1.9(vite@5.4.14(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0))':
dependencies:
'@vitest/spy': 2.1.9
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
- vite: 5.4.8(@types/node@22.12.0)(terser@5.34.0)
+ vite: 5.4.14(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0)
'@vitest/pretty-format@2.0.5':
dependencies:
@@ -16984,6 +18155,97 @@ snapshots:
loupe: 3.1.3
tinyrainbow: 1.2.0
+ '@webassemblyjs/ast@1.14.1':
+ dependencies:
+ '@webassemblyjs/helper-numbers': 1.13.2
+ '@webassemblyjs/helper-wasm-bytecode': 1.13.2
+
+ '@webassemblyjs/floating-point-hex-parser@1.13.2': {}
+
+ '@webassemblyjs/helper-api-error@1.13.2': {}
+
+ '@webassemblyjs/helper-buffer@1.14.1': {}
+
+ '@webassemblyjs/helper-numbers@1.13.2':
+ dependencies:
+ '@webassemblyjs/floating-point-hex-parser': 1.13.2
+ '@webassemblyjs/helper-api-error': 1.13.2
+ '@xtuc/long': 4.2.2
+
+ '@webassemblyjs/helper-wasm-bytecode@1.13.2': {}
+
+ '@webassemblyjs/helper-wasm-section@1.14.1':
+ dependencies:
+ '@webassemblyjs/ast': 1.14.1
+ '@webassemblyjs/helper-buffer': 1.14.1
+ '@webassemblyjs/helper-wasm-bytecode': 1.13.2
+ '@webassemblyjs/wasm-gen': 1.14.1
+
+ '@webassemblyjs/ieee754@1.13.2':
+ dependencies:
+ '@xtuc/ieee754': 1.2.0
+
+ '@webassemblyjs/leb128@1.13.2':
+ dependencies:
+ '@xtuc/long': 4.2.2
+
+ '@webassemblyjs/utf8@1.13.2': {}
+
+ '@webassemblyjs/wasm-edit@1.14.1':
+ dependencies:
+ '@webassemblyjs/ast': 1.14.1
+ '@webassemblyjs/helper-buffer': 1.14.1
+ '@webassemblyjs/helper-wasm-bytecode': 1.13.2
+ '@webassemblyjs/helper-wasm-section': 1.14.1
+ '@webassemblyjs/wasm-gen': 1.14.1
+ '@webassemblyjs/wasm-opt': 1.14.1
+ '@webassemblyjs/wasm-parser': 1.14.1
+ '@webassemblyjs/wast-printer': 1.14.1
+
+ '@webassemblyjs/wasm-gen@1.14.1':
+ dependencies:
+ '@webassemblyjs/ast': 1.14.1
+ '@webassemblyjs/helper-wasm-bytecode': 1.13.2
+ '@webassemblyjs/ieee754': 1.13.2
+ '@webassemblyjs/leb128': 1.13.2
+ '@webassemblyjs/utf8': 1.13.2
+
+ '@webassemblyjs/wasm-opt@1.14.1':
+ dependencies:
+ '@webassemblyjs/ast': 1.14.1
+ '@webassemblyjs/helper-buffer': 1.14.1
+ '@webassemblyjs/wasm-gen': 1.14.1
+ '@webassemblyjs/wasm-parser': 1.14.1
+
+ '@webassemblyjs/wasm-parser@1.14.1':
+ dependencies:
+ '@webassemblyjs/ast': 1.14.1
+ '@webassemblyjs/helper-api-error': 1.13.2
+ '@webassemblyjs/helper-wasm-bytecode': 1.13.2
+ '@webassemblyjs/ieee754': 1.13.2
+ '@webassemblyjs/leb128': 1.13.2
+ '@webassemblyjs/utf8': 1.13.2
+
+ '@webassemblyjs/wast-printer@1.14.1':
+ dependencies:
+ '@webassemblyjs/ast': 1.14.1
+ '@xtuc/long': 4.2.2
+
+ '@webpack-cli/configtest@3.0.1(webpack-cli@6.0.1)(webpack@5.98.0)':
+ dependencies:
+ webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
+ webpack-cli: 6.0.1(webpack@5.98.0)
+
+ '@webpack-cli/info@3.0.1(webpack-cli@6.0.1)(webpack@5.98.0)':
+ dependencies:
+ webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
+ webpack-cli: 6.0.1(webpack@5.98.0)
+
+ '@webpack-cli/serve@3.0.1(webpack-cli@6.0.1)(webpack@5.98.0)':
+ dependencies:
+ webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
+ webpack-cli: 6.0.1(webpack@5.98.0)
+
'@workos-inc/node@7.34.0(express@4.21.0)(next@14.2.3(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))':
dependencies:
iron-session: 6.3.1(express@4.21.0)(next@14.2.3(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
@@ -16996,6 +18258,10 @@ snapshots:
'@xmldom/xmldom@0.8.10': {}
+ '@xtuc/ieee754@1.2.0': {}
+
+ '@xtuc/long@4.2.2': {}
+
abbrev@1.1.1: {}
abbrev@2.0.0: {}
@@ -17017,10 +18283,6 @@ snapshots:
dependencies:
acorn: 7.4.1
- acorn-jsx@5.3.2(acorn@8.12.1):
- dependencies:
- acorn: 8.12.1
-
acorn-jsx@5.3.2(acorn@8.14.0):
dependencies:
acorn: 8.14.0
@@ -17029,9 +18291,9 @@ snapshots:
dependencies:
acorn: 8.14.0
- acorn-typescript@1.4.13(acorn@8.12.1):
+ acorn-typescript@1.4.13(acorn@8.14.0):
dependencies:
- acorn: 8.12.1
+ acorn: 8.14.0
acorn-walk@8.3.2: {}
@@ -17041,8 +18303,6 @@ snapshots:
acorn@7.4.1: {}
- acorn@8.12.1: {}
-
acorn@8.14.0: {}
aes-decrypter@4.0.1:
@@ -17080,6 +18340,15 @@ snapshots:
clean-stack: 2.2.0
indent-string: 4.0.0
+ ajv-formats@2.1.1(ajv@8.17.1):
+ optionalDependencies:
+ ajv: 8.17.1
+
+ ajv-keywords@5.1.0(ajv@8.17.1):
+ dependencies:
+ ajv: 8.17.1
+ fast-deep-equal: 3.1.3
+
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
@@ -17530,6 +18799,8 @@ snapshots:
base64-js: 1.5.1
ieee754: 1.2.1
+ builtin-status-codes@3.0.0: {}
+
busboy@1.6.0:
dependencies:
streamsearch: 1.1.0
@@ -17700,6 +18971,8 @@ snapshots:
chromatic@11.10.4: {}
+ chrome-trace-event@1.0.4: {}
+
ci-info@3.9.0: {}
citty@0.1.6:
@@ -17754,6 +19027,12 @@ snapshots:
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
+ clone-deep@4.0.1:
+ dependencies:
+ is-plain-object: 2.0.4
+ kind-of: 6.0.3
+ shallow-clone: 3.0.1
+
clone@1.0.4: {}
clsx@1.2.1: {}
@@ -17804,6 +19083,8 @@ snapshots:
color-string: 1.9.1
optional: true
+ colorette@2.0.20: {}
+
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
@@ -17812,6 +19093,8 @@ snapshots:
commander@10.0.1: {}
+ commander@12.1.0: {}
+
commander@2.20.3: {}
commander@4.1.1: {}
@@ -17904,6 +19187,15 @@ snapshots:
dependencies:
is-what: 4.1.16
+ copy-webpack-plugin@13.0.0(webpack@5.98.0):
+ dependencies:
+ glob-parent: 6.0.2
+ normalize-path: 3.0.0
+ schema-utils: 4.3.0
+ serialize-javascript: 6.0.2
+ tinyglobby: 0.2.12
+ webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
+
core-js-pure@3.38.1: {}
core-js@3.40.0: {}
@@ -17915,6 +19207,15 @@ snapshots:
object-assign: 4.1.1
vary: 1.1.2
+ cosmiconfig@9.0.0(typescript@5.7.2):
+ dependencies:
+ env-paths: 2.2.1
+ import-fresh: 3.3.0
+ js-yaml: 4.1.0
+ parse-json: 5.2.0
+ optionalDependencies:
+ typescript: 5.7.2
+
crc-32@1.2.2: {}
crc32-stream@6.0.0:
@@ -17922,13 +19223,13 @@ snapshots:
crc-32: 1.2.2
readable-stream: 4.5.2
- create-jest@29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2)):
+ create-jest@29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)):
dependencies:
'@jest/types': 29.6.3
chalk: 4.1.2
exit: 0.1.2
graceful-fs: 4.2.11
- jest-config: 29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))
+ jest-config: 29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
jest-util: 29.7.0
prompts: 2.4.2
transitivePeerDependencies:
@@ -17965,6 +19266,19 @@ snapshots:
crossws@0.2.4: {}
+ css-loader@7.1.2(webpack@5.98.0):
+ dependencies:
+ icss-utils: 5.1.0(postcss@8.4.47)
+ postcss: 8.4.47
+ postcss-modules-extract-imports: 3.1.0(postcss@8.4.47)
+ postcss-modules-local-by-default: 4.2.0(postcss@8.4.47)
+ postcss-modules-scope: 3.2.1(postcss@8.4.47)
+ postcss-modules-values: 4.0.0(postcss@8.4.47)
+ postcss-value-parser: 4.2.0
+ semver: 7.6.3
+ optionalDependencies:
+ webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
+
css.escape@1.5.1: {}
cssesc@3.0.0: {}
@@ -18313,8 +19627,12 @@ snapshots:
entities@4.5.0: {}
+ env-paths@2.2.1: {}
+
env-paths@3.0.0: {}
+ envinfo@7.14.0: {}
+
error-ex@1.3.2:
dependencies:
is-arrayish: 0.2.1
@@ -18468,20 +19786,104 @@ snapshots:
esast-util-from-estree: 2.0.0
vfile-message: 4.0.2
- esbuild-register@3.6.0(esbuild@0.19.12):
- dependencies:
- debug: 4.3.7(supports-color@5.5.0)
- esbuild: 0.19.12
- transitivePeerDependencies:
- - supports-color
+ esbuild-android-64@0.14.54:
+ optional: true
- esbuild-register@3.6.0(esbuild@0.23.1):
- dependencies:
+ esbuild-android-arm64@0.14.54:
+ optional: true
+
+ esbuild-darwin-64@0.14.54:
+ optional: true
+
+ esbuild-darwin-arm64@0.14.54:
+ optional: true
+
+ esbuild-freebsd-64@0.14.54:
+ optional: true
+
+ esbuild-freebsd-arm64@0.14.54:
+ optional: true
+
+ esbuild-linux-32@0.14.54:
+ optional: true
+
+ esbuild-linux-64@0.14.54:
+ optional: true
+
+ esbuild-linux-arm64@0.14.54:
+ optional: true
+
+ esbuild-linux-arm@0.14.54:
+ optional: true
+
+ esbuild-linux-mips64le@0.14.54:
+ optional: true
+
+ esbuild-linux-ppc64le@0.14.54:
+ optional: true
+
+ esbuild-linux-riscv64@0.14.54:
+ optional: true
+
+ esbuild-linux-s390x@0.14.54:
+ optional: true
+
+ esbuild-netbsd-64@0.14.54:
+ optional: true
+
+ esbuild-openbsd-64@0.14.54:
+ optional: true
+
+ esbuild-register@3.6.0(esbuild@0.19.12):
+ dependencies:
+ debug: 4.3.7(supports-color@5.5.0)
+ esbuild: 0.19.12
+ transitivePeerDependencies:
+ - supports-color
+
+ esbuild-register@3.6.0(esbuild@0.23.1):
+ dependencies:
debug: 4.3.7(supports-color@5.5.0)
esbuild: 0.23.1
transitivePeerDependencies:
- supports-color
+ esbuild-sunos-64@0.14.54:
+ optional: true
+
+ esbuild-windows-32@0.14.54:
+ optional: true
+
+ esbuild-windows-64@0.14.54:
+ optional: true
+
+ esbuild-windows-arm64@0.14.54:
+ optional: true
+
+ esbuild@0.14.54:
+ optionalDependencies:
+ '@esbuild/linux-loong64': 0.14.54
+ esbuild-android-64: 0.14.54
+ esbuild-android-arm64: 0.14.54
+ esbuild-darwin-64: 0.14.54
+ esbuild-darwin-arm64: 0.14.54
+ esbuild-freebsd-64: 0.14.54
+ esbuild-freebsd-arm64: 0.14.54
+ esbuild-linux-32: 0.14.54
+ esbuild-linux-64: 0.14.54
+ esbuild-linux-arm: 0.14.54
+ esbuild-linux-arm64: 0.14.54
+ esbuild-linux-mips64le: 0.14.54
+ esbuild-linux-ppc64le: 0.14.54
+ esbuild-linux-riscv64: 0.14.54
+ esbuild-linux-s390x: 0.14.54
+ esbuild-netbsd-64: 0.14.54
+ esbuild-openbsd-64: 0.14.54
+ esbuild-sunos-64: 0.14.54
+ esbuild-windows-32: 0.14.54
+ esbuild-windows-64: 0.14.54
+ esbuild-windows-arm64: 0.14.54
+
esbuild@0.16.4:
optionalDependencies:
'@esbuild/android-arm': 0.16.4
@@ -18682,7 +20084,7 @@ snapshots:
optionalDependencies:
source-map: 0.6.1
- eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1))(eslint@8.57.1):
+ eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.30.0)(eslint@8.57.1):
dependencies:
confusing-browser-globals: 1.0.11
eslint: 8.57.1
@@ -18691,12 +20093,12 @@ snapshots:
object.entries: 1.1.8
semver: 6.3.1
- eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2))(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1))(eslint@8.57.1):
+ eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2))(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1):
dependencies:
'@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2)
'@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.7.2)
eslint: 8.57.1
- eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1))(eslint@8.57.1)
+ eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.30.0)(eslint@8.57.1)
transitivePeerDependencies:
- eslint-plugin-import
@@ -18761,7 +20163,7 @@ snapshots:
debug: 4.3.7(supports-color@5.5.0)
enhanced-resolve: 5.17.1
eslint: 8.57.1
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
fast-glob: 3.3.2
get-tsconfig: 4.8.1
is-bun-module: 1.2.1
@@ -18780,7 +20182,7 @@ snapshots:
debug: 4.3.7(supports-color@5.5.0)
enhanced-resolve: 5.17.1
eslint: 8.57.1
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1)
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
fast-glob: 3.3.2
get-tsconfig: 4.8.1
is-bun-module: 1.2.1
@@ -18799,7 +20201,7 @@ snapshots:
debug: 4.3.7(supports-color@5.5.0)
enhanced-resolve: 5.17.1
eslint: 8.57.1
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1)
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
fast-glob: 3.3.2
get-tsconfig: 4.8.1
is-bun-module: 1.2.1
@@ -18812,18 +20214,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
- dependencies:
- debug: 3.2.7
- optionalDependencies:
- '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.7.2)
- eslint: 8.57.1
- eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(eslint@8.57.1))(eslint@8.57.1)
- transitivePeerDependencies:
- - supports-color
-
- eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1):
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -18834,7 +20225,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1):
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -18856,7 +20247,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
@@ -18884,7 +20275,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1)
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
@@ -18955,11 +20346,11 @@ snapshots:
string.prototype.matchall: 4.0.11
string.prototype.repeat: 1.0.0
- eslint-plugin-tailwindcss@3.17.4(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))):
+ eslint-plugin-tailwindcss@3.17.4(tailwindcss@4.0.13):
dependencies:
fast-glob: 3.3.2
postcss: 8.4.47
- tailwindcss: 3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))
+ tailwindcss: 4.0.13
eslint-plugin-turbo@1.13.4(eslint@8.57.1):
dependencies:
@@ -19094,8 +20485,8 @@ snapshots:
espree@9.6.1:
dependencies:
- acorn: 8.12.1
- acorn-jsx: 5.3.2(acorn@8.12.1)
+ acorn: 8.14.0
+ acorn-jsx: 5.3.2(acorn@8.14.0)
eslint-visitor-keys: 3.4.3
esprima@4.0.1: {}
@@ -19329,6 +20720,8 @@ snapshots:
dependencies:
strnum: 1.0.5
+ fastest-levenshtein@1.0.16: {}
+
fastq@1.17.1:
dependencies:
reusify: 1.0.4
@@ -19345,6 +20738,10 @@ snapshots:
dependencies:
pend: 1.2.0
+ fdir@6.4.3(picomatch@4.0.2):
+ optionalDependencies:
+ picomatch: 4.0.2
+
fflate@0.4.8: {}
figures@3.2.0:
@@ -19397,6 +20794,8 @@ snapshots:
keyv: 4.5.4
rimraf: 3.0.2
+ flat@5.0.2: {}
+
flatted@3.3.1: {}
fluent-ffmpeg@2.1.3:
@@ -19591,6 +20990,8 @@ snapshots:
dependencies:
is-glob: 4.0.3
+ glob-regex@0.3.2: {}
+
glob-to-regexp@0.4.1: {}
glob@10.3.10:
@@ -19665,7 +21066,7 @@ snapshots:
'@types/glob': 7.2.0
array-union: 2.1.0
dir-glob: 3.0.1
- fast-glob: 3.3.2
+ fast-glob: 3.3.3
glob: 7.2.3
ignore: 5.3.2
merge2: 1.4.1
@@ -19675,7 +21076,7 @@ snapshots:
dependencies:
array-union: 2.1.0
dir-glob: 3.0.1
- fast-glob: 3.3.2
+ fast-glob: 3.3.3
ignore: 5.3.2
merge2: 1.4.1
slash: 3.0.0
@@ -19683,7 +21084,7 @@ snapshots:
globby@14.0.2:
dependencies:
'@sindresorhus/merge-streams': 2.3.0
- fast-glob: 3.3.2
+ fast-glob: 3.3.3
ignore: 5.3.2
path-type: 5.0.0
slash: 5.1.0
@@ -19916,6 +21317,13 @@ snapshots:
hosted-git-info@2.8.9: {}
+ hot-reload-extension-vite@1.0.13:
+ dependencies:
+ ws: 8.18.0
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
html-encoding-sniffer@4.0.0:
dependencies:
whatwg-encoding: 3.1.1
@@ -20002,6 +21410,10 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
+ icss-utils@5.1.0(postcss@8.4.47):
+ dependencies:
+ postcss: 8.4.47
+
ieee754@1.1.13: {}
ieee754@1.2.1: {}
@@ -20087,6 +21499,8 @@ snapshots:
interpret@1.4.0: {}
+ interpret@3.1.1: {}
+
invariant@2.2.4:
dependencies:
loose-envify: 1.4.0
@@ -20252,6 +21666,10 @@ snapshots:
is-plain-obj@4.1.0: {}
+ is-plain-object@2.0.4:
+ dependencies:
+ isobject: 3.0.1
+
is-plain-object@5.0.0: {}
is-potential-custom-element-name@1.0.1: {}
@@ -20336,6 +21754,8 @@ snapshots:
isexe@3.1.1: {}
+ isobject@3.0.1: {}
+
istanbul-lib-coverage@3.2.2: {}
istanbul-lib-instrument@5.2.1:
@@ -20436,16 +21856,16 @@ snapshots:
- babel-plugin-macros
- supports-color
- jest-cli@29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2)):
+ jest-cli@29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)):
dependencies:
- '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))
+ '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
chalk: 4.1.2
- create-jest: 29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))
+ create-jest: 29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
exit: 0.1.2
import-local: 3.2.0
- jest-config: 29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))
+ jest-config: 29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
jest-util: 29.7.0
jest-validate: 29.7.0
yargs: 17.7.2
@@ -20455,7 +21875,7 @@ snapshots:
- supports-color
- ts-node
- jest-config@29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2)):
+ jest-config@29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)):
dependencies:
'@babel/core': 7.25.2
'@jest/test-sequencer': 29.7.0
@@ -20481,7 +21901,7 @@ snapshots:
strip-json-comments: 3.1.1
optionalDependencies:
'@types/node': 20.16.9
- ts-node: 10.9.2(@types/node@20.16.9)(typescript@5.7.2)
+ ts-node: 10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
@@ -20694,6 +22114,12 @@ snapshots:
jest-util: 29.7.0
string-length: 4.0.2
+ jest-worker@27.5.1:
+ dependencies:
+ '@types/node': 20.16.9
+ merge-stream: 2.0.0
+ supports-color: 8.1.1
+
jest-worker@29.7.0:
dependencies:
'@types/node': 20.16.9
@@ -20701,12 +22127,12 @@ snapshots:
merge-stream: 2.0.0
supports-color: 8.1.1
- jest@29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2)):
+ jest@29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)):
dependencies:
- '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))
+ '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
'@jest/types': 29.6.3
import-local: 3.2.0
- jest-cli: 29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))
+ jest-cli: 29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
@@ -20715,7 +22141,7 @@ snapshots:
jiti@1.21.6: {}
- jiti@2.0.0: {}
+ jiti@2.4.2: {}
jju@1.4.0: {}
@@ -20735,6 +22161,8 @@ snapshots:
js-cookie: 3.0.5
nopt: 7.2.1
+ js-confetti@0.12.0: {}
+
js-cookie@3.0.5: {}
js-tokens@4.0.0: {}
@@ -20786,6 +22214,8 @@ snapshots:
jsesc@2.5.2: {}
+ jsesc@3.1.0: {}
+
json-buffer@3.0.1: {}
json-diff@0.9.0:
@@ -20871,9 +22301,55 @@ snapshots:
dependencies:
immediate: 3.0.6
+ lightningcss-darwin-arm64@1.29.2:
+ optional: true
+
+ lightningcss-darwin-x64@1.29.2:
+ optional: true
+
+ lightningcss-freebsd-x64@1.29.2:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.29.2:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.29.2:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.29.2:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.29.2:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.29.2:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.29.2:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.29.2:
+ optional: true
+
+ lightningcss@1.29.2:
+ dependencies:
+ detect-libc: 2.0.3
+ optionalDependencies:
+ lightningcss-darwin-arm64: 1.29.2
+ lightningcss-darwin-x64: 1.29.2
+ lightningcss-freebsd-x64: 1.29.2
+ lightningcss-linux-arm-gnueabihf: 1.29.2
+ lightningcss-linux-arm64-gnu: 1.29.2
+ lightningcss-linux-arm64-musl: 1.29.2
+ lightningcss-linux-x64-gnu: 1.29.2
+ lightningcss-linux-x64-musl: 1.29.2
+ lightningcss-win32-arm64-msvc: 1.29.2
+ lightningcss-win32-x64-msvc: 1.29.2
+ optional: true
+
lilconfig@2.1.0: {}
- lilconfig@3.1.2: {}
+ lilconfig@3.1.3: {}
lines-and-columns@1.2.4: {}
@@ -20889,7 +22365,7 @@ snapshots:
get-port-please: 3.1.2
h3: 1.12.0
http-shutdown: 1.2.2
- jiti: 2.0.0
+ jiti: 2.4.2
mlly: 1.7.1
node-forge: 1.3.1
pathe: 1.1.2
@@ -20902,6 +22378,8 @@ snapshots:
load-script@1.0.0: {}
+ loader-runner@4.3.0: {}
+
local-pkg@0.5.0:
dependencies:
mlly: 1.7.1
@@ -21892,6 +23370,8 @@ snapshots:
min-indent@1.0.1: {}
+ mini-svg-data-uri@1.4.4: {}
+
miniflare@3.20250204.1:
dependencies:
'@cspotcode/source-map-support': 0.8.1
@@ -22036,6 +23516,8 @@ snapshots:
nanoid@3.3.7: {}
+ nanoid@3.3.9: {}
+
nanoid@5.0.7: {}
natural-compare-lite@1.4.0: {}
@@ -22167,7 +23649,7 @@ snapshots:
cors: 2.8.5
next: 14.2.3(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- nitropack@2.9.7(@planetscale/database@1.19.0)(xml2js@0.6.2):
+ nitropack@2.9.7(@planetscale/database@1.19.0)(webpack-sources@3.2.3)(xml2js@0.6.2):
dependencies:
'@cloudflare/kv-asset-handler': 0.3.4
'@netlify/functions': 2.8.1
@@ -22230,9 +23712,9 @@ snapshots:
std-env: 3.7.0
ufo: 1.5.4
uncrypto: 0.1.3
- unctx: 2.3.1
+ unctx: 2.3.1(webpack-sources@3.2.3)
unenv: 1.10.0
- unimport: 3.13.0(rollup@4.22.5)
+ unimport: 3.13.0(rollup@4.22.5)(webpack-sources@3.2.3)
unstorage: 1.12.0(@planetscale/database@1.19.0)(ioredis@5.4.1)
unwasm: 0.3.9
optionalDependencies:
@@ -22452,7 +23934,7 @@ snapshots:
openapi-typescript@6.7.6:
dependencies:
ansi-colors: 4.1.3
- fast-glob: 3.3.2
+ fast-glob: 3.3.3
js-yaml: 4.1.0
supports-color: 9.4.0
undici: 5.28.4
@@ -22570,7 +24052,7 @@ snapshots:
parse-json@5.2.0:
dependencies:
- '@babel/code-frame': 7.24.7
+ '@babel/code-frame': 7.26.2
error-ex: 1.3.2
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
@@ -22643,8 +24125,12 @@ snapshots:
picocolors@1.1.0: {}
+ picocolors@1.1.1: {}
+
picomatch@2.3.1: {}
+ picomatch@4.0.2: {}
+
pify@2.3.0: {}
pirates@4.0.6: {}
@@ -22688,35 +24174,92 @@ snapshots:
read-cache: 1.0.0
resolve: 1.22.8
+ postcss-import@15.1.0(postcss@8.5.3):
+ dependencies:
+ postcss: 8.5.3
+ postcss-value-parser: 4.2.0
+ read-cache: 1.0.0
+ resolve: 1.22.8
+
postcss-js@4.0.1(postcss@8.4.47):
dependencies:
camelcase-css: 2.0.1
postcss: 8.4.47
- postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2)):
+ postcss-js@4.0.1(postcss@8.5.3):
dependencies:
- lilconfig: 3.1.2
- yaml: 2.5.1
+ camelcase-css: 2.0.1
+ postcss: 8.5.3
+
+ postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)):
+ dependencies:
+ lilconfig: 3.1.3
+ yaml: 2.7.0
optionalDependencies:
postcss: 8.4.47
- ts-node: 10.9.2(@types/node@20.16.9)(typescript@5.7.2)
+ ts-node: 10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)
- postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@22.12.0)(typescript@5.7.2)):
+ postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2)):
dependencies:
- lilconfig: 3.1.2
- yaml: 2.5.1
+ lilconfig: 3.1.3
+ yaml: 2.7.0
optionalDependencies:
postcss: 8.4.47
- ts-node: 10.9.2(@types/node@22.12.0)(typescript@5.7.2)
+ ts-node: 10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2)
+
+ postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@4.9.5)):
+ dependencies:
+ lilconfig: 3.1.3
+ yaml: 2.7.0
+ optionalDependencies:
+ postcss: 8.5.3
+ ts-node: 10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@4.9.5)
+
+ postcss-loader@8.1.1(postcss@8.4.47)(typescript@5.7.2)(webpack@5.98.0):
+ dependencies:
+ cosmiconfig: 9.0.0(typescript@5.7.2)
+ jiti: 1.21.6
+ postcss: 8.4.47
+ semver: 7.6.3
+ optionalDependencies:
+ webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
+ transitivePeerDependencies:
+ - typescript
+
+ postcss-modules-extract-imports@3.1.0(postcss@8.4.47):
+ dependencies:
+ postcss: 8.4.47
+
+ postcss-modules-local-by-default@4.2.0(postcss@8.4.47):
+ dependencies:
+ icss-utils: 5.1.0(postcss@8.4.47)
+ postcss: 8.4.47
+ postcss-selector-parser: 7.1.0
+ postcss-value-parser: 4.2.0
+
+ postcss-modules-scope@3.2.1(postcss@8.4.47):
+ dependencies:
+ postcss: 8.4.47
+ postcss-selector-parser: 7.1.0
+
+ postcss-modules-values@4.0.0(postcss@8.4.47):
+ dependencies:
+ icss-utils: 5.1.0(postcss@8.4.47)
+ postcss: 8.4.47
postcss-nested@6.2.0(postcss@8.4.47):
dependencies:
postcss: 8.4.47
postcss-selector-parser: 6.1.2
- postcss-pseudo-companion-classes@0.1.1(postcss@8.4.47):
+ postcss-nested@6.2.0(postcss@8.5.3):
dependencies:
- postcss: 8.4.47
+ postcss: 8.5.3
+ postcss-selector-parser: 6.1.2
+
+ postcss-pseudo-companion-classes@0.1.1(postcss@8.5.3):
+ dependencies:
+ postcss: 8.5.3
postcss-selector-parser@6.0.10:
dependencies:
@@ -22728,12 +24271,17 @@ snapshots:
cssesc: 3.0.0
util-deprecate: 1.0.2
+ postcss-selector-parser@7.1.0:
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
postcss-value-parser@4.2.0: {}
postcss@8.4.31:
dependencies:
nanoid: 3.3.7
- picocolors: 1.1.0
+ picocolors: 1.1.1
source-map-js: 1.2.1
postcss@8.4.47:
@@ -22742,6 +24290,12 @@ snapshots:
picocolors: 1.1.0
source-map-js: 1.2.1
+ postcss@8.5.3:
+ dependencies:
+ nanoid: 3.3.9
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
posthog-js@1.215.3:
dependencies:
core-js: 3.40.0
@@ -22847,6 +24401,8 @@ snapshots:
punycode@1.3.2: {}
+ punycode@1.4.1: {}
+
punycode@2.3.1: {}
pure-rand@6.1.0: {}
@@ -22923,6 +24479,11 @@ snapshots:
react: 18.3.1
scheduler: 0.23.2
+ react-dom@19.0.0(react@19.0.0):
+ dependencies:
+ react: 19.0.0
+ scheduler: 0.25.0
+
react-draggable@4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
clsx: 1.2.1
@@ -22953,6 +24514,12 @@ snapshots:
react-fast-compare@3.2.2: {}
+ react-frame-component@5.2.7(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ prop-types: 15.8.1
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
react-hls-player@3.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
hls.js: 0.14.17
@@ -22994,6 +24561,8 @@ snapshots:
dependencies:
fast-deep-equal: 2.0.1
+ react-refresh@0.13.0: {}
+
react-refresh@0.14.2: {}
react-remove-scroll-bar@2.3.6(@types/react@18.3.9)(react@18.3.1):
@@ -23068,6 +24637,13 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
+ react-tooltip@5.28.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ dependencies:
+ '@floating-ui/dom': 1.6.11
+ classnames: 2.5.1
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+
react@18.2.0:
dependencies:
loose-envify: 1.4.0
@@ -23076,6 +24652,8 @@ snapshots:
dependencies:
loose-envify: 1.4.0
+ react@19.0.0: {}
+
read-cache@1.0.0:
dependencies:
pify: 2.3.0
@@ -23137,6 +24715,10 @@ snapshots:
dependencies:
resolve: 1.22.8
+ rechoir@0.8.0:
+ dependencies:
+ resolve: 1.22.8
+
recma-build-jsx@1.0.0:
dependencies:
'@types/estree': 1.0.6
@@ -23167,6 +24749,14 @@ snapshots:
unified: 11.0.5
vfile: 6.0.3
+ recrawl-sync@2.2.3:
+ dependencies:
+ '@cush/relative': 1.0.0
+ glob-regex: 0.3.2
+ slash: 3.0.0
+ sucrase: 3.35.0
+ tslib: 1.14.1
+
redent@3.0.0:
dependencies:
indent-string: 4.0.0
@@ -23264,7 +24854,7 @@ snapshots:
estree-util-value-to-estree: 3.2.1
toml: 3.0.0
unified: 11.0.5
- yaml: 2.5.1
+ yaml: 2.7.0
remark-mdx@2.3.0:
dependencies:
@@ -23385,6 +24975,10 @@ snapshots:
dependencies:
estree-walker: 0.6.1
+ rollup@2.77.3:
+ optionalDependencies:
+ fsevents: 2.3.3
+
rollup@3.29.5:
optionalDependencies:
fsevents: 2.3.3
@@ -23460,6 +25054,15 @@ snapshots:
dependencies:
loose-envify: 1.4.0
+ scheduler@0.25.0: {}
+
+ schema-utils@4.3.0:
+ dependencies:
+ '@types/json-schema': 7.0.15
+ ajv: 8.17.1
+ ajv-formats: 2.1.1(ajv@8.17.1)
+ ajv-keywords: 5.1.0(ajv@8.17.1)
+
scule@1.3.0: {}
section-matter@1.0.0:
@@ -23549,6 +25152,10 @@ snapshots:
setprototypeof@1.2.0: {}
+ shallow-clone@3.0.1:
+ dependencies:
+ kind-of: 6.0.3
+
sharp@0.33.5:
dependencies:
color: 4.2.3
@@ -23582,6 +25189,8 @@ snapshots:
shebang-regex@3.0.0: {}
+ shell-quote@1.8.2: {}
+
shelljs@0.8.5:
dependencies:
glob: 7.2.3
@@ -23776,9 +25385,9 @@ snapshots:
stoppable@1.1.0: {}
- storybook-solidjs-vite@1.0.0-beta.2(storybook@8.3.3)(vite@5.4.8(@types/node@22.12.0)(terser@5.34.0)):
+ storybook-solidjs-vite@1.0.0-beta.2(storybook@8.3.3)(vite@5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0)):
dependencies:
- '@storybook/builder-vite': 9.0.0-alpha.2(storybook@8.3.3)(vite@5.4.8(@types/node@22.12.0)(terser@5.34.0))
+ '@storybook/builder-vite': 9.0.0-alpha.4(storybook@8.3.3)(vite@5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0))
transitivePeerDependencies:
- storybook
- vite
@@ -23796,6 +25405,13 @@ snapshots:
- supports-color
- utf-8-validate
+ stream-http@3.2.0:
+ dependencies:
+ builtin-status-codes: 3.0.0
+ inherits: 2.0.4
+ readable-stream: 3.6.2
+ xtend: 4.0.2
+
streamsearch@1.1.0: {}
streamx@2.20.1:
@@ -23921,6 +25537,10 @@ snapshots:
strnum@1.0.5: {}
+ style-loader@4.0.0(webpack@5.98.0):
+ dependencies:
+ webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
+
style-to-object@0.3.0:
dependencies:
inline-style-parser: 0.1.1
@@ -24013,23 +25633,23 @@ snapshots:
tailwind-merge@2.5.2: {}
- tailwind-scrollbar@3.1.0(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))):
+ tailwind-scrollbar@3.1.0(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))):
dependencies:
- tailwindcss: 3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))
+ tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
- tailwind-scrollbar@3.1.0(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@22.12.0)(typescript@5.7.2))):
+ tailwind-scrollbar@3.1.0(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2))):
dependencies:
- tailwindcss: 3.4.13(ts-node@10.9.2(@types/node@22.12.0)(typescript@5.7.2))
+ tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2))
- tailwindcss-animate@1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))):
+ tailwindcss-animate@1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))):
dependencies:
- tailwindcss: 3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))
+ tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
- tailwindcss-animate@1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@22.12.0)(typescript@5.7.2))):
+ tailwindcss-animate@1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2))):
dependencies:
- tailwindcss: 3.4.13(ts-node@10.9.2(@types/node@22.12.0)(typescript@5.7.2))
+ tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2))
- tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2)):
+ tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)):
dependencies:
'@alloc/quick-lru': 5.2.0
arg: 5.0.2
@@ -24048,7 +25668,7 @@ snapshots:
postcss: 8.4.47
postcss-import: 15.1.0(postcss@8.4.47)
postcss-js: 4.0.1(postcss@8.4.47)
- postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))
+ postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
postcss-nested: 6.2.0(postcss@8.4.47)
postcss-selector-parser: 6.1.2
resolve: 1.22.8
@@ -24056,7 +25676,7 @@ snapshots:
transitivePeerDependencies:
- ts-node
- tailwindcss@3.4.13(ts-node@10.9.2(@types/node@22.12.0)(typescript@5.7.2)):
+ tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2)):
dependencies:
'@alloc/quick-lru': 5.2.0
arg: 5.0.2
@@ -24075,7 +25695,7 @@ snapshots:
postcss: 8.4.47
postcss-import: 15.1.0(postcss@8.4.47)
postcss-js: 4.0.1(postcss@8.4.47)
- postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@22.12.0)(typescript@5.7.2))
+ postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2))
postcss-nested: 6.2.0(postcss@8.4.47)
postcss-selector-parser: 6.1.2
resolve: 1.22.8
@@ -24083,6 +25703,35 @@ snapshots:
transitivePeerDependencies:
- ts-node
+ tailwindcss@3.4.16(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@4.9.5)):
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ arg: 5.0.2
+ chokidar: 3.6.0
+ didyoumean: 1.2.2
+ dlv: 1.1.3
+ fast-glob: 3.3.3
+ glob-parent: 6.0.2
+ is-glob: 4.0.3
+ jiti: 1.21.6
+ lilconfig: 3.1.3
+ micromatch: 4.0.8
+ normalize-path: 3.0.0
+ object-hash: 3.0.0
+ picocolors: 1.1.1
+ postcss: 8.5.3
+ postcss-import: 15.1.0(postcss@8.5.3)
+ postcss-js: 4.0.1(postcss@8.5.3)
+ postcss-load-config: 4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@4.9.5))
+ postcss-nested: 6.2.0(postcss@8.5.3)
+ postcss-selector-parser: 6.1.2
+ resolve: 1.22.8
+ sucrase: 3.35.0
+ transitivePeerDependencies:
+ - ts-node
+
+ tailwindcss@4.0.13: {}
+
tapable@2.2.1: {}
tar-stream@3.1.7:
@@ -24117,6 +25766,17 @@ snapshots:
solid-js: 1.9.3
solid-use: 0.9.0(solid-js@1.9.3)
+ terser-webpack-plugin@5.3.14(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack@5.98.0):
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.25
+ jest-worker: 27.5.1
+ schema-utils: 4.3.0
+ serialize-javascript: 6.0.2
+ terser: 5.34.0
+ webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
+ optionalDependencies:
+ '@swc/core': 1.11.8(@swc/helpers@0.5.13)
+
terser@5.34.0:
dependencies:
'@jridgewell/source-map': 0.3.6
@@ -24165,6 +25825,11 @@ snapshots:
tinyexec@0.3.2: {}
+ tinyglobby@0.2.12:
+ dependencies:
+ fdir: 6.4.3(picomatch@4.0.2)
+ picomatch: 4.0.2
+
tinygradient@1.1.5:
dependencies:
'@types/tinycolor2': 1.4.6
@@ -24242,12 +25907,12 @@ snapshots:
ts-interface-checker@0.1.13: {}
- ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2)))(typescript@5.7.2):
+ ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)))(typescript@5.7.2):
dependencies:
bs-logger: 0.2.6
ejs: 3.1.10
fast-json-stable-stringify: 2.1.0
- jest: 29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2))
+ jest: 29.7.0(@types/node@20.16.9)(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
jest-util: 29.7.0
json5: 2.2.3
lodash.memoize: 4.1.2
@@ -24261,7 +25926,17 @@ snapshots:
'@jest/types': 29.6.3
babel-jest: 29.7.0(@babel/core@7.25.2)
- ts-node@10.9.2(@types/node@20.16.9)(typescript@5.7.2):
+ ts-loader@9.5.2(typescript@5.7.2)(webpack@5.98.0):
+ dependencies:
+ chalk: 4.1.2
+ enhanced-resolve: 5.17.1
+ micromatch: 4.0.8
+ semver: 7.6.3
+ source-map: 0.7.4
+ typescript: 5.7.2
+ webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
+
+ ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
@@ -24269,7 +25944,7 @@ snapshots:
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 20.16.9
- acorn: 8.12.1
+ acorn: 8.14.0
acorn-walk: 8.3.4
arg: 4.1.3
create-require: 1.1.1
@@ -24278,8 +25953,31 @@ snapshots:
typescript: 5.7.2
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
+ optionalDependencies:
+ '@swc/core': 1.11.8(@swc/helpers@0.5.13)
+
+ ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@4.9.5):
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1
+ '@tsconfig/node10': 1.0.11
+ '@tsconfig/node12': 1.0.11
+ '@tsconfig/node14': 1.0.3
+ '@tsconfig/node16': 1.0.4
+ '@types/node': 22.12.0
+ acorn: 8.14.0
+ acorn-walk: 8.3.4
+ arg: 4.1.3
+ create-require: 1.1.1
+ diff: 4.0.2
+ make-error: 1.3.6
+ typescript: 4.9.5
+ v8-compile-cache-lib: 3.0.1
+ yn: 3.1.1
+ optionalDependencies:
+ '@swc/core': 1.11.8(@swc/helpers@0.5.13)
+ optional: true
- ts-node@10.9.2(@types/node@22.12.0)(typescript@5.7.2):
+ ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@5.7.2):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
@@ -24287,7 +25985,7 @@ snapshots:
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 22.12.0
- acorn: 8.12.1
+ acorn: 8.14.0
acorn-walk: 8.3.4
arg: 4.1.3
create-require: 1.1.1
@@ -24296,6 +25994,8 @@ snapshots:
typescript: 5.7.2
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
+ optionalDependencies:
+ '@swc/core': 1.11.8(@swc/helpers@0.5.13)
ts-pattern@5.5.0: {}
@@ -24312,6 +26012,12 @@ snapshots:
minimist: 1.2.8
strip-bom: 3.0.0
+ tsconfig-paths@4.2.0:
+ dependencies:
+ json5: 2.2.3
+ minimist: 1.2.8
+ strip-bom: 3.0.0
+
tslib@1.14.1: {}
tslib@2.6.2: {}
@@ -24421,6 +26127,8 @@ snapshots:
is-typed-array: 1.1.13
possible-typed-array-names: 1.0.0
+ typescript@4.9.5: {}
+
typescript@5.7.2: {}
ufo@1.5.4: {}
@@ -24437,12 +26145,12 @@ snapshots:
uncrypto@0.1.3: {}
- unctx@2.3.1:
+ unctx@2.3.1(webpack-sources@3.2.3):
dependencies:
- acorn: 8.12.1
+ acorn: 8.14.0
estree-walker: 3.0.3
magic-string: 0.30.11
- unplugin: 1.14.1
+ unplugin: 1.14.1(webpack-sources@3.2.3)
transitivePeerDependencies:
- webpack-sources
@@ -24498,13 +26206,13 @@ snapshots:
trough: 2.2.0
vfile: 6.0.3
- unimport@3.13.0(rollup@4.22.5):
+ unimport@3.13.0(rollup@4.22.5)(webpack-sources@3.2.3):
dependencies:
'@rollup/pluginutils': 5.1.2(rollup@4.22.5)
- acorn: 8.12.1
+ acorn: 8.14.0
escape-string-regexp: 5.0.0
estree-walker: 3.0.3
- fast-glob: 3.3.2
+ fast-glob: 3.3.3
local-pkg: 0.5.0
magic-string: 0.30.11
mlly: 1.7.1
@@ -24512,7 +26220,7 @@ snapshots:
pkg-types: 1.2.0
scule: 1.3.0
strip-literal: 2.1.0
- unplugin: 1.14.1
+ unplugin: 1.14.1(webpack-sources@3.2.3)
transitivePeerDependencies:
- rollup
- webpack-sources
@@ -24586,7 +26294,7 @@ snapshots:
unpipe@1.0.0: {}
- unplugin-auto-import@0.18.3(rollup@4.22.5):
+ unplugin-auto-import@0.18.3(rollup@4.22.5)(webpack-sources@3.2.3):
dependencies:
'@antfu/utils': 0.7.10
'@rollup/pluginutils': 5.1.2(rollup@4.22.5)
@@ -24594,21 +26302,21 @@ snapshots:
local-pkg: 0.5.0
magic-string: 0.30.11
minimatch: 9.0.5
- unimport: 3.13.0(rollup@4.22.5)
- unplugin: 1.14.1
+ unimport: 3.13.0(rollup@4.22.5)(webpack-sources@3.2.3)
+ unplugin: 1.14.1(webpack-sources@3.2.3)
transitivePeerDependencies:
- rollup
- webpack-sources
- unplugin-fonts@1.1.1(vite@5.4.8(@types/node@22.12.0)(terser@5.34.0)):
+ unplugin-fonts@1.1.1(vite@5.4.14(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0))(webpack-sources@3.2.3):
dependencies:
fast-glob: 3.3.2
- unplugin: 1.14.1
- vite: 5.4.8(@types/node@22.12.0)(terser@5.34.0)
+ unplugin: 1.14.1(webpack-sources@3.2.3)
+ vite: 5.4.14(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0)
transitivePeerDependencies:
- webpack-sources
- unplugin-icons@0.19.3:
+ unplugin-icons@0.19.3(webpack-sources@3.2.3):
dependencies:
'@antfu/install-pkg': 0.4.1
'@antfu/utils': 0.7.10
@@ -24616,15 +26324,17 @@ snapshots:
debug: 4.3.7(supports-color@5.5.0)
kolorist: 1.8.0
local-pkg: 0.5.0
- unplugin: 1.14.1
+ unplugin: 1.14.1(webpack-sources@3.2.3)
transitivePeerDependencies:
- supports-color
- webpack-sources
- unplugin@1.14.1:
+ unplugin@1.14.1(webpack-sources@3.2.3):
dependencies:
- acorn: 8.12.1
+ acorn: 8.14.0
webpack-virtual-modules: 0.6.2
+ optionalDependencies:
+ webpack-sources: 3.2.3
unplugin@1.16.1:
dependencies:
@@ -24668,7 +26378,7 @@ snapshots:
dependencies:
browserslist: 4.24.0
escalade: 3.2.0
- picocolors: 1.1.0
+ picocolors: 1.1.1
update-check@1.5.4:
dependencies:
@@ -24694,6 +26404,11 @@ snapshots:
punycode: 1.3.2
querystring: 0.2.0
+ url@0.11.4:
+ dependencies:
+ punycode: 1.4.1
+ qs: 6.13.0
+
urlpattern-polyfill@8.0.2: {}
use-callback-ref@1.3.2(@types/react@18.3.9)(react@18.3.1):
@@ -24819,7 +26534,7 @@ snapshots:
dependencies:
global: 4.4.0
- vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(terser@5.34.0)(xml2js@0.6.2):
+ vinxi@0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(lightningcss@1.29.2)(terser@5.34.0)(webpack-sources@3.2.3)(xml2js@0.6.2):
dependencies:
'@babel/core': 7.25.2
'@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.25.2)
@@ -24841,7 +26556,7 @@ snapshots:
hookable: 5.5.3
http-proxy: 1.18.1
micromatch: 4.0.8
- nitropack: 2.9.7(@planetscale/database@1.19.0)(xml2js@0.6.2)
+ nitropack: 2.9.7(@planetscale/database@1.19.0)(webpack-sources@3.2.3)(xml2js@0.6.2)
node-fetch-native: 1.6.4
path-to-regexp: 6.3.0
pathe: 1.1.2
@@ -24850,10 +26565,10 @@ snapshots:
serve-placeholder: 2.0.2
serve-static: 1.16.2
ufo: 1.5.4
- unctx: 2.3.1
+ unctx: 2.3.1(webpack-sources@3.2.3)
unenv: 1.10.0
unstorage: 1.12.0(@planetscale/database@1.19.0)(ioredis@5.4.1)
- vite: 5.4.8(@types/node@22.12.0)(terser@5.34.0)
+ vite: 5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0)
zod: 3.24.1
transitivePeerDependencies:
- '@azure/app-configuration'
@@ -24888,13 +26603,13 @@ snapshots:
- webpack-sources
- xml2js
- vite-node@2.1.9(@types/node@22.12.0)(terser@5.34.0):
+ vite-node@2.1.9(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0):
dependencies:
cac: 6.7.14
debug: 4.3.7(supports-color@5.5.0)
es-module-lexer: 1.5.4
pathe: 1.1.2
- vite: 5.4.8(@types/node@22.12.0)(terser@5.34.0)
+ vite: 5.4.14(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0)
transitivePeerDependencies:
- '@types/node'
- less
@@ -24906,7 +26621,7 @@ snapshots:
- supports-color
- terser
- vite-plugin-solid@2.10.2(@testing-library/jest-dom@6.5.0)(solid-js@1.9.3)(vite@5.4.8(@types/node@22.12.0)(terser@5.34.0)):
+ vite-plugin-solid@2.10.2(@testing-library/jest-dom@6.5.0)(solid-js@1.9.3)(vite@5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0)):
dependencies:
'@babel/core': 7.25.2
'@types/babel__core': 7.20.5
@@ -24914,8 +26629,8 @@ snapshots:
merge-anything: 5.1.7
solid-js: 1.9.3
solid-refresh: 0.6.3(solid-js@1.9.3)
- vite: 5.4.8(@types/node@22.12.0)(terser@5.34.0)
- vitefu: 0.2.5(vite@5.4.8(@types/node@22.12.0)(terser@5.34.0))
+ vite: 5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0)
+ vitefu: 0.2.5(vite@5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0))
optionalDependencies:
'@testing-library/jest-dom': 6.5.0
transitivePeerDependencies:
@@ -24924,29 +26639,48 @@ snapshots:
- supports-color
- utf-8-validate
- vite-tsconfig-paths@4.3.2(typescript@5.7.2)(vite@4.5.5(@types/node@20.16.9)(terser@5.34.0)):
+ vite-tsconfig-paths@3.6.0(vite@2.9.18):
+ dependencies:
+ debug: 4.3.7(supports-color@5.5.0)
+ globrex: 0.1.2
+ recrawl-sync: 2.2.3
+ tsconfig-paths: 4.2.0
+ vite: 2.9.18
+ transitivePeerDependencies:
+ - supports-color
+
+ vite-tsconfig-paths@4.3.2(typescript@5.7.2)(vite@4.5.5(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0)):
dependencies:
debug: 4.3.7(supports-color@5.5.0)
globrex: 0.1.2
tsconfck: 3.1.3(typescript@5.7.2)
optionalDependencies:
- vite: 4.5.5(@types/node@20.16.9)(terser@5.34.0)
+ vite: 4.5.5(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0)
transitivePeerDependencies:
- supports-color
- typescript
- vite-tsconfig-paths@5.0.1(typescript@5.7.2)(vite@5.4.8(@types/node@22.12.0)(terser@5.34.0)):
+ vite-tsconfig-paths@5.0.1(typescript@5.7.2)(vite@5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0)):
dependencies:
debug: 4.3.7(supports-color@5.5.0)
globrex: 0.1.2
tsconfck: 3.1.3(typescript@5.7.2)
optionalDependencies:
- vite: 5.4.8(@types/node@22.12.0)(terser@5.34.0)
+ vite: 5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0)
transitivePeerDependencies:
- supports-color
- typescript
- vite@4.5.5(@types/node@20.16.9)(terser@5.34.0):
+ vite@2.9.18:
+ dependencies:
+ esbuild: 0.14.54
+ postcss: 8.5.3
+ resolve: 1.22.8
+ rollup: 2.77.3
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ vite@4.5.5(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0):
dependencies:
esbuild: 0.18.20
postcss: 8.4.47
@@ -24954,9 +26688,21 @@ snapshots:
optionalDependencies:
'@types/node': 20.16.9
fsevents: 2.3.3
+ lightningcss: 1.29.2
+ terser: 5.34.0
+
+ vite@5.4.14(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0):
+ dependencies:
+ esbuild: 0.21.5
+ postcss: 8.4.47
+ rollup: 4.22.5
+ optionalDependencies:
+ '@types/node': 20.16.9
+ fsevents: 2.3.3
+ lightningcss: 1.29.2
terser: 5.34.0
- vite@5.4.8(@types/node@22.12.0)(terser@5.34.0):
+ vite@5.4.14(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0):
dependencies:
esbuild: 0.21.5
postcss: 8.4.47
@@ -24964,16 +26710,28 @@ snapshots:
optionalDependencies:
'@types/node': 22.12.0
fsevents: 2.3.3
+ lightningcss: 1.29.2
terser: 5.34.0
- vitefu@0.2.5(vite@5.4.8(@types/node@22.12.0)(terser@5.34.0)):
+ vite@5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0):
+ dependencies:
+ esbuild: 0.21.5
+ postcss: 8.4.47
+ rollup: 4.22.5
+ optionalDependencies:
+ '@types/node': 22.12.0
+ fsevents: 2.3.3
+ lightningcss: 1.29.2
+ terser: 5.34.0
+
+ vitefu@0.2.5(vite@5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0)):
optionalDependencies:
- vite: 5.4.8(@types/node@22.12.0)(terser@5.34.0)
+ vite: 5.4.8(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0)
- vitest@2.1.9(@types/node@22.12.0)(jsdom@25.0.1)(terser@5.34.0):
+ vitest@2.1.9(@types/node@22.12.0)(jsdom@25.0.1)(lightningcss@1.29.2)(terser@5.34.0):
dependencies:
'@vitest/expect': 2.1.9
- '@vitest/mocker': 2.1.9(vite@5.4.8(@types/node@22.12.0)(terser@5.34.0))
+ '@vitest/mocker': 2.1.9(vite@5.4.14(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0))
'@vitest/pretty-format': 2.1.9
'@vitest/runner': 2.1.9
'@vitest/snapshot': 2.1.9
@@ -24989,8 +26747,8 @@ snapshots:
tinyexec: 0.3.2
tinypool: 1.0.2
tinyrainbow: 1.2.0
- vite: 5.4.8(@types/node@22.12.0)(terser@5.34.0)
- vite-node: 2.1.9(@types/node@22.12.0)(terser@5.34.0)
+ vite: 5.4.14(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0)
+ vite-node: 2.1.9(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 22.12.0
@@ -25014,6 +26772,11 @@ snapshots:
dependencies:
makeerror: 1.0.12
+ watchpack@2.4.2:
+ dependencies:
+ glob-to-regexp: 0.4.1
+ graceful-fs: 4.2.11
+
wcwidth@1.0.1:
dependencies:
defaults: 1.0.4
@@ -25038,8 +26801,65 @@ snapshots:
webidl-conversions@7.0.0: {}
+ webpack-cli@6.0.1(webpack@5.98.0):
+ dependencies:
+ '@discoveryjs/json-ext': 0.6.3
+ '@webpack-cli/configtest': 3.0.1(webpack-cli@6.0.1)(webpack@5.98.0)
+ '@webpack-cli/info': 3.0.1(webpack-cli@6.0.1)(webpack@5.98.0)
+ '@webpack-cli/serve': 3.0.1(webpack-cli@6.0.1)(webpack@5.98.0)
+ colorette: 2.0.20
+ commander: 12.1.0
+ cross-spawn: 7.0.6
+ envinfo: 7.14.0
+ fastest-levenshtein: 1.0.16
+ import-local: 3.2.0
+ interpret: 3.1.1
+ rechoir: 0.8.0
+ webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
+ webpack-merge: 6.0.1
+
+ webpack-merge@6.0.1:
+ dependencies:
+ clone-deep: 4.0.1
+ flat: 5.0.2
+ wildcard: 2.0.1
+
+ webpack-sources@3.2.3: {}
+
webpack-virtual-modules@0.6.2: {}
+ webpack@5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1):
+ dependencies:
+ '@types/eslint-scope': 3.7.7
+ '@types/estree': 1.0.6
+ '@webassemblyjs/ast': 1.14.1
+ '@webassemblyjs/wasm-edit': 1.14.1
+ '@webassemblyjs/wasm-parser': 1.14.1
+ acorn: 8.14.0
+ browserslist: 4.24.0
+ chrome-trace-event: 1.0.4
+ enhanced-resolve: 5.17.1
+ es-module-lexer: 1.5.4
+ eslint-scope: 5.1.1
+ events: 3.3.0
+ glob-to-regexp: 0.4.1
+ graceful-fs: 4.2.11
+ json-parse-even-better-errors: 2.3.1
+ loader-runner: 4.3.0
+ mime-types: 2.1.35
+ neo-async: 2.6.2
+ schema-utils: 4.3.0
+ tapable: 2.2.1
+ terser-webpack-plugin: 5.3.14(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack@5.98.0)
+ watchpack: 2.4.2
+ webpack-sources: 3.2.3
+ optionalDependencies:
+ webpack-cli: 6.0.1(webpack@5.98.0)
+ transitivePeerDependencies:
+ - '@swc/core'
+ - esbuild
+ - uglify-js
+
whatwg-encoding@3.1.1:
dependencies:
iconv-lite: 0.6.3
@@ -25119,6 +26939,8 @@ snapshots:
dependencies:
string-width: 5.1.2
+ wildcard@2.0.1: {}
+
word-wrap@1.2.5: {}
wordwrap@1.0.0: {}
@@ -25188,14 +27010,14 @@ snapshots:
xmlchars@2.2.0: {}
+ xtend@4.0.2: {}
+
y18n@5.0.8: {}
yallist@3.1.1: {}
yallist@4.0.0: {}
- yaml@2.5.1: {}
-
yaml@2.7.0: {}
yargs-parser@18.1.3:
@@ -25240,4 +27062,10 @@ snapshots:
zod@3.24.1: {}
+ zustand@5.0.3(@types/react@18.3.9)(react@18.3.1)(use-sync-external-store@1.2.2(react@18.3.1)):
+ optionalDependencies:
+ '@types/react': 18.3.9
+ react: 18.3.1
+ use-sync-external-store: 1.2.2(react@18.3.1)
+
zwitch@2.0.4: {}
diff --git a/scripts/setup.js b/scripts/setup.js
index 0136777dd..9a2f22e8f 100644
--- a/scripts/setup.js
+++ b/scripts/setup.js
@@ -178,6 +178,11 @@ async function main() {
path.join(__root, ".env"),
path.join(__root, "apps/desktop/.env")
);
+
+ await fs.copyFile(
+ path.join(__root, ".env"),
+ path.join(__root, "apps/loom-importer-extension/.env")
+ );
}
main();
From d4b29afa54c64e663613386536866220a5410e6f Mon Sep 17 00:00:00 2001
From: neo773
Date: Sun, 16 Mar 2025 01:30:41 +0530
Subject: [PATCH 02/36] fix: pnpm lockfile
---
pnpm-lock.yaml | 834 +------------------------------------------------
1 file changed, 5 insertions(+), 829 deletions(-)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 86af15478..2a93da47d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -314,91 +314,6 @@ importers:
specifier: ^2.7.2
version: 2.9.18
- apps/old:
- dependencies:
- '@cap/ui':
- specifier: workspace:*
- version: link:../../packages/ui
- copy-webpack-plugin:
- specifier: ^13.0.0
- version: 13.0.0(webpack@5.98.0)
- lucide-react:
- specifier: ^0.294.0
- version: 0.294.0(react@18.3.1)
- react:
- specifier: ^18.2.0
- version: 18.3.1
- react-dom:
- specifier: ^18.2.0
- version: 18.3.1(react@18.3.1)
- stream-http:
- specifier: ^3.2.0
- version: 3.2.0
- url:
- specifier: ^0.11.4
- version: 0.11.4
- devDependencies:
- '@tailwindcss/forms':
- specifier: ^0.5.7
- version: 0.5.10(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)))
- '@types/chrome':
- specifier: ^0.0.309
- version: 0.0.309
- '@types/node':
- specifier: ^20.10.4
- version: 20.16.9
- '@types/react':
- specifier: ^18.0.29
- version: 18.3.9
- '@types/react-dom':
- specifier: ^18.0.11
- version: 18.3.0
- '@vitejs/plugin-react-swc':
- specifier: ^3.5.0
- version: 3.8.0(@swc/helpers@0.5.13)(vite@5.4.14(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0))
- autoprefixer:
- specifier: ^10.4.16
- version: 10.4.20(postcss@8.4.47)
- css-loader:
- specifier: ^7.1.2
- version: 7.1.2(webpack@5.98.0)
- postcss:
- specifier: ^8.4.47
- version: 8.4.47
- postcss-loader:
- specifier: ^8.1.1
- version: 8.1.1(postcss@8.4.47)(typescript@5.7.2)(webpack@5.98.0)
- prettier:
- specifier: ^2.2.1
- version: 2.8.8
- shell-quote:
- specifier: ^1.8.1
- version: 1.8.2
- style-loader:
- specifier: ^4.0.0
- version: 4.0.0(webpack@5.98.0)
- tailwindcss:
- specifier: ^3.3.6
- version: 3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
- ts-loader:
- specifier: ^9.5.2
- version: 9.5.2(typescript@5.7.2)(webpack@5.98.0)
- typescript:
- specifier: ^5.0.4
- version: 5.7.2
- vite:
- specifier: ^5.0.7
- version: 5.4.14(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0)
- webpack:
- specifier: ^5.98.0
- version: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
- webpack-cli:
- specifier: ^6.0.1
- version: 6.0.1(webpack@5.98.0)
- webpack-merge:
- specifier: ^6.0.1
- version: 6.0.1
-
apps/storybook:
dependencies:
'@cap/ui-solid':
@@ -1711,10 +1626,6 @@ packages:
'@deno/shim-deno@0.19.2':
resolution: {integrity: sha512-q3VTHl44ad8T2Tw2SpeAvghdGOjlnLPDNO2cpOxwMrBE/PVas6geWpbpIgrM+czOCH0yejp0yi8OaTuB+NU40Q==}
- '@discoveryjs/json-ext@0.6.3':
- resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==}
- engines: {node: '>=14.17.0'}
-
'@effect-ts/core@0.60.5':
resolution: {integrity: sha512-qi1WrtJA90XLMnj2hnUszW9Sx4dXP03ZJtCc5DiUBIOhF4Vw7plfb65/bdBySPoC9s7zy995TdUX1XBSxUkl5w==}
@@ -5039,11 +4950,6 @@ packages:
zod:
optional: true
- '@tailwindcss/forms@0.5.10':
- resolution: {integrity: sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==}
- peerDependencies:
- tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1'
-
'@tailwindcss/typography@0.5.15':
resolution: {integrity: sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==}
peerDependencies:
@@ -5335,9 +5241,6 @@ packages:
'@types/chrome@0.0.176':
resolution: {integrity: sha512-LOveFOMIUhMJjvRzZv5whGBpncP/gdJ4hcxeAqg94wGi6CyKaCmLgFSofgItf85GuLTl/0BQ6J/Y1e8BqZWfEg==}
- '@types/chrome@0.0.309':
- resolution: {integrity: sha512-ZFADzcp8b+roUrux68U8pti4cmNOLJXWkShk8lfxj9SBcjYqpJt7NypBprSJUJDJVakGZgd2Tt00QePIGh7oPA==}
-
'@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
@@ -5365,12 +5268,6 @@ packages:
'@types/dom-webcodecs@0.1.11':
resolution: {integrity: sha512-yPEZ3z7EohrmOxbk/QTAa0yonMFkNkjnVXqbGb7D4rMr+F1dGQ8ZUFxXkyLLJuiICPejZ0AZE9Rrk9wUCczx4A==}
- '@types/eslint-scope@3.7.7':
- resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
-
- '@types/eslint@9.6.1':
- resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==}
-
'@types/estree-jsx@1.0.5':
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
@@ -5743,11 +5640,6 @@ packages:
peerDependencies:
vinxi: ^0.4.3
- '@vitejs/plugin-react-swc@3.8.0':
- resolution: {integrity: sha512-T4sHPvS+DIqDP51ifPqa9XIRAz/kIvIi8oXcnOZZgHmMotgmmdxe/DD5tMFlt5nuIRzT0/QuiwmKlH0503Aapw==}
- peerDependencies:
- vite: ^4 || ^5 || ^6
-
'@vitejs/plugin-react@1.3.2':
resolution: {integrity: sha512-aurBNmMo0kz1O4qRoY+FM4epSA39y3ShWGuqfLRA/3z0oEJAdtoSfgA3aO98/PCCHAqMaduLxIxErWrVKIFzXA==}
engines: {node: '>=12.0.0'}
@@ -5805,76 +5697,6 @@ packages:
'@vitest/utils@2.1.9':
resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==}
- '@webassemblyjs/ast@1.14.1':
- resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
-
- '@webassemblyjs/floating-point-hex-parser@1.13.2':
- resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==}
-
- '@webassemblyjs/helper-api-error@1.13.2':
- resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==}
-
- '@webassemblyjs/helper-buffer@1.14.1':
- resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==}
-
- '@webassemblyjs/helper-numbers@1.13.2':
- resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==}
-
- '@webassemblyjs/helper-wasm-bytecode@1.13.2':
- resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==}
-
- '@webassemblyjs/helper-wasm-section@1.14.1':
- resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==}
-
- '@webassemblyjs/ieee754@1.13.2':
- resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==}
-
- '@webassemblyjs/leb128@1.13.2':
- resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==}
-
- '@webassemblyjs/utf8@1.13.2':
- resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==}
-
- '@webassemblyjs/wasm-edit@1.14.1':
- resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==}
-
- '@webassemblyjs/wasm-gen@1.14.1':
- resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==}
-
- '@webassemblyjs/wasm-opt@1.14.1':
- resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==}
-
- '@webassemblyjs/wasm-parser@1.14.1':
- resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==}
-
- '@webassemblyjs/wast-printer@1.14.1':
- resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==}
-
- '@webpack-cli/configtest@3.0.1':
- resolution: {integrity: sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==}
- engines: {node: '>=18.12.0'}
- peerDependencies:
- webpack: ^5.82.0
- webpack-cli: 6.x.x
-
- '@webpack-cli/info@3.0.1':
- resolution: {integrity: sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==}
- engines: {node: '>=18.12.0'}
- peerDependencies:
- webpack: ^5.82.0
- webpack-cli: 6.x.x
-
- '@webpack-cli/serve@3.0.1':
- resolution: {integrity: sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==}
- engines: {node: '>=18.12.0'}
- peerDependencies:
- webpack: ^5.82.0
- webpack-cli: 6.x.x
- webpack-dev-server: '*'
- peerDependenciesMeta:
- webpack-dev-server:
- optional: true
-
'@workos-inc/node@7.34.0':
resolution: {integrity: sha512-YxUnNQbu40eDrTk+MCsDqwRemp7gH7AhhDRlYY2Oj4i5fvP0nnrg/jxZoKxjO/AayYiVb8tv2quBZzlUXR3WmA==}
engines: {node: '>=16'}
@@ -5883,12 +5705,6 @@ packages:
resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==}
engines: {node: '>=10.0.0'}
- '@xtuc/ieee754@1.2.0':
- resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
-
- '@xtuc/long@4.2.2':
- resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
-
abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
@@ -5963,19 +5779,6 @@ packages:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'}
- ajv-formats@2.1.1:
- resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
- peerDependencies:
- ajv: ^8.0.0
- peerDependenciesMeta:
- ajv:
- optional: true
-
- ajv-keywords@5.1.0:
- resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==}
- peerDependencies:
- ajv: ^8.8.2
-
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
@@ -6334,9 +6137,6 @@ packages:
buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
- builtin-status-codes@3.0.0:
- resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==}
-
busboy@1.6.0:
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
engines: {node: '>=10.16.0'}
@@ -6478,10 +6278,6 @@ packages:
'@chromatic-com/playwright':
optional: true
- chrome-trace-event@1.0.4:
- resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==}
- engines: {node: '>=6.0'}
-
ci-info@3.9.0:
resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
engines: {node: '>=8'}
@@ -6542,10 +6338,6 @@ packages:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
- clone-deep@4.0.1:
- resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==}
- engines: {node: '>=6'}
-
clone@1.0.4:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
@@ -6606,9 +6398,6 @@ packages:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
- colorette@2.0.20:
- resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
-
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
@@ -6620,10 +6409,6 @@ packages:
resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
engines: {node: '>=14'}
- commander@12.1.0:
- resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
- engines: {node: '>=18'}
-
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@@ -6718,12 +6503,6 @@ packages:
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
engines: {node: '>=12.13'}
- copy-webpack-plugin@13.0.0:
- resolution: {integrity: sha512-FgR/h5a6hzJqATDGd9YG41SeDViH+0bkHn6WNXCi5zKAZkeESeSxLySSsFLHqLEVCh0E+rITmCf0dusXWYukeQ==}
- engines: {node: '>= 18.12.0'}
- peerDependencies:
- webpack: ^5.1.0
-
core-js-pure@3.38.1:
resolution: {integrity: sha512-BY8Etc1FZqdw1glX0XNOq2FDwfrg/VGqoZOZCdaL+UmdaqDwQwYXkMJT4t6In+zfEfOJDcM9T0KdbBeJg8KKCQ==}
@@ -6737,15 +6516,6 @@ packages:
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
engines: {node: '>= 0.10'}
- cosmiconfig@9.0.0:
- resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==}
- engines: {node: '>=14'}
- peerDependencies:
- typescript: '>=4.9.5'
- peerDependenciesMeta:
- typescript:
- optional: true
-
crc-32@1.2.2:
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
engines: {node: '>=0.8'}
@@ -6791,18 +6561,6 @@ packages:
uWebSockets.js:
optional: true
- css-loader@7.1.2:
- resolution: {integrity: sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==}
- engines: {node: '>= 18.12.0'}
- peerDependencies:
- '@rspack/core': 0.x || 1.x
- webpack: ^5.27.0
- peerDependenciesMeta:
- '@rspack/core':
- optional: true
- webpack:
- optional: true
-
css.escape@1.5.1:
resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
@@ -7253,19 +7011,10 @@ packages:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
- env-paths@2.2.1:
- resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
- engines: {node: '>=6'}
-
env-paths@3.0.0:
resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
- envinfo@7.14.0:
- resolution: {integrity: sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==}
- engines: {node: '>=4'}
- hasBin: true
-
error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
@@ -7881,10 +7630,6 @@ packages:
resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==}
hasBin: true
- fastest-levenshtein@1.0.16:
- resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==}
- engines: {node: '>= 4.9.1'}
-
fastq@1.17.1:
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
@@ -7897,14 +7642,6 @@ packages:
fd-slicer@1.1.0:
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
- fdir@6.4.3:
- resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==}
- peerDependencies:
- picomatch: ^3 || ^4
- peerDependenciesMeta:
- picomatch:
- optional: true
-
fflate@0.4.8:
resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==}
@@ -7949,10 +7686,6 @@ packages:
resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
engines: {node: ^10.12.0 || >=12.0.0}
- flat@5.0.2:
- resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
- hasBin: true
-
flatted@3.3.1:
resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
@@ -8427,12 +8160,6 @@ packages:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
- icss-utils@5.1.0:
- resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==}
- engines: {node: ^10 || ^12 || >= 14}
- peerDependencies:
- postcss: ^8.1.0
-
ieee754@1.1.13:
resolution: {integrity: sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==}
@@ -8510,10 +8237,6 @@ packages:
resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==}
engines: {node: '>= 0.10'}
- interpret@3.1.1:
- resolution: {integrity: sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==}
- engines: {node: '>=10.13.0'}
-
invariant@2.2.4:
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
@@ -8705,10 +8428,6 @@ packages:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
- is-plain-object@2.0.4:
- resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==}
- engines: {node: '>=0.10.0'}
-
is-plain-object@5.0.0:
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
engines: {node: '>=0.10.0'}
@@ -8811,10 +8530,6 @@ packages:
resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==}
engines: {node: '>=16'}
- isobject@3.0.1:
- resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
- engines: {node: '>=0.10.0'}
-
istanbul-lib-coverage@3.2.2:
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
engines: {node: '>=8'}
@@ -8969,10 +8684,6 @@ packages:
resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- jest-worker@27.5.1:
- resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
- engines: {node: '>= 10.13.0'}
-
jest-worker@29.7.0:
resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -9239,10 +8950,6 @@ packages:
load-script@1.0.0:
resolution: {integrity: sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==}
- loader-runner@4.3.0:
- resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
- engines: {node: '>=6.11.5'}
-
local-pkg@0.5.0:
resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
engines: {node: '>=14'}
@@ -9784,10 +9491,6 @@ packages:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
- mini-svg-data-uri@1.4.4:
- resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
- hasBin: true
-
miniflare@3.20250204.1:
resolution: {integrity: sha512-B4PQi/Ai4d0ZTWahQwsFe5WAfr1j8ISMYxJZTc56g2/btgbX+Go099LmojAZY/fMRLhIYsglcStW8SeW3f/afA==}
engines: {node: '>=16.13'}
@@ -10372,10 +10075,6 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
- picomatch@4.0.2:
- resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
- engines: {node: '>=12'}
-
pify@2.3.0:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
@@ -10437,43 +10136,6 @@ packages:
ts-node:
optional: true
- postcss-loader@8.1.1:
- resolution: {integrity: sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==}
- engines: {node: '>= 18.12.0'}
- peerDependencies:
- '@rspack/core': 0.x || 1.x
- postcss: ^7.0.0 || ^8.0.1
- webpack: ^5.0.0
- peerDependenciesMeta:
- '@rspack/core':
- optional: true
- webpack:
- optional: true
-
- postcss-modules-extract-imports@3.1.0:
- resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==}
- engines: {node: ^10 || ^12 || >= 14}
- peerDependencies:
- postcss: ^8.1.0
-
- postcss-modules-local-by-default@4.2.0:
- resolution: {integrity: sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==}
- engines: {node: ^10 || ^12 || >= 14}
- peerDependencies:
- postcss: ^8.1.0
-
- postcss-modules-scope@3.2.1:
- resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==}
- engines: {node: ^10 || ^12 || >= 14}
- peerDependencies:
- postcss: ^8.1.0
-
- postcss-modules-values@4.0.0:
- resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==}
- engines: {node: ^10 || ^12 || >= 14}
- peerDependencies:
- postcss: ^8.1.0
-
postcss-nested@6.2.0:
resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
engines: {node: '>=12.0'}
@@ -10494,10 +10156,6 @@ packages:
resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
engines: {node: '>=4'}
- postcss-selector-parser@7.1.0:
- resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==}
- engines: {node: '>=4'}
-
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
@@ -10603,9 +10261,6 @@ packages:
punycode@1.3.2:
resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==}
- punycode@1.4.1:
- resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==}
-
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -10884,10 +10539,6 @@ packages:
resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
engines: {node: '>= 0.10'}
- rechoir@0.8.0:
- resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==}
- engines: {node: '>= 10.13.0'}
-
recma-build-jsx@1.0.0:
resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==}
@@ -11121,10 +10772,6 @@ packages:
scheduler@0.25.0:
resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==}
- schema-utils@4.3.0:
- resolution: {integrity: sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==}
- engines: {node: '>= 10.13.0'}
-
scule@1.3.0:
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
@@ -11198,10 +10845,6 @@ packages:
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
- shallow-clone@3.0.1:
- resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==}
- engines: {node: '>=8'}
-
sharp@0.33.5:
resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -11214,10 +10857,6 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
- shell-quote@1.8.2:
- resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==}
- engines: {node: '>= 0.4'}
-
shelljs@0.8.5:
resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==}
engines: {node: '>=4'}
@@ -11427,9 +11066,6 @@ packages:
resolution: {integrity: sha512-FG2KAVQN54T9R6voudiEftehtkXtLO+YVGP2gBPfacEdDQjY++ld7kTbHzpTT/bpCDx7Yq3dqOegLm9arVJfYw==}
hasBin: true
- stream-http@3.2.0:
- resolution: {integrity: sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==}
-
streamsearch@1.1.0:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'}
@@ -11533,12 +11169,6 @@ packages:
strnum@1.0.5:
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
- style-loader@4.0.0:
- resolution: {integrity: sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==}
- engines: {node: '>= 18.12.0'}
- peerDependencies:
- webpack: ^5.27.0
-
style-to-object@0.3.0:
resolution: {integrity: sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==}
@@ -11673,22 +11303,6 @@ packages:
peerDependencies:
solid-js: ^1.8
- terser-webpack-plugin@5.3.14:
- resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==}
- engines: {node: '>= 10.13.0'}
- peerDependencies:
- '@swc/core': '*'
- esbuild: '*'
- uglify-js: '*'
- webpack: ^5.1.0
- peerDependenciesMeta:
- '@swc/core':
- optional: true
- esbuild:
- optional: true
- uglify-js:
- optional: true
-
terser@5.34.0:
resolution: {integrity: sha512-y5NUX+U9HhVsK/zihZwoq4r9dICLyV2jXGOriDAVOeKhq3LKVjgJbGO90FisozXLlJfvjHqgckGmJFBb9KYoWQ==}
engines: {node: '>=10'}
@@ -11739,10 +11353,6 @@ packages:
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
- tinyglobby@0.2.12:
- resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
- engines: {node: '>=12.0.0'}
-
tinygradient@1.1.5:
resolution: {integrity: sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw==}
@@ -11863,13 +11473,6 @@ packages:
esbuild:
optional: true
- ts-loader@9.5.2:
- resolution: {integrity: sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==}
- engines: {node: '>=12.0.0'}
- peerDependencies:
- typescript: '*'
- webpack: ^5.0.0
-
ts-node@10.9.2:
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
hasBin: true
@@ -12288,10 +11891,6 @@ packages:
url@0.10.3:
resolution: {integrity: sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==}
- url@0.11.4:
- resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==}
- engines: {node: '>= 0.4'}
-
urlpattern-polyfill@8.0.2:
resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==}
@@ -12595,10 +12194,6 @@ packages:
walker@1.0.8:
resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
- watchpack@2.4.2:
- resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==}
- engines: {node: '>=10.13.0'}
-
wcwidth@1.0.1:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
@@ -12626,24 +12221,6 @@ packages:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'}
- webpack-cli@6.0.1:
- resolution: {integrity: sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==}
- engines: {node: '>=18.12.0'}
- hasBin: true
- peerDependencies:
- webpack: ^5.82.0
- webpack-bundle-analyzer: '*'
- webpack-dev-server: '*'
- peerDependenciesMeta:
- webpack-bundle-analyzer:
- optional: true
- webpack-dev-server:
- optional: true
-
- webpack-merge@6.0.1:
- resolution: {integrity: sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==}
- engines: {node: '>=18.0.0'}
-
webpack-sources@3.2.3:
resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
engines: {node: '>=10.13.0'}
@@ -12651,16 +12228,6 @@ packages:
webpack-virtual-modules@0.6.2:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
- webpack@5.98.0:
- resolution: {integrity: sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==}
- engines: {node: '>=10.13.0'}
- hasBin: true
- peerDependencies:
- webpack-cli: '*'
- peerDependenciesMeta:
- webpack-cli:
- optional: true
-
whatwg-encoding@3.1.1:
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
engines: {node: '>=18'}
@@ -12717,9 +12284,6 @@ packages:
resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==}
engines: {node: '>=12'}
- wildcard@2.0.1:
- resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==}
-
word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
@@ -12788,10 +12352,6 @@ packages:
xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
- xtend@4.0.2:
- resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
- engines: {node: '>=0.4'}
-
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@@ -14007,8 +13567,6 @@ snapshots:
'@deno/shim-deno-test': 0.5.0
which: 4.0.0
- '@discoveryjs/json-ext@0.6.3': {}
-
'@effect-ts/core@0.60.5':
dependencies:
'@effect-ts/system': 0.57.5
@@ -17142,6 +16700,7 @@ snapshots:
'@swc/core-win32-ia32-msvc': 1.11.8
'@swc/core-win32-x64-msvc': 1.11.8
'@swc/helpers': 0.5.13
+ optional: true
'@swc/counter@0.1.3': {}
@@ -17157,6 +16716,7 @@ snapshots:
'@swc/types@0.1.19':
dependencies:
'@swc/counter': 0.1.3
+ optional: true
'@t3-oss/env-core@0.12.0(typescript@5.7.2)(valibot@1.0.0-rc.1(typescript@5.7.2))(zod@3.24.1)':
optionalDependencies:
@@ -17172,11 +16732,6 @@ snapshots:
valibot: 1.0.0-rc.1(typescript@5.7.2)
zod: 3.24.1
- '@tailwindcss/forms@0.5.10(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)))':
- dependencies:
- mini-svg-data-uri: 1.4.4
- tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2))
-
'@tailwindcss/typography@0.5.15(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2)))':
dependencies:
lodash.castarray: 4.4.0
@@ -17496,11 +17051,6 @@ snapshots:
'@types/filesystem': 0.0.36
'@types/har-format': 1.2.16
- '@types/chrome@0.0.309':
- dependencies:
- '@types/filesystem': 0.0.36
- '@types/har-format': 1.2.16
-
'@types/connect@3.4.38':
dependencies:
'@types/node': 20.16.9
@@ -17530,16 +17080,6 @@ snapshots:
'@types/dom-webcodecs@0.1.11': {}
- '@types/eslint-scope@3.7.7':
- dependencies:
- '@types/eslint': 9.6.1
- '@types/estree': 1.0.6
-
- '@types/eslint@9.6.1':
- dependencies:
- '@types/estree': 1.0.6
- '@types/json-schema': 7.0.15
-
'@types/estree-jsx@1.0.5':
dependencies:
'@types/estree': 1.0.6
@@ -18052,13 +17592,6 @@ snapshots:
recast: 0.23.9
vinxi: 0.4.3(@planetscale/database@1.19.0)(@types/node@22.12.0)(ioredis@5.4.1)(lightningcss@1.29.2)(terser@5.34.0)(webpack-sources@3.2.3)(xml2js@0.6.2)
- '@vitejs/plugin-react-swc@3.8.0(@swc/helpers@0.5.13)(vite@5.4.14(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0))':
- dependencies:
- '@swc/core': 1.11.8(@swc/helpers@0.5.13)
- vite: 5.4.14(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0)
- transitivePeerDependencies:
- - '@swc/helpers'
-
'@vitejs/plugin-react@1.3.2':
dependencies:
'@babel/core': 7.25.2
@@ -18155,97 +17688,6 @@ snapshots:
loupe: 3.1.3
tinyrainbow: 1.2.0
- '@webassemblyjs/ast@1.14.1':
- dependencies:
- '@webassemblyjs/helper-numbers': 1.13.2
- '@webassemblyjs/helper-wasm-bytecode': 1.13.2
-
- '@webassemblyjs/floating-point-hex-parser@1.13.2': {}
-
- '@webassemblyjs/helper-api-error@1.13.2': {}
-
- '@webassemblyjs/helper-buffer@1.14.1': {}
-
- '@webassemblyjs/helper-numbers@1.13.2':
- dependencies:
- '@webassemblyjs/floating-point-hex-parser': 1.13.2
- '@webassemblyjs/helper-api-error': 1.13.2
- '@xtuc/long': 4.2.2
-
- '@webassemblyjs/helper-wasm-bytecode@1.13.2': {}
-
- '@webassemblyjs/helper-wasm-section@1.14.1':
- dependencies:
- '@webassemblyjs/ast': 1.14.1
- '@webassemblyjs/helper-buffer': 1.14.1
- '@webassemblyjs/helper-wasm-bytecode': 1.13.2
- '@webassemblyjs/wasm-gen': 1.14.1
-
- '@webassemblyjs/ieee754@1.13.2':
- dependencies:
- '@xtuc/ieee754': 1.2.0
-
- '@webassemblyjs/leb128@1.13.2':
- dependencies:
- '@xtuc/long': 4.2.2
-
- '@webassemblyjs/utf8@1.13.2': {}
-
- '@webassemblyjs/wasm-edit@1.14.1':
- dependencies:
- '@webassemblyjs/ast': 1.14.1
- '@webassemblyjs/helper-buffer': 1.14.1
- '@webassemblyjs/helper-wasm-bytecode': 1.13.2
- '@webassemblyjs/helper-wasm-section': 1.14.1
- '@webassemblyjs/wasm-gen': 1.14.1
- '@webassemblyjs/wasm-opt': 1.14.1
- '@webassemblyjs/wasm-parser': 1.14.1
- '@webassemblyjs/wast-printer': 1.14.1
-
- '@webassemblyjs/wasm-gen@1.14.1':
- dependencies:
- '@webassemblyjs/ast': 1.14.1
- '@webassemblyjs/helper-wasm-bytecode': 1.13.2
- '@webassemblyjs/ieee754': 1.13.2
- '@webassemblyjs/leb128': 1.13.2
- '@webassemblyjs/utf8': 1.13.2
-
- '@webassemblyjs/wasm-opt@1.14.1':
- dependencies:
- '@webassemblyjs/ast': 1.14.1
- '@webassemblyjs/helper-buffer': 1.14.1
- '@webassemblyjs/wasm-gen': 1.14.1
- '@webassemblyjs/wasm-parser': 1.14.1
-
- '@webassemblyjs/wasm-parser@1.14.1':
- dependencies:
- '@webassemblyjs/ast': 1.14.1
- '@webassemblyjs/helper-api-error': 1.13.2
- '@webassemblyjs/helper-wasm-bytecode': 1.13.2
- '@webassemblyjs/ieee754': 1.13.2
- '@webassemblyjs/leb128': 1.13.2
- '@webassemblyjs/utf8': 1.13.2
-
- '@webassemblyjs/wast-printer@1.14.1':
- dependencies:
- '@webassemblyjs/ast': 1.14.1
- '@xtuc/long': 4.2.2
-
- '@webpack-cli/configtest@3.0.1(webpack-cli@6.0.1)(webpack@5.98.0)':
- dependencies:
- webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
- webpack-cli: 6.0.1(webpack@5.98.0)
-
- '@webpack-cli/info@3.0.1(webpack-cli@6.0.1)(webpack@5.98.0)':
- dependencies:
- webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
- webpack-cli: 6.0.1(webpack@5.98.0)
-
- '@webpack-cli/serve@3.0.1(webpack-cli@6.0.1)(webpack@5.98.0)':
- dependencies:
- webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
- webpack-cli: 6.0.1(webpack@5.98.0)
-
'@workos-inc/node@7.34.0(express@4.21.0)(next@14.2.3(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))':
dependencies:
iron-session: 6.3.1(express@4.21.0)(next@14.2.3(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
@@ -18258,10 +17700,6 @@ snapshots:
'@xmldom/xmldom@0.8.10': {}
- '@xtuc/ieee754@1.2.0': {}
-
- '@xtuc/long@4.2.2': {}
-
abbrev@1.1.1: {}
abbrev@2.0.0: {}
@@ -18340,15 +17778,6 @@ snapshots:
clean-stack: 2.2.0
indent-string: 4.0.0
- ajv-formats@2.1.1(ajv@8.17.1):
- optionalDependencies:
- ajv: 8.17.1
-
- ajv-keywords@5.1.0(ajv@8.17.1):
- dependencies:
- ajv: 8.17.1
- fast-deep-equal: 3.1.3
-
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
@@ -18799,8 +18228,6 @@ snapshots:
base64-js: 1.5.1
ieee754: 1.2.1
- builtin-status-codes@3.0.0: {}
-
busboy@1.6.0:
dependencies:
streamsearch: 1.1.0
@@ -18971,8 +18398,6 @@ snapshots:
chromatic@11.10.4: {}
- chrome-trace-event@1.0.4: {}
-
ci-info@3.9.0: {}
citty@0.1.6:
@@ -19027,12 +18452,6 @@ snapshots:
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
- clone-deep@4.0.1:
- dependencies:
- is-plain-object: 2.0.4
- kind-of: 6.0.3
- shallow-clone: 3.0.1
-
clone@1.0.4: {}
clsx@1.2.1: {}
@@ -19083,8 +18502,6 @@ snapshots:
color-string: 1.9.1
optional: true
- colorette@2.0.20: {}
-
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
@@ -19093,8 +18510,6 @@ snapshots:
commander@10.0.1: {}
- commander@12.1.0: {}
-
commander@2.20.3: {}
commander@4.1.1: {}
@@ -19187,15 +18602,6 @@ snapshots:
dependencies:
is-what: 4.1.16
- copy-webpack-plugin@13.0.0(webpack@5.98.0):
- dependencies:
- glob-parent: 6.0.2
- normalize-path: 3.0.0
- schema-utils: 4.3.0
- serialize-javascript: 6.0.2
- tinyglobby: 0.2.12
- webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
-
core-js-pure@3.38.1: {}
core-js@3.40.0: {}
@@ -19207,15 +18613,6 @@ snapshots:
object-assign: 4.1.1
vary: 1.1.2
- cosmiconfig@9.0.0(typescript@5.7.2):
- dependencies:
- env-paths: 2.2.1
- import-fresh: 3.3.0
- js-yaml: 4.1.0
- parse-json: 5.2.0
- optionalDependencies:
- typescript: 5.7.2
-
crc-32@1.2.2: {}
crc32-stream@6.0.0:
@@ -19266,19 +18663,6 @@ snapshots:
crossws@0.2.4: {}
- css-loader@7.1.2(webpack@5.98.0):
- dependencies:
- icss-utils: 5.1.0(postcss@8.4.47)
- postcss: 8.4.47
- postcss-modules-extract-imports: 3.1.0(postcss@8.4.47)
- postcss-modules-local-by-default: 4.2.0(postcss@8.4.47)
- postcss-modules-scope: 3.2.1(postcss@8.4.47)
- postcss-modules-values: 4.0.0(postcss@8.4.47)
- postcss-value-parser: 4.2.0
- semver: 7.6.3
- optionalDependencies:
- webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
-
css.escape@1.5.1: {}
cssesc@3.0.0: {}
@@ -19627,12 +19011,8 @@ snapshots:
entities@4.5.0: {}
- env-paths@2.2.1: {}
-
env-paths@3.0.0: {}
- envinfo@7.14.0: {}
-
error-ex@1.3.2:
dependencies:
is-arrayish: 0.2.1
@@ -20221,7 +19601,7 @@ snapshots:
'@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.7.2)
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1)
+ eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(eslint@8.57.1))(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
@@ -20720,8 +20100,6 @@ snapshots:
dependencies:
strnum: 1.0.5
- fastest-levenshtein@1.0.16: {}
-
fastq@1.17.1:
dependencies:
reusify: 1.0.4
@@ -20738,10 +20116,6 @@ snapshots:
dependencies:
pend: 1.2.0
- fdir@6.4.3(picomatch@4.0.2):
- optionalDependencies:
- picomatch: 4.0.2
-
fflate@0.4.8: {}
figures@3.2.0:
@@ -20794,8 +20168,6 @@ snapshots:
keyv: 4.5.4
rimraf: 3.0.2
- flat@5.0.2: {}
-
flatted@3.3.1: {}
fluent-ffmpeg@2.1.3:
@@ -21410,10 +20782,6 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
- icss-utils@5.1.0(postcss@8.4.47):
- dependencies:
- postcss: 8.4.47
-
ieee754@1.1.13: {}
ieee754@1.2.1: {}
@@ -21499,8 +20867,6 @@ snapshots:
interpret@1.4.0: {}
- interpret@3.1.1: {}
-
invariant@2.2.4:
dependencies:
loose-envify: 1.4.0
@@ -21666,10 +21032,6 @@ snapshots:
is-plain-obj@4.1.0: {}
- is-plain-object@2.0.4:
- dependencies:
- isobject: 3.0.1
-
is-plain-object@5.0.0: {}
is-potential-custom-element-name@1.0.1: {}
@@ -21754,8 +21116,6 @@ snapshots:
isexe@3.1.1: {}
- isobject@3.0.1: {}
-
istanbul-lib-coverage@3.2.2: {}
istanbul-lib-instrument@5.2.1:
@@ -22114,12 +21474,6 @@ snapshots:
jest-util: 29.7.0
string-length: 4.0.2
- jest-worker@27.5.1:
- dependencies:
- '@types/node': 20.16.9
- merge-stream: 2.0.0
- supports-color: 8.1.1
-
jest-worker@29.7.0:
dependencies:
'@types/node': 20.16.9
@@ -22378,8 +21732,6 @@ snapshots:
load-script@1.0.0: {}
- loader-runner@4.3.0: {}
-
local-pkg@0.5.0:
dependencies:
mlly: 1.7.1
@@ -23370,8 +22722,6 @@ snapshots:
min-indent@1.0.1: {}
- mini-svg-data-uri@1.4.4: {}
-
miniflare@3.20250204.1:
dependencies:
'@cspotcode/source-map-support': 0.8.1
@@ -24129,8 +23479,6 @@ snapshots:
picomatch@2.3.1: {}
- picomatch@4.0.2: {}
-
pify@2.3.0: {}
pirates@4.0.6: {}
@@ -24215,38 +23563,6 @@ snapshots:
postcss: 8.5.3
ts-node: 10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@22.12.0)(typescript@4.9.5)
- postcss-loader@8.1.1(postcss@8.4.47)(typescript@5.7.2)(webpack@5.98.0):
- dependencies:
- cosmiconfig: 9.0.0(typescript@5.7.2)
- jiti: 1.21.6
- postcss: 8.4.47
- semver: 7.6.3
- optionalDependencies:
- webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
- transitivePeerDependencies:
- - typescript
-
- postcss-modules-extract-imports@3.1.0(postcss@8.4.47):
- dependencies:
- postcss: 8.4.47
-
- postcss-modules-local-by-default@4.2.0(postcss@8.4.47):
- dependencies:
- icss-utils: 5.1.0(postcss@8.4.47)
- postcss: 8.4.47
- postcss-selector-parser: 7.1.0
- postcss-value-parser: 4.2.0
-
- postcss-modules-scope@3.2.1(postcss@8.4.47):
- dependencies:
- postcss: 8.4.47
- postcss-selector-parser: 7.1.0
-
- postcss-modules-values@4.0.0(postcss@8.4.47):
- dependencies:
- icss-utils: 5.1.0(postcss@8.4.47)
- postcss: 8.4.47
-
postcss-nested@6.2.0(postcss@8.4.47):
dependencies:
postcss: 8.4.47
@@ -24271,11 +23587,6 @@ snapshots:
cssesc: 3.0.0
util-deprecate: 1.0.2
- postcss-selector-parser@7.1.0:
- dependencies:
- cssesc: 3.0.0
- util-deprecate: 1.0.2
-
postcss-value-parser@4.2.0: {}
postcss@8.4.31:
@@ -24401,8 +23712,6 @@ snapshots:
punycode@1.3.2: {}
- punycode@1.4.1: {}
-
punycode@2.3.1: {}
pure-rand@6.1.0: {}
@@ -24715,10 +24024,6 @@ snapshots:
dependencies:
resolve: 1.22.8
- rechoir@0.8.0:
- dependencies:
- resolve: 1.22.8
-
recma-build-jsx@1.0.0:
dependencies:
'@types/estree': 1.0.6
@@ -25056,13 +24361,6 @@ snapshots:
scheduler@0.25.0: {}
- schema-utils@4.3.0:
- dependencies:
- '@types/json-schema': 7.0.15
- ajv: 8.17.1
- ajv-formats: 2.1.1(ajv@8.17.1)
- ajv-keywords: 5.1.0(ajv@8.17.1)
-
scule@1.3.0: {}
section-matter@1.0.0:
@@ -25152,10 +24450,6 @@ snapshots:
setprototypeof@1.2.0: {}
- shallow-clone@3.0.1:
- dependencies:
- kind-of: 6.0.3
-
sharp@0.33.5:
dependencies:
color: 4.2.3
@@ -25189,8 +24483,6 @@ snapshots:
shebang-regex@3.0.0: {}
- shell-quote@1.8.2: {}
-
shelljs@0.8.5:
dependencies:
glob: 7.2.3
@@ -25405,13 +24697,6 @@ snapshots:
- supports-color
- utf-8-validate
- stream-http@3.2.0:
- dependencies:
- builtin-status-codes: 3.0.0
- inherits: 2.0.4
- readable-stream: 3.6.2
- xtend: 4.0.2
-
streamsearch@1.1.0: {}
streamx@2.20.1:
@@ -25537,10 +24822,6 @@ snapshots:
strnum@1.0.5: {}
- style-loader@4.0.0(webpack@5.98.0):
- dependencies:
- webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
-
style-to-object@0.3.0:
dependencies:
inline-style-parser: 0.1.1
@@ -25766,17 +25047,6 @@ snapshots:
solid-js: 1.9.3
solid-use: 0.9.0(solid-js@1.9.3)
- terser-webpack-plugin@5.3.14(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack@5.98.0):
- dependencies:
- '@jridgewell/trace-mapping': 0.3.25
- jest-worker: 27.5.1
- schema-utils: 4.3.0
- serialize-javascript: 6.0.2
- terser: 5.34.0
- webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
- optionalDependencies:
- '@swc/core': 1.11.8(@swc/helpers@0.5.13)
-
terser@5.34.0:
dependencies:
'@jridgewell/source-map': 0.3.6
@@ -25825,11 +25095,6 @@ snapshots:
tinyexec@0.3.2: {}
- tinyglobby@0.2.12:
- dependencies:
- fdir: 6.4.3(picomatch@4.0.2)
- picomatch: 4.0.2
-
tinygradient@1.1.5:
dependencies:
'@types/tinycolor2': 1.4.6
@@ -25926,16 +25191,6 @@ snapshots:
'@jest/types': 29.6.3
babel-jest: 29.7.0(@babel/core@7.25.2)
- ts-loader@9.5.2(typescript@5.7.2)(webpack@5.98.0):
- dependencies:
- chalk: 4.1.2
- enhanced-resolve: 5.17.1
- micromatch: 4.0.8
- semver: 7.6.3
- source-map: 0.7.4
- typescript: 5.7.2
- webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
-
ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.13))(@types/node@20.16.9)(typescript@5.7.2):
dependencies:
'@cspotcode/source-map-support': 0.8.1
@@ -26404,11 +25659,6 @@ snapshots:
punycode: 1.3.2
querystring: 0.2.0
- url@0.11.4:
- dependencies:
- punycode: 1.4.1
- qs: 6.13.0
-
urlpattern-polyfill@8.0.2: {}
use-callback-ref@1.3.2(@types/react@18.3.9)(react@18.3.1):
@@ -26691,17 +25941,6 @@ snapshots:
lightningcss: 1.29.2
terser: 5.34.0
- vite@5.4.14(@types/node@20.16.9)(lightningcss@1.29.2)(terser@5.34.0):
- dependencies:
- esbuild: 0.21.5
- postcss: 8.4.47
- rollup: 4.22.5
- optionalDependencies:
- '@types/node': 20.16.9
- fsevents: 2.3.3
- lightningcss: 1.29.2
- terser: 5.34.0
-
vite@5.4.14(@types/node@22.12.0)(lightningcss@1.29.2)(terser@5.34.0):
dependencies:
esbuild: 0.21.5
@@ -26772,11 +26011,6 @@ snapshots:
dependencies:
makeerror: 1.0.12
- watchpack@2.4.2:
- dependencies:
- glob-to-regexp: 0.4.1
- graceful-fs: 4.2.11
-
wcwidth@1.0.1:
dependencies:
defaults: 1.0.4
@@ -26801,65 +26035,11 @@ snapshots:
webidl-conversions@7.0.0: {}
- webpack-cli@6.0.1(webpack@5.98.0):
- dependencies:
- '@discoveryjs/json-ext': 0.6.3
- '@webpack-cli/configtest': 3.0.1(webpack-cli@6.0.1)(webpack@5.98.0)
- '@webpack-cli/info': 3.0.1(webpack-cli@6.0.1)(webpack@5.98.0)
- '@webpack-cli/serve': 3.0.1(webpack-cli@6.0.1)(webpack@5.98.0)
- colorette: 2.0.20
- commander: 12.1.0
- cross-spawn: 7.0.6
- envinfo: 7.14.0
- fastest-levenshtein: 1.0.16
- import-local: 3.2.0
- interpret: 3.1.1
- rechoir: 0.8.0
- webpack: 5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1)
- webpack-merge: 6.0.1
-
- webpack-merge@6.0.1:
- dependencies:
- clone-deep: 4.0.1
- flat: 5.0.2
- wildcard: 2.0.1
-
- webpack-sources@3.2.3: {}
+ webpack-sources@3.2.3:
+ optional: true
webpack-virtual-modules@0.6.2: {}
- webpack@5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack-cli@6.0.1):
- dependencies:
- '@types/eslint-scope': 3.7.7
- '@types/estree': 1.0.6
- '@webassemblyjs/ast': 1.14.1
- '@webassemblyjs/wasm-edit': 1.14.1
- '@webassemblyjs/wasm-parser': 1.14.1
- acorn: 8.14.0
- browserslist: 4.24.0
- chrome-trace-event: 1.0.4
- enhanced-resolve: 5.17.1
- es-module-lexer: 1.5.4
- eslint-scope: 5.1.1
- events: 3.3.0
- glob-to-regexp: 0.4.1
- graceful-fs: 4.2.11
- json-parse-even-better-errors: 2.3.1
- loader-runner: 4.3.0
- mime-types: 2.1.35
- neo-async: 2.6.2
- schema-utils: 4.3.0
- tapable: 2.2.1
- terser-webpack-plugin: 5.3.14(@swc/core@1.11.8(@swc/helpers@0.5.13))(webpack@5.98.0)
- watchpack: 2.4.2
- webpack-sources: 3.2.3
- optionalDependencies:
- webpack-cli: 6.0.1(webpack@5.98.0)
- transitivePeerDependencies:
- - '@swc/core'
- - esbuild
- - uglify-js
-
whatwg-encoding@3.1.1:
dependencies:
iconv-lite: 0.6.3
@@ -26939,8 +26119,6 @@ snapshots:
dependencies:
string-width: 5.1.2
- wildcard@2.0.1: {}
-
word-wrap@1.2.5: {}
wordwrap@1.0.0: {}
@@ -27010,8 +26188,6 @@ snapshots:
xmlchars@2.2.0: {}
- xtend@4.0.2: {}
-
y18n@5.0.8: {}
yallist@3.1.1: {}
From 3c4fe292aece350fe9555653d3538bff1f70a273 Mon Sep 17 00:00:00 2001
From: neo773
Date: Sun, 16 Mar 2025 01:37:25 +0530
Subject: [PATCH 03/36] fix: update pnpm
---
pnpm-lock.yaml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2a93da47d..624e24390 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -6,7 +6,7 @@ settings:
patchedDependencies:
'@kobalte/core@0.13.7':
- hash: kzigaefucz2gzue42hjqb5kxba
+ hash: daf56e70aa12211e66d61eca48f0538cc1963c967982714f3abc09110083cf0d
path: patches/@kobalte__core@0.13.7.patch
importers:
@@ -60,7 +60,7 @@ importers:
version: 0.0.14
'@kobalte/core':
specifier: ^0.13.7
- version: 0.13.7(patch_hash=kzigaefucz2gzue42hjqb5kxba)(solid-js@1.9.3)
+ version: 0.13.7(patch_hash=daf56e70aa12211e66d61eca48f0538cc1963c967982714f3abc09110083cf0d)(solid-js@1.9.3)
'@solid-primitives/bounds':
specifier: ^0.0.122
version: 0.0.122(solid-js@1.9.3)
@@ -959,7 +959,7 @@ importers:
dependencies:
'@kobalte/core':
specifier: ^0.13.7
- version: 0.13.7(patch_hash=kzigaefucz2gzue42hjqb5kxba)(solid-js@1.9.3)
+ version: 0.13.7(patch_hash=daf56e70aa12211e66d61eca48f0538cc1963c967982714f3abc09110083cf0d)(solid-js@1.9.3)
cva:
specifier: npm:class-variance-authority@^0.7.0
version: class-variance-authority@0.7.0
@@ -14598,7 +14598,7 @@ snapshots:
dependencies:
tslib: 2.8.1
- '@kobalte/core@0.13.7(patch_hash=kzigaefucz2gzue42hjqb5kxba)(solid-js@1.9.3)':
+ '@kobalte/core@0.13.7(patch_hash=daf56e70aa12211e66d61eca48f0538cc1963c967982714f3abc09110083cf0d)(solid-js@1.9.3)':
dependencies:
'@floating-ui/dom': 1.6.12
'@internationalized/date': 3.5.6
From a837326c6e765fe9264b9c1da79cf2191aea9922 Mon Sep 17 00:00:00 2001
From: Brendan Allan
Date: Mon, 7 Apr 2025 22:47:20 +0800
Subject: [PATCH 04/36] fix env handling + remove bun
---
.gitignore | 1 -
apps/loom-importer-extension/bun.lock | 255 ----------------------
apps/loom-importer-extension/package.json | 8 +-
3 files changed, 4 insertions(+), 260 deletions(-)
delete mode 100644 apps/loom-importer-extension/bun.lock
diff --git a/.gitignore b/.gitignore
index e675807f8..e3a11dd6c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,7 +29,6 @@ target
*.sln
*.sw?
.turbo
-pnpm-lock.yaml
.zed
.output
.vinxi
diff --git a/apps/loom-importer-extension/bun.lock b/apps/loom-importer-extension/bun.lock
deleted file mode 100644
index dd6402710..000000000
--- a/apps/loom-importer-extension/bun.lock
+++ /dev/null
@@ -1,255 +0,0 @@
-{
- "lockfileVersion": 1,
- "workspaces": {
- "": {
- "name": "vite-project",
- "dependencies": {
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
- "react-frame-component": "^5.2.1",
- "vite-tsconfig-paths": "^3.3.17",
- },
- "devDependencies": {
- "@types/chrome": "^0.0.176",
- "@types/react": "^19.0.10",
- "@types/react-dom": "^19.0.4",
- "@vitejs/plugin-react": "^1.0.7",
- "typescript": "^4.4.4",
- "vite": "^2.7.2",
- },
- },
- },
- "packages": {
- "@babel/code-frame": ["@babel/code-frame@7.16.7", "", { "dependencies": { "@babel/highlight": "^7.16.7" } }, "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg=="],
-
- "@babel/compat-data": ["@babel/compat-data@7.16.8", "", {}, "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q=="],
-
- "@babel/core": ["@babel/core@7.16.12", "", { "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", "@babel/helper-compilation-targets": "^7.16.7", "@babel/helper-module-transforms": "^7.16.7", "@babel/helpers": "^7.16.7", "@babel/parser": "^7.16.12", "@babel/template": "^7.16.7", "@babel/traverse": "^7.16.10", "@babel/types": "^7.16.8", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.1.2", "semver": "^6.3.0", "source-map": "^0.5.0" } }, "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg=="],
-
- "@babel/generator": ["@babel/generator@7.16.8", "", { "dependencies": { "@babel/types": "^7.16.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw=="],
-
- "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.16.7", "", { "dependencies": { "@babel/types": "^7.16.7" } }, "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw=="],
-
- "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.16.7", "", { "dependencies": { "@babel/compat-data": "^7.16.4", "@babel/helper-validator-option": "^7.16.7", "browserslist": "^4.17.5", "semver": "^6.3.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA=="],
-
- "@babel/helper-environment-visitor": ["@babel/helper-environment-visitor@7.16.7", "", { "dependencies": { "@babel/types": "^7.16.7" } }, "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag=="],
-
- "@babel/helper-function-name": ["@babel/helper-function-name@7.16.7", "", { "dependencies": { "@babel/helper-get-function-arity": "^7.16.7", "@babel/template": "^7.16.7", "@babel/types": "^7.16.7" } }, "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA=="],
-
- "@babel/helper-get-function-arity": ["@babel/helper-get-function-arity@7.16.7", "", { "dependencies": { "@babel/types": "^7.16.7" } }, "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw=="],
-
- "@babel/helper-hoist-variables": ["@babel/helper-hoist-variables@7.16.7", "", { "dependencies": { "@babel/types": "^7.16.7" } }, "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg=="],
-
- "@babel/helper-module-imports": ["@babel/helper-module-imports@7.16.7", "", { "dependencies": { "@babel/types": "^7.16.7" } }, "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg=="],
-
- "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.16.7", "", { "dependencies": { "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", "@babel/helper-simple-access": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", "@babel/helper-validator-identifier": "^7.16.7", "@babel/template": "^7.16.7", "@babel/traverse": "^7.16.7", "@babel/types": "^7.16.7" } }, "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng=="],
-
- "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.16.7", "", {}, "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA=="],
-
- "@babel/helper-simple-access": ["@babel/helper-simple-access@7.16.7", "", { "dependencies": { "@babel/types": "^7.16.7" } }, "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g=="],
-
- "@babel/helper-split-export-declaration": ["@babel/helper-split-export-declaration@7.16.7", "", { "dependencies": { "@babel/types": "^7.16.7" } }, "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw=="],
-
- "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.16.7", "", {}, "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw=="],
-
- "@babel/helper-validator-option": ["@babel/helper-validator-option@7.16.7", "", {}, "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ=="],
-
- "@babel/helpers": ["@babel/helpers@7.16.7", "", { "dependencies": { "@babel/template": "^7.16.7", "@babel/traverse": "^7.16.7", "@babel/types": "^7.16.7" } }, "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw=="],
-
- "@babel/highlight": ["@babel/highlight@7.16.10", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw=="],
-
- "@babel/parser": ["@babel/parser@7.16.12", "", { "bin": { "parser": "bin/babel-parser.js" } }, "sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A=="],
-
- "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.16.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q=="],
-
- "@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.16.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-jsx": "^7.16.7", "@babel/types": "^7.16.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8D16ye66fxiE8m890w0BpPpngG9o9OVBBy0gH2E+2AR7qMR2ZpTYJEqLxAsoroenMId0p/wMW+Blc0meDgu0Ag=="],
-
- "@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.16.7", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.16.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A=="],
-
- "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.16.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oe5VuWs7J9ilH3BCCApGoYjHoSO48vkjX2CbA5bFVhIuO2HKxA3vyF7rleA4o6/4rTDbk6r8hBW7Ul8E+UZrpA=="],
-
- "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.16.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-rONFiQz9vgbsnaMtQlZCjIRwhJvlrPET8TabIUK2hzlXw9B9s2Ieaxte1SCOOXMbWRHodbKixNf3BLcWVOQ8Bw=="],
-
- "@babel/template": ["@babel/template@7.16.7", "", { "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/parser": "^7.16.7", "@babel/types": "^7.16.7" } }, "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w=="],
-
- "@babel/traverse": ["@babel/traverse@7.16.10", "", { "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-function-name": "^7.16.7", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", "@babel/parser": "^7.16.10", "@babel/types": "^7.16.8", "debug": "^4.1.0", "globals": "^11.1.0" } }, "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw=="],
-
- "@babel/types": ["@babel/types@7.16.8", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" } }, "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg=="],
-
- "@cush/relative": ["@cush/relative@1.0.0", "", {}, "sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA=="],
-
- "@rollup/pluginutils": ["@rollup/pluginutils@4.1.2", "", { "dependencies": { "estree-walker": "^2.0.1", "picomatch": "^2.2.2" } }, "sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ=="],
-
- "@types/chrome": ["@types/chrome@0.0.176", "", { "dependencies": { "@types/filesystem": "*", "@types/har-format": "*" } }, "sha512-LOveFOMIUhMJjvRzZv5whGBpncP/gdJ4hcxeAqg94wGi6CyKaCmLgFSofgItf85GuLTl/0BQ6J/Y1e8BqZWfEg=="],
-
- "@types/filesystem": ["@types/filesystem@0.0.32", "", { "dependencies": { "@types/filewriter": "*" } }, "sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ=="],
-
- "@types/filewriter": ["@types/filewriter@0.0.29", "", {}, "sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ=="],
-
- "@types/har-format": ["@types/har-format@1.2.8", "", {}, "sha512-OP6L9VuZNdskgNN3zFQQ54ceYD8OLq5IbqO4VK91ORLfOm7WdT/CiT/pHEBSQEqCInJ2y3O6iCm/zGtPElpgJQ=="],
-
- "@types/json5": ["@types/json5@0.0.29", "", {}, ""],
-
- "@types/react": ["@types/react@19.0.10", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g=="],
-
- "@types/react-dom": ["@types/react-dom@19.0.4", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg=="],
-
- "@vitejs/plugin-react": ["@vitejs/plugin-react@1.1.4", "", { "dependencies": { "@babel/core": "^7.16.5", "@babel/plugin-transform-react-jsx": "^7.16.5", "@babel/plugin-transform-react-jsx-development": "^7.16.5", "@babel/plugin-transform-react-jsx-self": "^7.16.5", "@babel/plugin-transform-react-jsx-source": "^7.16.5", "@rollup/pluginutils": "^4.1.2", "react-refresh": "^0.11.0", "resolve": "^1.20.0" } }, "sha512-cMUBDonNY8PPeHWjIrYKbRn6bLSunh/Ixo2XLLBd3DM0uYBZft+c+04zkGhhN1lAwvoRKJ2FdtvhGhPgViHc6w=="],
-
- "ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
-
- "browserslist": ["browserslist@4.19.1", "", { "dependencies": { "caniuse-lite": "^1.0.30001286", "electron-to-chromium": "^1.4.17", "escalade": "^3.1.1", "node-releases": "^2.0.1", "picocolors": "^1.0.0" }, "bin": "cli.js" }, "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A=="],
-
- "caniuse-lite": ["caniuse-lite@1.0.30001301", "", {}, "sha512-csfD/GpHMqgEL3V3uIgosvh+SVIQvCh43SNu9HRbP1lnxkKm1kjDG4f32PP571JplkLjfS+mg2p1gxR7MYrrIA=="],
-
- "chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
-
- "color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
-
- "color-name": ["color-name@1.1.3", "", {}, ""],
-
- "convert-source-map": ["convert-source-map@1.8.0", "", { "dependencies": { "safe-buffer": "~5.1.1" } }, "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA=="],
-
- "csstype": ["csstype@3.0.10", "", {}, "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA=="],
-
- "debug": ["debug@4.3.3", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q=="],
-
- "electron-to-chromium": ["electron-to-chromium@1.4.51", "", {}, "sha512-JNEmcYl3mk1tGQmy0EvL5eik/CKSBuzAyGP0QFdG6LIgxQe3II0BL1m2zKc2MZMf3uGqHWE1TFddJML0RpjSHQ=="],
-
- "esbuild": ["esbuild@0.13.15", "", { "optionalDependencies": { "esbuild-android-arm64": "0.13.15", "esbuild-darwin-64": "0.13.15", "esbuild-darwin-arm64": "0.13.15", "esbuild-freebsd-64": "0.13.15", "esbuild-freebsd-arm64": "0.13.15", "esbuild-linux-32": "0.13.15", "esbuild-linux-64": "0.13.15", "esbuild-linux-arm": "0.13.15", "esbuild-linux-arm64": "0.13.15", "esbuild-linux-mips64le": "0.13.15", "esbuild-linux-ppc64le": "0.13.15", "esbuild-netbsd-64": "0.13.15", "esbuild-openbsd-64": "0.13.15", "esbuild-sunos-64": "0.13.15", "esbuild-windows-32": "0.13.15", "esbuild-windows-64": "0.13.15", "esbuild-windows-arm64": "0.13.15" }, "bin": "bin/esbuild" }, "sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw=="],
-
- "esbuild-android-arm64": ["esbuild-android-arm64@0.13.15", "", { "os": "android", "cpu": "arm64" }, "sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg=="],
-
- "esbuild-darwin-64": ["esbuild-darwin-64@0.13.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ=="],
-
- "esbuild-darwin-arm64": ["esbuild-darwin-arm64@0.13.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ=="],
-
- "esbuild-freebsd-64": ["esbuild-freebsd-64@0.13.15", "", { "os": "freebsd", "cpu": "x64" }, "sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA=="],
-
- "esbuild-freebsd-arm64": ["esbuild-freebsd-arm64@0.13.15", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ=="],
-
- "esbuild-linux-32": ["esbuild-linux-32@0.13.15", "", { "os": "linux", "cpu": "ia32" }, "sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g=="],
-
- "esbuild-linux-64": ["esbuild-linux-64@0.13.15", "", { "os": "linux", "cpu": "x64" }, "sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA=="],
-
- "esbuild-linux-arm": ["esbuild-linux-arm@0.13.15", "", { "os": "linux", "cpu": "arm" }, "sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA=="],
-
- "esbuild-linux-arm64": ["esbuild-linux-arm64@0.13.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA=="],
-
- "esbuild-linux-mips64le": ["esbuild-linux-mips64le@0.13.15", "", { "os": "linux", "cpu": "none" }, "sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg=="],
-
- "esbuild-linux-ppc64le": ["esbuild-linux-ppc64le@0.13.15", "", { "os": "linux", "cpu": "ppc64" }, "sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ=="],
-
- "esbuild-netbsd-64": ["esbuild-netbsd-64@0.13.15", "", { "os": "none", "cpu": "x64" }, "sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w=="],
-
- "esbuild-openbsd-64": ["esbuild-openbsd-64@0.13.15", "", { "os": "openbsd", "cpu": "x64" }, "sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g=="],
-
- "esbuild-sunos-64": ["esbuild-sunos-64@0.13.15", "", { "os": "sunos", "cpu": "x64" }, "sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw=="],
-
- "esbuild-windows-32": ["esbuild-windows-32@0.13.15", "", { "os": "win32", "cpu": "ia32" }, "sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw=="],
-
- "esbuild-windows-64": ["esbuild-windows-64@0.13.15", "", { "os": "win32", "cpu": "x64" }, "sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ=="],
-
- "esbuild-windows-arm64": ["esbuild-windows-arm64@0.13.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA=="],
-
- "escalade": ["escalade@3.1.1", "", {}, "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="],
-
- "escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, ""],
-
- "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
-
- "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
-
- "function-bind": ["function-bind@1.1.1", "", {}, "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="],
-
- "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
-
- "glob-regex": ["glob-regex@0.3.2", "", {}, "sha512-m5blUd3/OqDTWwzBBtWBPrGlAzatRywHameHeekAZyZrskYouOGdNB8T/q6JucucvJXtOuyHIn0/Yia7iDasDw=="],
-
- "globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
-
- "globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
-
- "has": ["has@1.0.3", "", { "dependencies": { "function-bind": "^1.1.1" } }, "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw=="],
-
- "has-flag": ["has-flag@3.0.0", "", {}, ""],
-
- "is-core-module": ["is-core-module@2.8.1", "", { "dependencies": { "has": "^1.0.3" } }, "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA=="],
-
- "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
-
- "jsesc": ["jsesc@2.5.2", "", { "bin": "bin/jsesc" }, "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="],
-
- "json5": ["json5@2.2.0", "", { "dependencies": { "minimist": "^1.2.5" }, "bin": "lib/cli.js" }, "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA=="],
-
- "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": "cli.js" }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
-
- "minimist": ["minimist@1.2.5", "", {}, "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="],
-
- "ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
-
- "nanoid": ["nanoid@3.2.0", "", { "bin": "bin/nanoid.cjs" }, "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA=="],
-
- "node-releases": ["node-releases@2.0.1", "", {}, "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA=="],
-
- "object-assign": ["object-assign@4.1.1", "", {}, ""],
-
- "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
-
- "picocolors": ["picocolors@1.0.0", "", {}, "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="],
-
- "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
-
- "postcss": ["postcss@8.4.5", "", { "dependencies": { "nanoid": "^3.1.30", "picocolors": "^1.0.0", "source-map-js": "^1.0.1" } }, "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg=="],
-
- "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
-
- "react": ["react@19.0.0", "", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="],
-
- "react-dom": ["react-dom@19.0.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="],
-
- "react-frame-component": ["react-frame-component@5.2.1", "", { "peerDependencies": { "prop-types": "^15.5.9", "react": ">= 16.3", "react-dom": ">= 16.3" } }, "sha512-nrSh1OZuHlX69eWqJPiUkPT9S6/wxc4PpJV+vOQ4pHQQ8XmIsIT+utWT+nX32ZfANHZuKONA7JsWMUGT36CqaQ=="],
-
- "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
-
- "react-refresh": ["react-refresh@0.11.0", "", {}, "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="],
-
- "recrawl-sync": ["recrawl-sync@2.2.1", "", { "dependencies": { "@cush/relative": "^1.0.0", "glob-regex": "^0.3.0", "slash": "^3.0.0", "tslib": "^1.9.3" } }, "sha512-A2yLDgeXNaduJJMlqyUdIN7fewopnNm/mVeeGytS1d2HLXKpS5EthQ0j8tWeX+as9UXiiwQRwfoslKC+/gjqxg=="],
-
- "resolve": ["resolve@1.22.0", "", { "dependencies": { "is-core-module": "^2.8.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw=="],
-
- "rollup": ["rollup@2.66.0", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, "sha512-L6mKOkdyP8HK5kKJXaiWG7KZDumPJjuo1P+cfyHOJPNNTK3Moe7zCH5+fy7v8pVmHXtlxorzaBjvkBMB23s98g=="],
-
- "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
-
- "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="],
-
- "semver": ["semver@6.3.0", "", { "bin": "bin/semver.js" }, "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="],
-
- "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
-
- "source-map": ["source-map@0.5.7", "", {}, ""],
-
- "source-map-js": ["source-map-js@1.0.2", "", {}, "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="],
-
- "strip-bom": ["strip-bom@3.0.0", "", {}, ""],
-
- "supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
-
- "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
-
- "to-fast-properties": ["to-fast-properties@2.0.0", "", {}, ""],
-
- "tsconfig-paths": ["tsconfig-paths@3.12.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.1", "minimist": "^1.2.0", "strip-bom": "^3.0.0" } }, "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg=="],
-
- "tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
-
- "typescript": ["typescript@4.5.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA=="],
-
- "vite": ["vite@2.7.13", "", { "dependencies": { "esbuild": "^0.13.12", "postcss": "^8.4.5", "resolve": "^1.20.0", "rollup": "^2.59.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "less": "*", "sass": "*", "stylus": "*" }, "optionalPeers": ["less", "sass", "stylus"], "bin": "bin/vite.js" }, "sha512-Mq8et7f3aK0SgSxjDNfOAimZGW9XryfHRa/uV0jseQSilg+KhYDSoNb9h1rknOy6SuMkvNDLKCYAYYUMCE+IgQ=="],
-
- "vite-tsconfig-paths": ["vite-tsconfig-paths@3.3.17", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "recrawl-sync": "^2.0.3", "tsconfig-paths": "^3.9.0" }, "peerDependencies": { "vite": ">2.0.0-0" } }, "sha512-wx+rfC53moVLxMBj2EApJZgY6HtvWUFVZ4dBxNGYBxSSqU6UaHdKlcOxrfGDxyTGtYEr9beWCryHn18C4EtZkg=="],
-
- "tsconfig-paths/json5": ["json5@1.0.1", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": "lib/cli.js" }, "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow=="],
- }
-}
diff --git a/apps/loom-importer-extension/package.json b/apps/loom-importer-extension/package.json
index 0ef39eb09..a309a07c1 100644
--- a/apps/loom-importer-extension/package.json
+++ b/apps/loom-importer-extension/package.json
@@ -1,10 +1,10 @@
{
- "name": "loom-importer-extension",
+ "name": "@cap/loom-importer-extension",
"version": "0.0.0",
"scripts": {
- "dev": "vite",
- "build": "tsc && vite build",
- "watch": "vite build --watch",
+ "dev": "dotenv -e ../../.env -- vite",
+ "build": "dotenv -e ../../.env -- tsc && vite build",
+ "watch": "dotenv -e ../../.env -- vite build --watch",
"type-check": "tsc --noEmit",
"dev:extension": "echo '⚡ Building in watch mode. Load the extension from the dist folder in Chrome extensions page (chrome://extensions/)' && pnpm watch",
"preview": "vite preview"
From 7c2fa4cfe45cf828d5ecb37055c88d9b4a4943d8 Mon Sep 17 00:00:00 2001
From: Brendan Allan
Date: Mon, 7 Apr 2025 23:07:03 +0800
Subject: [PATCH 05/36] use hono instead of raw api route
---
.../app/api/import/{loom/route.ts => loom.ts} | 232 ++++++++----------
apps/web/app/api/import/route.ts | 12 +
2 files changed, 114 insertions(+), 130 deletions(-)
rename apps/web/app/api/import/{loom/route.ts => loom.ts} (61%)
create mode 100644 apps/web/app/api/import/route.ts
diff --git a/apps/web/app/api/import/loom/route.ts b/apps/web/app/api/import/loom.ts
similarity index 61%
rename from apps/web/app/api/import/loom/route.ts
rename to apps/web/app/api/import/loom.ts
index 9d24687b5..d93bd8227 100644
--- a/apps/web/app/api/import/loom/route.ts
+++ b/apps/web/app/api/import/loom.ts
@@ -1,5 +1,4 @@
-import { type NextRequest } from "next/server";
-import { getCurrentUser } from "@cap/database/auth/session";
+import { zValidator } from "@hono/zod-validator";
import {
users,
spaces,
@@ -11,32 +10,107 @@ import { db } from "@cap/database";
import { eq, inArray, or } from "drizzle-orm";
import { nanoId } from "@cap/database/helpers";
import { z } from "zod";
+import { Hono } from "hono";
-export interface WorkspaceMember {
- name: string;
- email: string;
- role: string;
- dateJoined: string;
- status: string;
-}
+import { withAuth } from "../utils";
-export interface VideoOwner {
- name: string;
- email: string;
-}
+const WorkspaceMember = z.object({
+ name: z.string(),
+ email: z.string().email(),
+ role: z.string(),
+ dateJoined: z.string(),
+ status: z.string(),
+});
-export interface Video {
- id: string;
- owner: VideoOwner;
- title: string;
-}
+type WorkspaceMember = z.infer;
-export interface LoomExportData {
- workspaceMembers: WorkspaceMember[];
- videos: Video[];
- selectedWorkspaceId: string;
- userEmail: string;
-}
+const Video = z.object({
+ id: z.string(),
+ owner: z.object({
+ name: z.string(),
+ email: z.string().email(),
+ }),
+ title: z.string(),
+});
+
+type Video = z.infer;
+
+export const app = new Hono().use(withAuth);
+
+app.post(
+ "/",
+ zValidator(
+ "json",
+ z.object({
+ workspaceMembers: z.array(WorkspaceMember),
+ videos: z.array(Video),
+ selectedWorkspaceId: z.string(),
+ userEmail: z.string().email(),
+ })
+ ),
+ async (c) => {
+ try {
+ const user = c.get("user");
+ const body = c.req.valid("json");
+
+ let targetSpaceId: string;
+
+ const userSpaces = await db
+ .select({ spaceId: spaces.id })
+ .from(spaces)
+ .leftJoin(spaceMembers, eq(spaces.id, spaceMembers.spaceId))
+ .where(
+ or(eq(spaces.ownerId, user.id), eq(spaceMembers.userId, user.id))
+ );
+
+ const hasAccess = userSpaces.some(
+ (space) => space.spaceId === body.selectedWorkspaceId
+ );
+
+ if (hasAccess) {
+ targetSpaceId = body.selectedWorkspaceId;
+ } else {
+ console.warn(
+ `User ${user.id} attempted to import to workspace ${body.selectedWorkspaceId} without access`
+ );
+ return c.json(
+ { error: "You don't have access to the selected workspace" },
+ { status: 403 }
+ );
+ }
+
+ const userIds = await createUsersFromLoomWorkspaceMembers(
+ body.workspaceMembers,
+ targetSpaceId
+ );
+
+ await addUsersToOwnerWorkspace(userIds, user.id, targetSpaceId);
+
+ await importVideosFromLoom(
+ body.videos,
+ user.id,
+ targetSpaceId,
+ body.userEmail
+ );
+
+ return c.json({
+ success: true,
+ usersCreated: userIds.length,
+ videosImported: body.videos.length,
+ spaceId: targetSpaceId,
+ });
+ } catch (error) {
+ console.error("Error importing Loom data:", error);
+ return c.json(
+ {
+ error: "Failed to import data",
+ message: (error as Error).message,
+ },
+ { status: 500 }
+ );
+ }
+ }
+);
/**
* Creates user accounts for Loom workspace members
@@ -44,7 +118,7 @@ export interface LoomExportData {
async function createUsersFromLoomWorkspaceMembers(
workspaceMembers: WorkspaceMember[],
workspaceId: string
-): Promise {
+) {
const emails = workspaceMembers.map((member) => member.email);
const existingUsers = await db
@@ -69,7 +143,6 @@ async function createUsersFromLoomWorkspaceMembers(
name: firstName,
lastName: lastName,
inviteQuota: 1,
-
activeSpaceId: workspaceId,
});
@@ -86,7 +159,7 @@ async function addUsersToOwnerWorkspace(
userIds: string[],
ownerId: string,
spaceId: string
-): Promise {
+) {
const existingMembers = await db
.select({ userId: spaceMembers.userId })
.from(spaceMembers)
@@ -127,11 +200,7 @@ async function addUsersToOwnerWorkspace(
* Downloads a video from Loom's CDN
* This is an empty function as requested, to be implemented later
*/
-async function downloadVideoFromLoom(videoId: string): Promise<{
- videoUrl: string;
- thumbnailUrl: string;
- metadata: Record;
-}> {
+async function downloadVideoFromLoom(videoId: string) {
// TODO: For cap.so team replace this actual upload to S3 implementation
return {
@@ -152,7 +221,7 @@ async function importVideosFromLoom(
ownerId: string,
spaceId: string,
userEmail: string
-): Promise {
+) {
for (const loomVideo of loomVideos) {
try {
const owner = loomVideo.owner;
@@ -205,100 +274,3 @@ async function importVideosFromLoom(
}
}
}
-
-const loomExportSchema = z.object({
- workspaceMembers: z.array(
- z.object({
- name: z.string(),
- email: z.string().email(),
- role: z.string(),
- dateJoined: z.string(),
- status: z.string(),
- })
- ),
- videos: z.array(
- z.object({
- id: z.string(),
- owner: z.object({
- name: z.string(),
- email: z.string().email(),
- }),
- title: z.string(),
- })
- ),
- selectedWorkspaceId: z.string(),
- userEmail: z.string().email(),
-});
-
-export async function POST(request: NextRequest) {
- try {
- const user = await getCurrentUser();
-
- if (!user || !user.id) {
- console.error("User not found or unauthorized");
- return Response.json({ error: "Unauthorized" }, { status: 401 });
- }
- const body = loomExportSchema.parse(await request.json());
-
- let targetSpaceId: string;
-
- const userSpaces = await db
- .select({ spaceId: spaces.id })
- .from(spaces)
- .leftJoin(spaceMembers, eq(spaces.id, spaceMembers.spaceId))
- .where(or(eq(spaces.ownerId, user.id), eq(spaceMembers.userId, user.id)));
-
- const hasAccess = userSpaces.some(
- (space) => space.spaceId === body.selectedWorkspaceId
- );
-
- if (hasAccess) {
- targetSpaceId = body.selectedWorkspaceId;
- } else {
- console.warn(
- `User ${user.id} attempted to import to workspace ${body.selectedWorkspaceId} without access`
- );
- return Response.json(
- { error: "You don't have access to the selected workspace" },
- { status: 403 }
- );
- }
-
- const userIds = await createUsersFromLoomWorkspaceMembers(
- body.workspaceMembers,
- targetSpaceId
- );
-
- await addUsersToOwnerWorkspace(userIds, user.id, targetSpaceId);
-
- await importVideosFromLoom(
- body.videos,
- user.id,
- targetSpaceId,
- body.userEmail
- );
-
- return Response.json(
- {
- success: true,
- usersCreated: userIds.length,
- videosImported: body.videos.length,
- spaceId: targetSpaceId,
- },
- {
- status: 200,
- }
- );
- } catch (error) {
- console.error("Error importing Loom data:", error);
- return Response.json(
- {
- error: "Failed to import data",
- message: (error as Error).message,
- },
- {
- status: 500,
- }
- );
- }
-}
diff --git a/apps/web/app/api/import/route.ts b/apps/web/app/api/import/route.ts
new file mode 100644
index 000000000..58390bb14
--- /dev/null
+++ b/apps/web/app/api/import/route.ts
@@ -0,0 +1,12 @@
+import { Hono } from "hono";
+import { corsMiddleware } from "../utils";
+import * as loom from "./loom";
+import { handle } from "hono/vercel";
+
+export const app = new Hono()
+ .basePath("/api/import")
+ .use(corsMiddleware)
+ .route("/loom", loom.app);
+
+export const GET = handle(app);
+export const POST = handle(app);
From c103ce3de8032ce18e80f01b5c5aeb24a6fc06de Mon Sep 17 00:00:00 2001
From: neo773
Date: Tue, 6 May 2025 22:30:48 +0530
Subject: [PATCH 06/36] revert .gitignore and fix pnpm-lock.yaml
---
.gitignore | 1 +
pnpm-lock.yaml | 705 ++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 696 insertions(+), 10 deletions(-)
diff --git a/.gitignore b/.gitignore
index d51af4824..85a6803fa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,7 @@ target
*.sln
*.sw?
.turbo
+pnpm-lock.yaml
.zed
.output
.vinxi
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a35278655..f6c787d49 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -35,13 +35,13 @@ importers:
version: file:packages/database(@babel/core@7.27.1)(@cloudflare/workers-types@4.20250505.0)(@opentelemetry/api@1.9.0)(@types/react@18.3.20)(encoding@0.1.13)(mysql2@3.14.1)(nodemailer@6.10.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)
'@cap/ui':
specifier: workspace:*
- version: file:packages/ui(@types/react-dom@19.1.3(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)(valibot@1.0.0-rc.1(typescript@5.8.3))
+ version: file:packages/ui(@types/react-dom@19.1.3(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)
'@cap/ui-solid':
specifier: workspace:*
version: file:packages/ui-solid(@fontsource/geist-sans@5.2.5)
'@cap/utils':
specifier: workspace:*
- version: file:packages/utils(typescript@5.8.3)(valibot@1.0.0-rc.1(typescript@5.8.3))
+ version: file:packages/utils(typescript@5.8.3)
'@cap/web-api-contract':
specifier: workspace:*
version: link:../../packages/web-api-contract
@@ -264,6 +264,58 @@ importers:
specifier: ^3.109.1
version: 3.114.8(@cloudflare/workers-types@4.20250505.0)
+ apps/loom-importer-extension:
+ dependencies:
+ '@cap/ui':
+ specifier: workspace:^
+ version: file:packages/ui(@types/react-dom@18.3.7(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.15.3)(typescript@4.9.5)))(typescript@4.9.5)
+ '@cap/ui-solid':
+ specifier: workspace:^
+ version: file:packages/ui-solid(@fontsource/geist-sans@5.2.5)(ts-node@10.9.2(@types/node@22.15.3)(typescript@4.9.5))
+ js-confetti:
+ specifier: ^0.12.0
+ version: 0.12.0
+ react:
+ specifier: ^18.2.0
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.3.1(react@18.3.1)
+ react-frame-component:
+ specifier: ^5.2.1
+ version: 5.2.7(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ vite-tsconfig-paths:
+ specifier: ^3.3.17
+ version: 3.6.0(vite@2.9.18)
+ zustand:
+ specifier: ^5.0.3
+ version: 5.0.4(@types/react@18.3.20)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1))
+ devDependencies:
+ '@types/chrome':
+ specifier: ^0.0.176
+ version: 0.0.176
+ '@types/react':
+ specifier: ^18.2.0
+ version: 18.3.20
+ '@types/react-dom':
+ specifier: ^18.2.0
+ version: 18.3.7(@types/react@18.3.20)
+ '@vitejs/plugin-react':
+ specifier: ^1.0.7
+ version: 1.3.2
+ hot-reload-extension-vite:
+ specifier: ^1.0.13
+ version: 1.0.13
+ tailwindcss:
+ specifier: ^3.4.16
+ version: 3.4.17(ts-node@10.9.2(@types/node@22.15.3)(typescript@4.9.5))
+ typescript:
+ specifier: ^4.4.4
+ version: 4.9.5
+ vite:
+ specifier: ^2.7.2
+ version: 2.9.18
+
apps/storybook:
dependencies:
'@cap/ui-solid':
@@ -840,7 +892,7 @@ importers:
dependencies:
'@cap/utils':
specifier: workspace:*
- version: file:packages/utils(typescript@5.8.3)(valibot@1.0.0-rc.1(typescript@5.8.3))
+ version: file:packages/utils(typescript@5.8.3)
'@kobalte/tailwindcss':
specifier: ^0.9.0
version: 0.9.0(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@20.17.32)(typescript@5.8.3)))
@@ -1262,6 +1314,10 @@ packages:
resolution: {integrity: sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-annotate-as-pure@7.27.1':
+ resolution: {integrity: sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-compilation-targets@7.27.1':
resolution: {integrity: sha512-2YaDd/Rd9E598B5+WIc8wJPmWETiiJXFYVE60oX8FDohv7rAUU3CQj+A1MgeEmcsk2+dQuEjIe/GDvig0SqL4g==}
engines: {node: '>=6.9.0'}
@@ -1400,6 +1456,12 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
+ '@babel/plugin-transform-react-jsx-development@7.27.1':
+ resolution: {integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
'@babel/plugin-transform-react-jsx-self@7.27.1':
resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
engines: {node: '>=6.9.0'}
@@ -1412,6 +1474,12 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
+ '@babel/plugin-transform-react-jsx@7.27.1':
+ resolution: {integrity: sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
'@babel/runtime@7.27.1':
resolution: {integrity: sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==}
engines: {node: '>=6.9.0'}
@@ -1613,6 +1681,9 @@ packages:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
+ '@cush/relative@1.0.0':
+ resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
+
'@dabh/diagnostics@2.0.3':
resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==}
@@ -2136,6 +2207,12 @@ packages:
cpu: [ia32]
os: [linux]
+ '@esbuild/linux-loong64@0.14.54':
+ resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+
'@esbuild/linux-loong64@0.16.4':
resolution: {integrity: sha512-peDrrUuxbZ9Jw+DwLCh/9xmZAk0p0K1iY5d2IcwmnN+B87xw7kujOkig6ZRcZqgrXgeRGurRHn0ENMAjjD5DEg==}
engines: {node: '>=12'}
@@ -4563,6 +4640,10 @@ packages:
rollup:
optional: true
+ '@rollup/pluginutils@4.2.1':
+ resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==}
+ engines: {node: '>= 8.0.0'}
+
'@rollup/pluginutils@5.1.4':
resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==}
engines: {node: '>=14.0.0'}
@@ -5622,6 +5703,9 @@ packages:
'@types/cacheable-request@6.0.3':
resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==}
+ '@types/chrome@0.0.176':
+ resolution: {integrity: sha512-LOveFOMIUhMJjvRzZv5whGBpncP/gdJ4hcxeAqg94wGi6CyKaCmLgFSofgItf85GuLTl/0BQ6J/Y1e8BqZWfEg==}
+
'@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
@@ -5664,6 +5748,12 @@ packages:
'@types/file-saver@2.0.7':
resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==}
+ '@types/filesystem@0.0.36':
+ resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==}
+
+ '@types/filewriter@0.0.33':
+ resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==}
+
'@types/fluent-ffmpeg@2.1.27':
resolution: {integrity: sha512-QiDWjihpUhriISNoBi2hJBRUUmoj/BMTYcfz+F+ZM9hHWBYABFAE6hjP/TbCZC0GWwlpa3FzvHH9RzFeRusZ7A==}
@@ -5673,6 +5763,9 @@ packages:
'@types/graceful-fs@4.1.9':
resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
+ '@types/har-format@1.2.16':
+ resolution: {integrity: sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==}
+
'@types/hast@2.3.10':
resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==}
@@ -6106,6 +6199,10 @@ packages:
peerDependencies:
vinxi: ^0.5.5
+ '@vitejs/plugin-react@1.3.2':
+ resolution: {integrity: sha512-aurBNmMo0kz1O4qRoY+FM4epSA39y3ShWGuqfLRA/3z0oEJAdtoSfgA3aO98/PCCHAqMaduLxIxErWrVKIFzXA==}
+ engines: {node: '>=12.0.0'}
+
'@vitejs/plugin-react@4.4.1':
resolution: {integrity: sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -7697,11 +7794,136 @@ packages:
esast-util-from-js@2.0.1:
resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==}
+ esbuild-android-64@0.14.54:
+ resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+
+ esbuild-android-arm64@0.14.54:
+ resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+
+ esbuild-darwin-64@0.14.54:
+ resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+
+ esbuild-darwin-arm64@0.14.54:
+ resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+
+ esbuild-freebsd-64@0.14.54:
+ resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+
+ esbuild-freebsd-arm64@0.14.54:
+ resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ esbuild-linux-32@0.14.54:
+ resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+
+ esbuild-linux-64@0.14.54:
+ resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+
+ esbuild-linux-arm64@0.14.54:
+ resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+
+ esbuild-linux-arm@0.14.54:
+ resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+
+ esbuild-linux-mips64le@0.14.54:
+ resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+
+ esbuild-linux-ppc64le@0.14.54:
+ resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+
+ esbuild-linux-riscv64@0.14.54:
+ resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+
+ esbuild-linux-s390x@0.14.54:
+ resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+
+ esbuild-netbsd-64@0.14.54:
+ resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+
+ esbuild-openbsd-64@0.14.54:
+ resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+
esbuild-register@3.6.0:
resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==}
peerDependencies:
esbuild: '>=0.12 <1'
+ esbuild-sunos-64@0.14.54:
+ resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+
+ esbuild-windows-32@0.14.54:
+ resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+
+ esbuild-windows-64@0.14.54:
+ resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+
+ esbuild-windows-arm64@0.14.54:
+ resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+
+ esbuild@0.14.54:
+ resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==}
+ engines: {node: '>=12'}
+ hasBin: true
+
esbuild@0.16.4:
resolution: {integrity: sha512-qQrPMQpPTWf8jHugLWHoGqZjApyx3OEm76dlTXobHwh/EBbavbRdjXdYi/GWr43GyN0sfpap14GPkb05NH3ROA==}
engines: {node: '>=12'}
@@ -8434,6 +8656,9 @@ packages:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
engines: {node: '>=10.13.0'}
+ glob-regex@0.3.2:
+ resolution: {integrity: sha512-m5blUd3/OqDTWwzBBtWBPrGlAzatRywHameHeekAZyZrskYouOGdNB8T/q6JucucvJXtOuyHIn0/Yia7iDasDw==}
+
glob-to-regexp@0.4.1:
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
@@ -8633,6 +8858,9 @@ packages:
resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==}
engines: {node: ^16.14.0 || >=18.0.0}
+ hot-reload-extension-vite@1.0.13:
+ resolution: {integrity: sha512-bX9Bcvhay+xwVinLb/NdoA85NDAixbZ4xcmOyibvHjDjIzw1qllEGfN4LnoAi1jSfH0Mv5Sf5WrbplJS6IaMmg==}
+
html-entities@2.3.3:
resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==}
@@ -9300,6 +9528,9 @@ packages:
engines: {node: '>=14'}
hasBin: true
+ js-confetti@0.12.0:
+ resolution: {integrity: sha512-1R0Akxn3Zn82pMqW65N1V2NwKkZJ75bvBN/VAb36Ya0YHwbaSiAJZVRr/19HBxH/O8x2x01UFAbYI18VqlDN6g==}
+
js-cookie@3.0.5:
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
engines: {node: '>=14'}
@@ -11071,6 +11302,13 @@ packages:
react-fast-compare@3.2.2:
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
+ react-frame-component@5.2.7:
+ resolution: {integrity: sha512-ROjHtSLoSVYUBfTieazj/nL8jIX9rZFmHC0yXEU+dx6Y82OcBEGgU9o7VyHMrBFUN9FuQ849MtIPNNLsb4krbg==}
+ peerDependencies:
+ prop-types: ^15.5.9
+ react: '>= 16.3'
+ react-dom: '>= 16.3'
+
react-hls-player@3.0.7:
resolution: {integrity: sha512-i5QWNyLmaUhV/mgnpljRJT0CBfJnylClV/bne8aiXO3ZqU0+D3U/jtTDwdXM4i5qHhyFy9lemyZ179IgadKd0Q==}
peerDependencies:
@@ -11112,6 +11350,10 @@ packages:
react-promise-suspense@0.3.4:
resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==}
+ react-refresh@0.13.0:
+ resolution: {integrity: sha512-XP8A9BT0CpRBD+NYLLeIhld/RqG9+gktUjW1FkE+Vm7OCinbG1SshcK5tb9ls4kzvjZr9mOQc7HYgBngEyPAXg==}
+ engines: {node: '>=0.10.0'}
+
react-refresh@0.17.0:
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
engines: {node: '>=0.10.0'}
@@ -11265,6 +11507,9 @@ packages:
recma-stringify@1.0.0:
resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==}
+ recrawl-sync@2.2.3:
+ resolution: {integrity: sha512-vSaTR9t+cpxlskkdUFrsEpnf67kSmPk66yAGT1fZPrDudxQjoMzPgQhSMImQ0pAw5k0NPirefQfhopSjhdUtpQ==}
+
redent@3.0.0:
resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
engines: {node: '>=8'}
@@ -11431,6 +11676,11 @@ packages:
rollup-pluginutils@2.8.2:
resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==}
+ rollup@2.77.3:
+ resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==}
+ engines: {node: '>=10.0.0'}
+ hasBin: true
+
rollup@4.40.1:
resolution: {integrity: sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -12259,6 +12509,10 @@ packages:
tsconfig-paths@3.15.0:
resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
+ tsconfig-paths@4.2.0:
+ resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==}
+ engines: {node: '>=6'}
+
tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
@@ -12377,6 +12631,11 @@ packages:
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
engines: {node: '>= 0.4'}
+ typescript@4.9.5:
+ resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
+ engines: {node: '>=4.2.0'}
+ hasBin: true
+
typescript@5.8.3:
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
engines: {node: '>=14.17'}
@@ -12820,6 +13079,11 @@ packages:
'@testing-library/jest-dom':
optional: true
+ vite-tsconfig-paths@3.6.0:
+ resolution: {integrity: sha512-UfsPYonxLqPD633X8cWcPFVuYzx/CMNHAjZTasYwX69sXpa4gNmQkR0XCjj82h7zhLGdTWagMjC1qfb9S+zv0A==}
+ peerDependencies:
+ vite: '>2.0.0-0'
+
vite-tsconfig-paths@4.3.2:
resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==}
peerDependencies:
@@ -12836,6 +13100,22 @@ packages:
vite:
optional: true
+ vite@2.9.18:
+ resolution: {integrity: sha512-sAOqI5wNM9QvSEE70W3UGMdT8cyEn0+PmJMTFvTB8wB0YbYUWw3gUbY62AOyrXosGieF2htmeLATvNxpv/zNyQ==}
+ engines: {node: '>=12.2.0'}
+ hasBin: true
+ peerDependencies:
+ less: '*'
+ sass: '*'
+ stylus: '*'
+ peerDependenciesMeta:
+ less:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+
vite@5.4.19:
resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==}
engines: {node: ^18.0.0 || >=20.0.0}
@@ -13240,6 +13520,24 @@ packages:
zod@3.24.4:
resolution: {integrity: sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==}
+ zustand@5.0.4:
+ resolution: {integrity: sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ==}
+ engines: {node: '>=12.20.0'}
+ peerDependencies:
+ '@types/react': '>=18.0.0'
+ immer: '>=9.0.6'
+ react: '>=18.0.0'
+ use-sync-external-store: '>=1.2.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+ use-sync-external-store:
+ optional: true
+
zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
@@ -13902,6 +14200,10 @@ snapshots:
'@jridgewell/trace-mapping': 0.3.25
jsesc: 3.1.0
+ '@babel/helper-annotate-as-pure@7.27.1':
+ dependencies:
+ '@babel/types': 7.27.1
+
'@babel/helper-compilation-targets@7.27.1':
dependencies:
'@babel/compat-data': 7.27.1
@@ -14039,6 +14341,13 @@ snapshots:
'@babel/core': 7.27.1
'@babel/helper-plugin-utils': 7.27.1
+ '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.27.1)':
+ dependencies:
+ '@babel/core': 7.27.1
+ '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.27.1)
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.27.1)':
dependencies:
'@babel/core': 7.27.1
@@ -14049,6 +14358,17 @@ snapshots:
'@babel/core': 7.27.1
'@babel/helper-plugin-utils': 7.27.1
+ '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.27.1)':
+ dependencies:
+ '@babel/core': 7.27.1
+ '@babel/helper-annotate-as-pure': 7.27.1
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.1)
+ '@babel/types': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/runtime@7.27.1': {}
'@babel/standalone@7.27.1': {}
@@ -14085,7 +14405,7 @@ snapshots:
'@cap/database@file:packages/database(@babel/core@7.27.1)(@cloudflare/workers-types@4.20250505.0)(@opentelemetry/api@1.9.0)(@types/react@18.3.20)(encoding@0.1.13)(mysql2@3.14.1)(nodemailer@6.10.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)':
dependencies:
- '@cap/env': file:packages/env(typescript@5.8.3)(valibot@1.0.0-rc.1(typescript@5.8.3))
+ '@cap/env': file:packages/env(typescript@5.8.3)
'@mattrax/mysql-planetscale': 0.0.3
'@paralleldrive/cuid2': 2.2.2
'@planetscale/database': 1.19.0
@@ -14138,6 +14458,22 @@ snapshots:
- typescript
- valibot
+ '@cap/env@file:packages/env(typescript@4.9.5)':
+ dependencies:
+ '@t3-oss/env-nextjs': 0.12.0(typescript@4.9.5)(zod@3.24.4)
+ zod: 3.24.4
+ transitivePeerDependencies:
+ - typescript
+ - valibot
+
+ '@cap/env@file:packages/env(typescript@5.8.3)':
+ dependencies:
+ '@t3-oss/env-nextjs': 0.12.0(typescript@5.8.3)(valibot@1.0.0-rc.1(typescript@5.8.3))(zod@3.24.4)
+ zod: 3.24.4
+ transitivePeerDependencies:
+ - typescript
+ - valibot
+
'@cap/env@file:packages/env(typescript@5.8.3)(valibot@1.0.0-rc.1(typescript@5.8.3))':
dependencies:
'@t3-oss/env-nextjs': 0.12.0(typescript@5.8.3)(valibot@1.0.0-rc.1(typescript@5.8.3))(zod@3.24.4)
@@ -14156,6 +14492,16 @@ snapshots:
transitivePeerDependencies:
- ts-node
+ '@cap/ui-solid@file:packages/ui-solid(@fontsource/geist-sans@5.2.5)(ts-node@10.9.2(@types/node@22.15.3)(typescript@4.9.5))':
+ dependencies:
+ '@fontsource/geist-sans': 5.2.5
+ '@kobalte/core': 0.13.9(solid-js@1.9.6)
+ cva: class-variance-authority@0.7.1
+ solid-js: 1.9.6
+ tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@22.15.3)(typescript@4.9.5))
+ transitivePeerDependencies:
+ - ts-node
+
'@cap/ui@file:packages/ui(@types/react-dom@18.3.7(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@20.17.32)(typescript@5.8.3)))(typescript@5.8.3)(valibot@1.0.0-rc.1(typescript@5.8.3))':
dependencies:
'@cap/utils': file:packages/utils(typescript@5.8.3)(valibot@1.0.0-rc.1(typescript@5.8.3))
@@ -14183,6 +14529,60 @@ snapshots:
- typescript
- valibot
+ '@cap/ui@file:packages/ui(@types/react-dom@18.3.7(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.15.3)(typescript@4.9.5)))(typescript@4.9.5)':
+ dependencies:
+ '@cap/utils': file:packages/utils(typescript@4.9.5)
+ '@kobalte/tailwindcss': 0.9.0(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.15.3)(typescript@4.9.5)))
+ '@radix-ui/react-dialog': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-dropdown-menu': 2.1.12(@types/react-dom@18.3.7(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-label': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-navigation-menu': 1.2.10(@types/react-dom@18.3.7(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-popover': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-slot': 1.2.0(@types/react@18.3.20)(react@18.3.1)
+ '@radix-ui/react-switch': 1.2.2(@types/react-dom@18.3.7(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@tailwindcss/typography': 0.5.16(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.15.3)(typescript@4.9.5)))
+ class-variance-authority: 0.7.1
+ cmdk: 0.2.1(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ lucide-react: 0.294.0(react@18.3.1)
+ react-hook-form: 7.56.2(react@18.3.1)
+ react-loading-skeleton: 3.5.0(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+ - '@types/react-dom'
+ - aws-crt
+ - react
+ - react-dom
+ - tailwindcss
+ - typescript
+ - valibot
+
+ '@cap/ui@file:packages/ui(@types/react-dom@19.1.3(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)':
+ dependencies:
+ '@cap/utils': file:packages/utils(typescript@5.8.3)
+ '@kobalte/tailwindcss': 0.9.0(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.15.3)(typescript@5.8.3)))
+ '@radix-ui/react-dialog': 1.1.11(@types/react-dom@19.1.3(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-dropdown-menu': 2.1.12(@types/react-dom@19.1.3(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-label': 2.1.4(@types/react-dom@19.1.3(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-navigation-menu': 1.2.10(@types/react-dom@19.1.3(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-popover': 1.1.11(@types/react-dom@19.1.3(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-slot': 1.2.0(@types/react@18.3.20)(react@18.3.1)
+ '@radix-ui/react-switch': 1.2.2(@types/react-dom@19.1.3(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@tailwindcss/typography': 0.5.16(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.15.3)(typescript@5.8.3)))
+ class-variance-authority: 0.7.1
+ cmdk: 0.2.1(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ lucide-react: 0.294.0(react@18.3.1)
+ react-hook-form: 7.56.2(react@18.3.1)
+ react-loading-skeleton: 3.5.0(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+ - '@types/react-dom'
+ - aws-crt
+ - react
+ - react-dom
+ - tailwindcss
+ - typescript
+ - valibot
+
'@cap/ui@file:packages/ui(@types/react-dom@19.1.3(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)(valibot@1.0.0-rc.1(typescript@5.8.3))':
dependencies:
'@cap/utils': file:packages/utils(typescript@5.8.3)(valibot@1.0.0-rc.1(typescript@5.8.3))
@@ -14210,6 +14610,30 @@ snapshots:
- typescript
- valibot
+ '@cap/utils@file:packages/utils(typescript@4.9.5)':
+ dependencies:
+ '@aws-sdk/client-s3': 3.802.0
+ '@cap/env': file:packages/env(typescript@4.9.5)
+ clsx: 2.1.1
+ stripe: 14.25.0
+ tailwind-merge: 2.6.0
+ transitivePeerDependencies:
+ - aws-crt
+ - typescript
+ - valibot
+
+ '@cap/utils@file:packages/utils(typescript@5.8.3)':
+ dependencies:
+ '@aws-sdk/client-s3': 3.802.0
+ '@cap/env': file:packages/env(typescript@5.8.3)
+ clsx: 2.1.1
+ stripe: 14.25.0
+ tailwind-merge: 2.6.0
+ transitivePeerDependencies:
+ - aws-crt
+ - typescript
+ - valibot
+
'@cap/utils@file:packages/utils(typescript@5.8.3)(valibot@1.0.0-rc.1(typescript@5.8.3))':
dependencies:
'@aws-sdk/client-s3': 3.802.0
@@ -14436,6 +14860,8 @@ snapshots:
dependencies:
'@jridgewell/trace-mapping': 0.3.9
+ '@cush/relative@1.0.0': {}
+
'@dabh/diagnostics@2.0.3':
dependencies:
colorspace: 1.1.4
@@ -14763,6 +15189,9 @@ snapshots:
'@esbuild/linux-ia32@0.25.3':
optional: true
+ '@esbuild/linux-loong64@0.14.54':
+ optional: true
+
'@esbuild/linux-loong64@0.16.4':
optional: true
@@ -15524,6 +15953,10 @@ snapshots:
dependencies:
tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@20.17.32)(typescript@5.8.3))
+ '@kobalte/tailwindcss@0.9.0(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.15.3)(typescript@4.9.5)))':
+ dependencies:
+ tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@22.15.3)(typescript@4.9.5))
+
'@kobalte/tailwindcss@0.9.0(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.15.3)(typescript@5.8.3)))':
dependencies:
tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@22.15.3)(typescript@5.8.3))
@@ -17581,6 +18014,11 @@ snapshots:
optionalDependencies:
rollup: 4.40.1
+ '@rollup/pluginutils@4.2.1':
+ dependencies:
+ estree-walker: 2.0.2
+ picomatch: 2.3.1
+
'@rollup/pluginutils@5.1.4(rollup@4.40.1)':
dependencies:
'@types/estree': 1.0.7
@@ -18453,12 +18891,24 @@ snapshots:
dependencies:
defer-to-connect: 2.0.1
+ '@t3-oss/env-core@0.12.0(typescript@4.9.5)(zod@3.24.4)':
+ optionalDependencies:
+ typescript: 4.9.5
+ zod: 3.24.4
+
'@t3-oss/env-core@0.12.0(typescript@5.8.3)(valibot@1.0.0-rc.1(typescript@5.8.3))(zod@3.24.4)':
optionalDependencies:
typescript: 5.8.3
valibot: 1.0.0-rc.1(typescript@5.8.3)
zod: 3.24.4
+ '@t3-oss/env-nextjs@0.12.0(typescript@4.9.5)(zod@3.24.4)':
+ dependencies:
+ '@t3-oss/env-core': 0.12.0(typescript@4.9.5)(zod@3.24.4)
+ optionalDependencies:
+ typescript: 4.9.5
+ zod: 3.24.4
+
'@t3-oss/env-nextjs@0.12.0(typescript@5.8.3)(valibot@1.0.0-rc.1(typescript@5.8.3))(zod@3.24.4)':
dependencies:
'@t3-oss/env-core': 0.12.0(typescript@5.8.3)(valibot@1.0.0-rc.1(typescript@5.8.3))(zod@3.24.4)
@@ -18475,6 +18925,14 @@ snapshots:
postcss-selector-parser: 6.0.10
tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@20.17.32)(typescript@5.8.3))
+ '@tailwindcss/typography@0.5.16(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.15.3)(typescript@4.9.5)))':
+ dependencies:
+ lodash.castarray: 4.4.0
+ lodash.isplainobject: 4.0.6
+ lodash.merge: 4.6.2
+ postcss-selector-parser: 6.0.10
+ tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@22.15.3)(typescript@4.9.5))
+
'@tailwindcss/typography@0.5.16(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.15.3)(typescript@5.8.3)))':
dependencies:
lodash.castarray: 4.4.0
@@ -18841,6 +19299,11 @@ snapshots:
'@types/node': 20.17.32
'@types/responselike': 1.0.3
+ '@types/chrome@0.0.176':
+ dependencies:
+ '@types/filesystem': 0.0.36
+ '@types/har-format': 1.2.16
+
'@types/connect@3.4.38':
dependencies:
'@types/node': 20.17.32
@@ -18892,6 +19355,12 @@ snapshots:
'@types/file-saver@2.0.7': {}
+ '@types/filesystem@0.0.36':
+ dependencies:
+ '@types/filewriter': 0.0.33
+
+ '@types/filewriter@0.0.33': {}
+
'@types/fluent-ffmpeg@2.1.27':
dependencies:
'@types/node': 20.17.32
@@ -18902,6 +19371,8 @@ snapshots:
dependencies:
'@types/node': 20.17.32
+ '@types/har-format@1.2.16': {}
+
'@types/hast@2.3.10':
dependencies:
'@types/unist': 2.0.11
@@ -19428,6 +19899,19 @@ snapshots:
recast: 0.23.11
vinxi: 0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.3)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250505.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250505.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.7.1)
+ '@vitejs/plugin-react@1.3.2':
+ dependencies:
+ '@babel/core': 7.27.1
+ '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.27.1)
+ '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.27.1)
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.1)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.27.1)
+ '@rollup/pluginutils': 4.2.1
+ react-refresh: 0.13.0
+ resolve: 1.22.10
+ transitivePeerDependencies:
+ - supports-color
+
'@vitejs/plugin-react@4.4.1(vite@6.3.5(@types/node@20.17.32)(jiti@2.4.2)(terser@5.39.0)(yaml@2.7.1))':
dependencies:
'@babel/core': 7.27.1
@@ -21121,6 +21605,54 @@ snapshots:
esast-util-from-estree: 2.0.0
vfile-message: 4.0.2
+ esbuild-android-64@0.14.54:
+ optional: true
+
+ esbuild-android-arm64@0.14.54:
+ optional: true
+
+ esbuild-darwin-64@0.14.54:
+ optional: true
+
+ esbuild-darwin-arm64@0.14.54:
+ optional: true
+
+ esbuild-freebsd-64@0.14.54:
+ optional: true
+
+ esbuild-freebsd-arm64@0.14.54:
+ optional: true
+
+ esbuild-linux-32@0.14.54:
+ optional: true
+
+ esbuild-linux-64@0.14.54:
+ optional: true
+
+ esbuild-linux-arm64@0.14.54:
+ optional: true
+
+ esbuild-linux-arm@0.14.54:
+ optional: true
+
+ esbuild-linux-mips64le@0.14.54:
+ optional: true
+
+ esbuild-linux-ppc64le@0.14.54:
+ optional: true
+
+ esbuild-linux-riscv64@0.14.54:
+ optional: true
+
+ esbuild-linux-s390x@0.14.54:
+ optional: true
+
+ esbuild-netbsd-64@0.14.54:
+ optional: true
+
+ esbuild-openbsd-64@0.14.54:
+ optional: true
+
esbuild-register@3.6.0(esbuild@0.25.3):
dependencies:
debug: 4.4.0(supports-color@5.5.0)
@@ -21128,6 +21660,42 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ esbuild-sunos-64@0.14.54:
+ optional: true
+
+ esbuild-windows-32@0.14.54:
+ optional: true
+
+ esbuild-windows-64@0.14.54:
+ optional: true
+
+ esbuild-windows-arm64@0.14.54:
+ optional: true
+
+ esbuild@0.14.54:
+ optionalDependencies:
+ '@esbuild/linux-loong64': 0.14.54
+ esbuild-android-64: 0.14.54
+ esbuild-android-arm64: 0.14.54
+ esbuild-darwin-64: 0.14.54
+ esbuild-darwin-arm64: 0.14.54
+ esbuild-freebsd-64: 0.14.54
+ esbuild-freebsd-arm64: 0.14.54
+ esbuild-linux-32: 0.14.54
+ esbuild-linux-64: 0.14.54
+ esbuild-linux-arm: 0.14.54
+ esbuild-linux-arm64: 0.14.54
+ esbuild-linux-mips64le: 0.14.54
+ esbuild-linux-ppc64le: 0.14.54
+ esbuild-linux-riscv64: 0.14.54
+ esbuild-linux-s390x: 0.14.54
+ esbuild-netbsd-64: 0.14.54
+ esbuild-openbsd-64: 0.14.54
+ esbuild-sunos-64: 0.14.54
+ esbuild-windows-32: 0.14.54
+ esbuild-windows-64: 0.14.54
+ esbuild-windows-arm64: 0.14.54
+
esbuild@0.16.4:
optionalDependencies:
'@esbuild/android-arm': 0.16.4
@@ -21357,7 +21925,7 @@ snapshots:
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1)
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
eslint-plugin-react: 7.37.5(eslint@8.57.1)
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1)
@@ -21376,7 +21944,7 @@ snapshots:
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1)
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
eslint-plugin-react: 7.37.5(eslint@8.57.1)
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1)
@@ -21415,7 +21983,7 @@ snapshots:
tinyglobby: 0.2.13
unrs-resolver: 1.7.2
optionalDependencies:
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
@@ -21467,7 +22035,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
+ eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -21496,7 +22064,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1):
+ eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -22325,6 +22893,8 @@ snapshots:
dependencies:
is-glob: 4.0.3
+ glob-regex@0.3.2: {}
+
glob-to-regexp@0.4.1: {}
glob@10.3.10:
@@ -22621,6 +23191,13 @@ snapshots:
dependencies:
lru-cache: 10.4.3
+ hot-reload-extension-vite@1.0.13:
+ dependencies:
+ ws: 8.18.2
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
html-entities@2.3.3: {}
html-escaper@2.0.2: {}
@@ -23443,6 +24020,8 @@ snapshots:
js-cookie: 3.0.5
nopt: 7.2.1
+ js-confetti@0.12.0: {}
+
js-cookie@3.0.5: {}
js-tokens@4.0.0: {}
@@ -25528,6 +26107,14 @@ snapshots:
postcss: 8.5.3
ts-node: 10.9.2(@types/node@20.17.32)(typescript@5.8.3)
+ postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.15.3)(typescript@4.9.5)):
+ dependencies:
+ lilconfig: 3.1.3
+ yaml: 2.7.1
+ optionalDependencies:
+ postcss: 8.5.3
+ ts-node: 10.9.2(@types/node@22.15.3)(typescript@4.9.5)
+
postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.15.3)(typescript@5.8.3)):
dependencies:
lilconfig: 3.1.3
@@ -25817,6 +26404,12 @@ snapshots:
react-fast-compare@3.2.2: {}
+ react-frame-component@5.2.7(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ prop-types: 15.8.1
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
react-hls-player@3.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
hls.js: 0.14.17
@@ -25857,6 +26450,8 @@ snapshots:
dependencies:
fast-deep-equal: 2.0.1
+ react-refresh@0.13.0: {}
+
react-refresh@0.17.0: {}
react-remove-scroll-bar@2.3.8(@types/react@18.3.20)(react@18.3.1):
@@ -26052,6 +26647,14 @@ snapshots:
unified: 11.0.5
vfile: 6.0.3
+ recrawl-sync@2.2.3:
+ dependencies:
+ '@cush/relative': 1.0.0
+ glob-regex: 0.3.2
+ slash: 3.0.0
+ sucrase: 3.35.0
+ tslib: 1.14.1
+
redent@3.0.0:
dependencies:
indent-string: 4.0.0
@@ -26267,6 +26870,10 @@ snapshots:
dependencies:
estree-walker: 0.6.1
+ rollup@2.77.3:
+ optionalDependencies:
+ fsevents: 2.3.3
+
rollup@4.40.1:
dependencies:
'@types/estree': 1.0.7
@@ -27067,6 +27674,33 @@ snapshots:
transitivePeerDependencies:
- ts-node
+ tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.15.3)(typescript@4.9.5)):
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ arg: 5.0.2
+ chokidar: 3.6.0
+ didyoumean: 1.2.2
+ dlv: 1.1.3
+ fast-glob: 3.3.3
+ glob-parent: 6.0.2
+ is-glob: 4.0.3
+ jiti: 1.21.7
+ lilconfig: 3.1.3
+ micromatch: 4.0.8
+ normalize-path: 3.0.0
+ object-hash: 3.0.0
+ picocolors: 1.1.1
+ postcss: 8.5.3
+ postcss-import: 15.1.0(postcss@8.5.3)
+ postcss-js: 4.0.1(postcss@8.5.3)
+ postcss-load-config: 4.0.2(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.15.3)(typescript@4.9.5))
+ postcss-nested: 6.2.0(postcss@8.5.3)
+ postcss-selector-parser: 6.1.2
+ resolve: 1.22.10
+ sucrase: 3.35.0
+ transitivePeerDependencies:
+ - ts-node
+
tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.15.3)(typescript@5.8.3)):
dependencies:
'@alloc/quick-lru': 5.2.0
@@ -27280,6 +27914,25 @@ snapshots:
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
+ ts-node@10.9.2(@types/node@22.15.3)(typescript@4.9.5):
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1
+ '@tsconfig/node10': 1.0.11
+ '@tsconfig/node12': 1.0.11
+ '@tsconfig/node14': 1.0.3
+ '@tsconfig/node16': 1.0.4
+ '@types/node': 22.15.3
+ acorn: 8.14.1
+ acorn-walk: 8.3.4
+ arg: 4.1.3
+ create-require: 1.1.1
+ diff: 4.0.2
+ make-error: 1.3.6
+ typescript: 4.9.5
+ v8-compile-cache-lib: 3.0.1
+ yn: 3.1.1
+ optional: true
+
ts-node@10.9.2(@types/node@22.15.3)(typescript@5.8.3):
dependencies:
'@cspotcode/source-map-support': 0.8.1
@@ -27312,6 +27965,12 @@ snapshots:
minimist: 1.2.8
strip-bom: 3.0.0
+ tsconfig-paths@4.2.0:
+ dependencies:
+ json5: 2.2.3
+ minimist: 1.2.8
+ strip-bom: 3.0.0
+
tslib@1.14.1: {}
tslib@2.6.2: {}
@@ -27426,6 +28085,8 @@ snapshots:
possible-typed-array-names: 1.1.0
reflect.getprototypeof: 1.0.10
+ typescript@4.9.5: {}
+
typescript@5.8.3: {}
ufo@1.6.1: {}
@@ -28012,6 +28673,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ vite-tsconfig-paths@3.6.0(vite@2.9.18):
+ dependencies:
+ debug: 4.4.0(supports-color@5.5.0)
+ globrex: 0.1.2
+ recrawl-sync: 2.2.3
+ tsconfig-paths: 4.2.0
+ vite: 2.9.18
+ transitivePeerDependencies:
+ - supports-color
+
vite-tsconfig-paths@4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@20.17.32)(jiti@2.4.2)(terser@5.39.0)(yaml@2.7.1)):
dependencies:
debug: 4.4.0(supports-color@5.5.0)
@@ -28034,6 +28705,15 @@ snapshots:
- supports-color
- typescript
+ vite@2.9.18:
+ dependencies:
+ esbuild: 0.14.54
+ postcss: 8.5.3
+ resolve: 1.22.10
+ rollup: 2.77.3
+ optionalDependencies:
+ fsevents: 2.3.3
+
vite@5.4.19(@types/node@22.15.3)(terser@5.39.0):
dependencies:
esbuild: 0.21.5
@@ -28437,5 +29117,10 @@ snapshots:
zod@3.24.4: {}
+ zustand@5.0.4(@types/react@18.3.20)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)):
+ optionalDependencies:
+ '@types/react': 18.3.20
+ react: 18.3.1
+ use-sync-external-store: 1.5.0(react@18.3.1)
+
zwitch@2.0.4: {}
-
\ No newline at end of file
From e53b196f90160371acd4cdec29e6fe1f9e60e2ea Mon Sep 17 00:00:00 2001
From: ameer2468 <33054370+ameer2468@users.noreply.github.com>
Date: Mon, 11 Aug 2025 18:06:46 +0300
Subject: [PATCH 07/36] Update pnpm-lock.yaml
---
pnpm-lock.yaml | 779 ++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 680 insertions(+), 99 deletions(-)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7672780d3..8d039299c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -103,7 +103,7 @@ importers:
version: 0.14.10(solid-js@1.9.6)
'@solidjs/start':
specifier: ^1.1.3
- version: 1.1.3(@testing-library/jest-dom@6.5.0)(@types/node@22.15.17)(jiti@2.4.2)(solid-js@1.9.6)(terser@5.39.0)(vinxi@0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.1))(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1))(yaml@2.8.1)
+ version: 1.1.3(@testing-library/jest-dom@6.5.0)(@types/node@22.15.17)(jiti@2.4.2)(solid-js@1.9.6)(terser@5.39.0)(vinxi@0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.0))(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0))(yaml@2.8.0)
'@tanstack/solid-query':
specifier: ^5.51.21
version: 5.75.4(solid-js@1.9.6)
@@ -190,7 +190,7 @@ importers:
version: 9.0.1
vinxi:
specifier: ^0.5.6
- version: 0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.1)
+ version: 0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.0)
webcodecs:
specifier: ^0.1.0
version: 0.1.0
@@ -224,10 +224,10 @@ importers:
version: 5.8.3
vite:
specifier: ^6.3.5
- version: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)
+ version: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)
vite-tsconfig-paths:
specifier: ^5.0.1
- version: 5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1))
+ version: 5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0))
vitest:
specifier: ~2.1.9
version: 2.1.9(@types/node@22.15.17)(jsdom@26.1.0)(terser@5.39.0)
@@ -275,6 +275,58 @@ importers:
specifier: ^3.109.1
version: 3.114.8(@cloudflare/workers-types@4.20250507.0)
+ apps/loom-importer-extension:
+ dependencies:
+ '@cap/ui':
+ specifier: workspace:^
+ version: link:../../packages/ui
+ '@cap/ui-solid':
+ specifier: workspace:^
+ version: link:../../packages/ui-solid
+ js-confetti:
+ specifier: ^0.12.0
+ version: 0.12.0
+ react:
+ specifier: ^18.2.0
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.3.1(react@18.3.1)
+ react-frame-component:
+ specifier: ^5.2.1
+ version: 5.2.7(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ vite-tsconfig-paths:
+ specifier: ^3.3.17
+ version: 3.6.0(vite@2.9.18)
+ zustand:
+ specifier: ^5.0.3
+ version: 5.0.7(@types/react@18.3.21)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1))
+ devDependencies:
+ '@types/chrome':
+ specifier: ^0.0.176
+ version: 0.0.176
+ '@types/react':
+ specifier: ^18.2.0
+ version: 18.3.21
+ '@types/react-dom':
+ specifier: ^18.2.0
+ version: 18.3.7(@types/react@18.3.21)
+ '@vitejs/plugin-react':
+ specifier: ^1.0.7
+ version: 1.3.2
+ hot-reload-extension-vite:
+ specifier: ^1.0.13
+ version: 1.0.13
+ tailwindcss:
+ specifier: ^3.4.16
+ version: 3.4.17(ts-node@10.9.2(@types/node@22.15.17)(typescript@4.9.5))
+ typescript:
+ specifier: ^4.4.4
+ version: 4.9.5
+ vite:
+ specifier: ^2.7.2
+ version: 2.9.18
+
apps/storybook:
dependencies:
'@cap/ui-solid':
@@ -319,16 +371,16 @@ importers:
version: 1.0.0-beta.7(@storybook/test@8.6.12(storybook@8.6.12(prettier@3.5.3)))(solid-js@1.9.6)(storybook@8.6.12(prettier@3.5.3))
storybook-solidjs-vite:
specifier: ^1.0.0-beta.2
- version: 1.0.0-beta.7(@storybook/test@8.6.12(storybook@8.6.12(prettier@3.5.3)))(solid-js@1.9.6)(storybook@8.6.12(prettier@3.5.3))(vite-plugin-solid@2.11.6(@testing-library/jest-dom@6.5.0)(solid-js@1.9.6)(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)))(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1))
+ version: 1.0.0-beta.7(@storybook/test@8.6.12(storybook@8.6.12(prettier@3.5.3)))(solid-js@1.9.6)(storybook@8.6.12(prettier@3.5.3))(vite-plugin-solid@2.11.6(@testing-library/jest-dom@6.5.0)(solid-js@1.9.6)(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)))(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0))
typescript:
specifier: ^5.8.3
version: 5.8.3
vite:
specifier: ^6.3.5
- version: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)
+ version: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)
vite-plugin-solid:
specifier: ^2.10.2
- version: 2.11.6(@testing-library/jest-dom@6.5.0)(solid-js@1.9.6)(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1))
+ version: 2.11.6(@testing-library/jest-dom@6.5.0)(solid-js@1.9.6)(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0))
apps/tasks:
dependencies:
@@ -766,13 +818,13 @@ importers:
dependencies:
'@vitejs/plugin-react':
specifier: ^4.0.3
- version: 4.4.1(vite@6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1))
+ version: 4.4.1(vite@6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0))
vite:
specifier: ^6.3.5
- version: 6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)
+ version: 6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)
vite-tsconfig-paths:
specifier: ^4.2.0
- version: 4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1))
+ version: 4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0))
zod:
specifier: ^3
version: 3.25.76
@@ -990,7 +1042,7 @@ importers:
version: 19.1.7(@types/react@18.3.21)
'@vitejs/plugin-react':
specifier: ^4.0.3
- version: 4.4.1(vite@6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1))
+ version: 4.4.1(vite@6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0))
autoprefixer:
specifier: ^10.4.16
version: 10.4.21(postcss@8.5.3)
@@ -1032,10 +1084,10 @@ importers:
version: 5.8.3
vite:
specifier: ^6.3.5
- version: 6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)
+ version: 6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)
vite-tsconfig-paths:
specifier: ^4.2.1
- version: 4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1))
+ version: 4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0))
packages/ui-solid:
dependencies:
@@ -1087,7 +1139,7 @@ importers:
version: 0.18.6(rollup@4.40.2)
unplugin-fonts:
specifier: ^1.1.1
- version: 1.3.1(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1))
+ version: 1.3.1(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0))
unplugin-icons:
specifier: ^0.19.2
version: 0.19.3
@@ -1383,6 +1435,10 @@ packages:
resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-annotate-as-pure@7.27.3':
+ resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-compilation-targets@7.27.2':
resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
engines: {node: '>=6.9.0'}
@@ -1526,6 +1582,12 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
+ '@babel/plugin-transform-react-jsx-development@7.27.1':
+ resolution: {integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
'@babel/plugin-transform-react-jsx-self@7.27.1':
resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
engines: {node: '>=6.9.0'}
@@ -1538,6 +1600,12 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
+ '@babel/plugin-transform-react-jsx@7.27.1':
+ resolution: {integrity: sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
'@babel/runtime@7.27.1':
resolution: {integrity: sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==}
engines: {node: '>=6.9.0'}
@@ -1743,6 +1811,9 @@ packages:
resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
engines: {node: '>=18'}
+ '@cush/relative@1.0.0':
+ resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
+
'@dabh/diagnostics@2.0.3':
resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==}
@@ -2214,6 +2285,12 @@ packages:
cpu: [ia32]
os: [linux]
+ '@esbuild/linux-loong64@0.14.54':
+ resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+
'@esbuild/linux-loong64@0.17.19':
resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==}
engines: {node: '>=12'}
@@ -4903,6 +4980,10 @@ packages:
rollup:
optional: true
+ '@rollup/pluginutils@4.2.1':
+ resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==}
+ engines: {node: '>= 8.0.0'}
+
'@rollup/pluginutils@5.1.4':
resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==}
engines: {node: '>=14.0.0'}
@@ -5981,6 +6062,9 @@ packages:
'@types/canvas-confetti@1.9.0':
resolution: {integrity: sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==}
+ '@types/chrome@0.0.176':
+ resolution: {integrity: sha512-LOveFOMIUhMJjvRzZv5whGBpncP/gdJ4hcxeAqg94wGi6CyKaCmLgFSofgItf85GuLTl/0BQ6J/Y1e8BqZWfEg==}
+
'@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
@@ -6026,6 +6110,12 @@ packages:
'@types/file-saver@2.0.7':
resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==}
+ '@types/filesystem@0.0.36':
+ resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==}
+
+ '@types/filewriter@0.0.33':
+ resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==}
+
'@types/fluent-ffmpeg@2.1.27':
resolution: {integrity: sha512-QiDWjihpUhriISNoBi2hJBRUUmoj/BMTYcfz+F+ZM9hHWBYABFAE6hjP/TbCZC0GWwlpa3FzvHH9RzFeRusZ7A==}
@@ -6035,6 +6125,9 @@ packages:
'@types/graceful-fs@4.1.9':
resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
+ '@types/har-format@1.2.16':
+ resolution: {integrity: sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==}
+
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
@@ -6467,6 +6560,10 @@ packages:
'@virtual-grid/shared@2.0.1':
resolution: {integrity: sha512-E0krspmtVOGRg/qAgDKUjhTRV7VXmFp7Q05ljT87Llffh8g5JoXVsAUPV7JiRKnrSRFShpiVdCy9eq0VvVeifA==}
+ '@vitejs/plugin-react@1.3.2':
+ resolution: {integrity: sha512-aurBNmMo0kz1O4qRoY+FM4epSA39y3ShWGuqfLRA/3z0oEJAdtoSfgA3aO98/PCCHAqMaduLxIxErWrVKIFzXA==}
+ engines: {node: '>=12.0.0'}
+
'@vitejs/plugin-react@4.4.1':
resolution: {integrity: sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -8062,11 +8159,136 @@ packages:
esast-util-from-js@2.0.1:
resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==}
+ esbuild-android-64@0.14.54:
+ resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+
+ esbuild-android-arm64@0.14.54:
+ resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+
+ esbuild-darwin-64@0.14.54:
+ resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+
+ esbuild-darwin-arm64@0.14.54:
+ resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+
+ esbuild-freebsd-64@0.14.54:
+ resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+
+ esbuild-freebsd-arm64@0.14.54:
+ resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ esbuild-linux-32@0.14.54:
+ resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+
+ esbuild-linux-64@0.14.54:
+ resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+
+ esbuild-linux-arm64@0.14.54:
+ resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+
+ esbuild-linux-arm@0.14.54:
+ resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+
+ esbuild-linux-mips64le@0.14.54:
+ resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+
+ esbuild-linux-ppc64le@0.14.54:
+ resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+
+ esbuild-linux-riscv64@0.14.54:
+ resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+
+ esbuild-linux-s390x@0.14.54:
+ resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+
+ esbuild-netbsd-64@0.14.54:
+ resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+
+ esbuild-openbsd-64@0.14.54:
+ resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+
esbuild-register@3.6.0:
resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==}
peerDependencies:
esbuild: '>=0.12 <1'
+ esbuild-sunos-64@0.14.54:
+ resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+
+ esbuild-windows-32@0.14.54:
+ resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+
+ esbuild-windows-64@0.14.54:
+ resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+
+ esbuild-windows-arm64@0.14.54:
+ resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+
+ esbuild@0.14.54:
+ resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==}
+ engines: {node: '>=12'}
+ hasBin: true
+
esbuild@0.17.19:
resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==}
engines: {node: '>=12'}
@@ -8818,6 +9040,9 @@ packages:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
engines: {node: '>=10.13.0'}
+ glob-regex@0.3.2:
+ resolution: {integrity: sha512-m5blUd3/OqDTWwzBBtWBPrGlAzatRywHameHeekAZyZrskYouOGdNB8T/q6JucucvJXtOuyHIn0/Yia7iDasDw==}
+
glob-to-regexp@0.4.1:
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
@@ -9004,6 +9229,9 @@ packages:
resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==}
engines: {node: ^16.14.0 || >=18.0.0}
+ hot-reload-extension-vite@1.0.13:
+ resolution: {integrity: sha512-bX9Bcvhay+xwVinLb/NdoA85NDAixbZ4xcmOyibvHjDjIzw1qllEGfN4LnoAi1jSfH0Mv5Sf5WrbplJS6IaMmg==}
+
html-encoding-sniffer@4.0.0:
resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
engines: {node: '>=18'}
@@ -9653,6 +9881,9 @@ packages:
jose@5.6.3:
resolution: {integrity: sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==}
+ js-confetti@0.12.0:
+ resolution: {integrity: sha512-1R0Akxn3Zn82pMqW65N1V2NwKkZJ75bvBN/VAb36Ya0YHwbaSiAJZVRr/19HBxH/O8x2x01UFAbYI18VqlDN6g==}
+
js-cookie@3.0.5:
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
engines: {node: '>=14'}
@@ -10559,7 +10790,6 @@ packages:
node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
- deprecated: Use your platform's native DOMException instead
node-fetch-native@1.6.6:
resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==}
@@ -11338,6 +11568,11 @@ packages:
peerDependencies:
react: ^16.3.0 || ^17.0.1 || ^18.0.0 || ^19.0.0
+ react-dom@18.3.1:
+ resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
+ peerDependencies:
+ react: ^18.3.1
+
react-dom@19.1.0:
resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==}
peerDependencies:
@@ -11354,6 +11589,13 @@ packages:
engines: {node: '>=18.0.0'}
hasBin: true
+ react-frame-component@5.2.7:
+ resolution: {integrity: sha512-ROjHtSLoSVYUBfTieazj/nL8jIX9rZFmHC0yXEU+dx6Y82OcBEGgU9o7VyHMrBFUN9FuQ849MtIPNNLsb4krbg==}
+ peerDependencies:
+ prop-types: ^15.5.9
+ react: '>= 16.3'
+ react-dom: '>= 16.3'
+
react-hls-player@3.0.7:
resolution: {integrity: sha512-i5QWNyLmaUhV/mgnpljRJT0CBfJnylClV/bne8aiXO3ZqU0+D3U/jtTDwdXM4i5qHhyFy9lemyZ179IgadKd0Q==}
peerDependencies:
@@ -11399,6 +11641,10 @@ packages:
react-promise-suspense@0.3.4:
resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==}
+ react-refresh@0.13.0:
+ resolution: {integrity: sha512-XP8A9BT0CpRBD+NYLLeIhld/RqG9+gktUjW1FkE+Vm7OCinbG1SshcK5tb9ls4kzvjZr9mOQc7HYgBngEyPAXg==}
+ engines: {node: '>=0.10.0'}
+
react-refresh@0.17.0:
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
engines: {node: '>=0.10.0'}
@@ -11475,6 +11721,10 @@ packages:
react: '>=16.14.0'
react-dom: '>=16.14.0'
+ react@18.3.1:
+ resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
+ engines: {node: '>=0.10.0'}
+
react@19.1.0:
resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
engines: {node: '>=0.10.0'}
@@ -11536,6 +11786,9 @@ packages:
recma-stringify@1.0.0:
resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==}
+ recrawl-sync@2.2.3:
+ resolution: {integrity: sha512-vSaTR9t+cpxlskkdUFrsEpnf67kSmPk66yAGT1fZPrDudxQjoMzPgQhSMImQ0pAw5k0NPirefQfhopSjhdUtpQ==}
+
redent@3.0.0:
resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
engines: {node: '>=8'}
@@ -11693,6 +11946,11 @@ packages:
rollup-pluginutils@2.8.2:
resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==}
+ rollup@2.77.3:
+ resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==}
+ engines: {node: '>=10.0.0'}
+ hasBin: true
+
rollup@4.40.2:
resolution: {integrity: sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -11740,6 +11998,9 @@ packages:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
+ scheduler@0.23.2:
+ resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
+
scheduler@0.26.0:
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
@@ -12546,8 +12807,8 @@ packages:
ts-pattern@5.7.0:
resolution: {integrity: sha512-0/FvIG4g3kNkYgbNwBBW5pZBkfpeYQnH+2AA3xmjkCAit/DSDPKmgwC3fKof4oYUq6gupClVOJlFl+939VRBMg==}
- ts-pattern@5.8.0:
- resolution: {integrity: sha512-kIjN2qmWiHnhgr5DAkAafF9fwb0T5OhMVSWrm8XEdTFnX6+wfXwYOFjeF86UZ54vduqiR7BfqScFmXSzSaH8oA==}
+ ts-pattern@5.7.1:
+ resolution: {integrity: sha512-EGs8PguQqAAUIcQfK4E9xdXxB6s2GK4sJfT/vcc9V1ELIvC4LH/zXu2t/5fajtv6oiRCxdv7BgtVK3vWgROxag==}
tsconfck@3.1.5:
resolution: {integrity: sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==}
@@ -12562,6 +12823,10 @@ packages:
tsconfig-paths@3.15.0:
resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
+ tsconfig-paths@4.2.0:
+ resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==}
+ engines: {node: '>=6'}
+
tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
@@ -12668,6 +12933,11 @@ packages:
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
engines: {node: '>= 0.4'}
+ typescript@4.9.5:
+ resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
+ engines: {node: '>=4.2.0'}
+ hasBin: true
+
typescript@5.8.3:
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
engines: {node: '>=14.17'}
@@ -13077,6 +13347,11 @@ packages:
'@testing-library/jest-dom':
optional: true
+ vite-tsconfig-paths@3.6.0:
+ resolution: {integrity: sha512-UfsPYonxLqPD633X8cWcPFVuYzx/CMNHAjZTasYwX69sXpa4gNmQkR0XCjj82h7zhLGdTWagMjC1qfb9S+zv0A==}
+ peerDependencies:
+ vite: '>2.0.0-0'
+
vite-tsconfig-paths@4.3.2:
resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==}
peerDependencies:
@@ -13093,6 +13368,22 @@ packages:
vite:
optional: true
+ vite@2.9.18:
+ resolution: {integrity: sha512-sAOqI5wNM9QvSEE70W3UGMdT8cyEn0+PmJMTFvTB8wB0YbYUWw3gUbY62AOyrXosGieF2htmeLATvNxpv/zNyQ==}
+ engines: {node: '>=12.2.0'}
+ hasBin: true
+ peerDependencies:
+ less: '*'
+ sass: '*'
+ stylus: '*'
+ peerDependenciesMeta:
+ less:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+
vite@5.4.19:
resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==}
engines: {node: ^18.0.0 || >=20.0.0}
@@ -13475,11 +13766,6 @@ packages:
engines: {node: '>= 14.6'}
hasBin: true
- yaml@2.8.1:
- resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==}
- engines: {node: '>= 14.6'}
- hasBin: true
-
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
@@ -13543,6 +13829,24 @@ packages:
zod@3.25.76:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
+ zustand@5.0.7:
+ resolution: {integrity: sha512-Ot6uqHDW/O2VdYsKLLU8GQu8sCOM1LcoE8RwvLv9uuRT9s6SOHCKs0ZEOhxg+I1Ld+A1Q5lwx+UlKXXUoCZITg==}
+ engines: {node: '>=12.20.0'}
+ peerDependencies:
+ '@types/react': '>=18.0.0'
+ immer: '>=9.0.6'
+ react: '>=18.0.0'
+ use-sync-external-store: '>=1.2.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+ use-sync-external-store:
+ optional: true
+
zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
@@ -14213,7 +14517,7 @@ snapshots:
'@babel/generator@7.27.1':
dependencies:
'@babel/parser': 7.27.2
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
'@jridgewell/gen-mapping': 0.3.8
'@jridgewell/trace-mapping': 0.3.25
jsesc: 3.1.0
@@ -14226,6 +14530,10 @@ snapshots:
'@jridgewell/trace-mapping': 0.3.25
jsesc: 3.1.0
+ '@babel/helper-annotate-as-pure@7.27.3':
+ dependencies:
+ '@babel/types': 7.27.6
+
'@babel/helper-compilation-targets@7.27.2':
dependencies:
'@babel/compat-data': 7.27.2
@@ -14236,12 +14544,12 @@ snapshots:
'@babel/helper-module-imports@7.18.6':
dependencies:
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
'@babel/helper-module-imports@7.27.1':
dependencies:
- '@babel/traverse': 7.27.1
- '@babel/types': 7.27.1
+ '@babel/traverse': 7.27.4
+ '@babel/types': 7.27.6
transitivePeerDependencies:
- supports-color
@@ -14265,7 +14573,7 @@ snapshots:
'@babel/helpers@7.27.1':
dependencies:
'@babel/template': 7.27.2
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
'@babel/highlight@7.25.9':
dependencies:
@@ -14276,7 +14584,7 @@ snapshots:
'@babel/parser@7.27.2':
dependencies:
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
'@babel/parser@7.27.5':
dependencies:
@@ -14367,6 +14675,13 @@ snapshots:
'@babel/core': 7.27.1
'@babel/helper-plugin-utils': 7.27.1
+ '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.27.1)':
+ dependencies:
+ '@babel/core': 7.27.1
+ '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.27.1)
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.27.1)':
dependencies:
'@babel/core': 7.27.1
@@ -14377,6 +14692,17 @@ snapshots:
'@babel/core': 7.27.1
'@babel/helper-plugin-utils': 7.27.1
+ '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.27.1)':
+ dependencies:
+ '@babel/core': 7.27.1
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.1)
+ '@babel/types': 7.27.6
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/runtime@7.27.1': {}
'@babel/standalone@7.27.2': {}
@@ -14385,7 +14711,7 @@ snapshots:
dependencies:
'@babel/code-frame': 7.27.1
'@babel/parser': 7.27.2
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
'@babel/traverse@7.27.1':
dependencies:
@@ -14393,7 +14719,7 @@ snapshots:
'@babel/generator': 7.27.1
'@babel/parser': 7.27.2
'@babel/template': 7.27.2
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
debug: 4.4.1
globals: 11.12.0
transitivePeerDependencies:
@@ -14565,9 +14891,9 @@ snapshots:
gray-matter: 4.0.3
imagescript: 1.3.1
micromatch: 4.0.8
- ts-pattern: 5.8.0
+ ts-pattern: 5.7.1
unified: 11.0.5
- yaml: 2.8.1
+ yaml: 2.8.0
zod: 3.25.76
transitivePeerDependencies:
- '@effect-ts/otel-node'
@@ -14646,6 +14972,8 @@ snapshots:
'@csstools/css-tokenizer@3.0.4': {}
+ '@cush/relative@1.0.0': {}
+
'@dabh/diagnostics@2.0.3':
dependencies:
colorspace: 1.1.4
@@ -14954,6 +15282,9 @@ snapshots:
'@esbuild/linux-ia32@0.25.5':
optional: true
+ '@esbuild/linux-loong64@0.14.54':
+ optional: true
+
'@esbuild/linux-loong64@0.17.19':
optional: true
@@ -17981,6 +18312,11 @@ snapshots:
optionalDependencies:
rollup: 4.40.2
+ '@rollup/pluginutils@4.2.1':
+ dependencies:
+ estree-walker: 2.0.2
+ picomatch: 2.3.1
+
'@rollup/pluginutils@5.1.4(rollup@4.40.2)':
dependencies:
'@types/estree': 1.0.7
@@ -18616,11 +18952,11 @@ snapshots:
dependencies:
solid-js: 1.9.6
- '@solidjs/start@1.1.3(@testing-library/jest-dom@6.5.0)(@types/node@22.15.17)(jiti@2.4.2)(solid-js@1.9.6)(terser@5.39.0)(vinxi@0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.1))(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1))(yaml@2.8.1)':
+ '@solidjs/start@1.1.3(@testing-library/jest-dom@6.5.0)(@types/node@22.15.17)(jiti@2.4.2)(solid-js@1.9.6)(terser@5.39.0)(vinxi@0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.0))(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0))(yaml@2.8.0)':
dependencies:
- '@tanstack/server-functions-plugin': 1.119.2(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)
- '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.1))
- '@vinxi/server-components': 0.5.1(vinxi@0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.1))
+ '@tanstack/server-functions-plugin': 1.119.2(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)
+ '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.0))
+ '@vinxi/server-components': 0.5.1(vinxi@0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.0))
defu: 6.1.4
error-stack-parser: 2.1.4
html-to-image: 1.11.13
@@ -18631,8 +18967,8 @@ snapshots:
source-map-js: 1.2.1
terracotta: 1.0.6(solid-js@1.9.6)
tinyglobby: 0.2.13
- vinxi: 0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.1)
- vite-plugin-solid: 2.11.6(@testing-library/jest-dom@6.5.0)(solid-js@1.9.6)(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1))
+ vinxi: 0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.0)
+ vite-plugin-solid: 2.11.6(@testing-library/jest-dom@6.5.0)(solid-js@1.9.6)(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0))
transitivePeerDependencies:
- '@testing-library/jest-dom'
- '@types/node'
@@ -18759,12 +19095,12 @@ snapshots:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
- '@storybook/builder-vite@9.2.0-alpha.2(storybook@8.6.12(prettier@3.5.3))(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1))':
+ '@storybook/builder-vite@9.2.0-alpha.2(storybook@8.6.12(prettier@3.5.3))(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0))':
dependencies:
'@storybook/csf-plugin': 9.2.0-alpha.2(storybook@8.6.12(prettier@3.5.3))
storybook: 8.6.12(prettier@3.5.3)
ts-dedent: 2.2.0
- vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)
+ vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)
'@storybook/core@8.6.12(prettier@3.5.3)(storybook@8.6.12(prettier@3.5.3))':
dependencies:
@@ -18904,7 +19240,7 @@ snapshots:
postcss-selector-parser: 6.0.10
tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@22.15.17)(typescript@5.8.3))
- '@tanstack/directive-functions-plugin@1.119.2(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)':
+ '@tanstack/directive-functions-plugin@1.119.2(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)':
dependencies:
'@babel/code-frame': 7.26.2
'@babel/core': 7.27.1
@@ -18912,12 +19248,12 @@ snapshots:
'@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.27.1)
'@babel/template': 7.27.2
'@babel/traverse': 7.27.1
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
'@tanstack/router-utils': 1.115.0
babel-dead-code-elimination: 1.0.10
dedent: 1.6.0
tiny-invariant: 1.3.3
- vite: 6.1.4(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)
+ vite: 6.1.4(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
@@ -18960,7 +19296,7 @@ snapshots:
ansis: 3.17.0
diff: 7.0.0
- '@tanstack/server-functions-plugin@1.119.2(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)':
+ '@tanstack/server-functions-plugin@1.119.2(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)':
dependencies:
'@babel/code-frame': 7.26.2
'@babel/core': 7.27.1
@@ -18969,7 +19305,7 @@ snapshots:
'@babel/template': 7.27.2
'@babel/traverse': 7.27.1
'@babel/types': 7.27.1
- '@tanstack/directive-functions-plugin': 1.119.2(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)
+ '@tanstack/directive-functions-plugin': 1.119.2(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)
babel-dead-code-elimination: 1.0.10
dedent: 1.6.0
tiny-invariant: 1.3.3
@@ -19233,16 +19569,16 @@ snapshots:
'@types/babel__generator@7.27.0':
dependencies:
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
'@types/babel__template@7.4.4':
dependencies:
'@babel/parser': 7.27.2
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
'@types/babel__traverse@7.20.7':
dependencies:
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
'@types/body-parser@1.19.5':
dependencies:
@@ -19260,6 +19596,11 @@ snapshots:
'@types/canvas-confetti@1.9.0': {}
+ '@types/chrome@0.0.176':
+ dependencies:
+ '@types/filesystem': 0.0.36
+ '@types/har-format': 1.2.16
+
'@types/connect@3.4.38':
dependencies:
'@types/node': 20.17.43
@@ -19315,6 +19656,12 @@ snapshots:
'@types/file-saver@2.0.7': {}
+ '@types/filesystem@0.0.36':
+ dependencies:
+ '@types/filewriter': 0.0.33
+
+ '@types/filewriter@0.0.33': {}
+
'@types/fluent-ffmpeg@2.1.27':
dependencies:
'@types/node': 20.17.43
@@ -19325,6 +19672,8 @@ snapshots:
dependencies:
'@types/node': 20.17.43
+ '@types/har-format@1.2.16': {}
+
'@types/hast@3.0.4':
dependencies:
'@types/unist': 3.0.3
@@ -19821,7 +20170,7 @@ snapshots:
untun: 0.1.3
uqr: 0.1.2
- '@vinxi/plugin-directives@0.5.1(vinxi@0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.1))':
+ '@vinxi/plugin-directives@0.5.1(vinxi@0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.0))':
dependencies:
'@babel/parser': 7.27.2
acorn: 8.14.1
@@ -19832,18 +20181,18 @@ snapshots:
magicast: 0.2.11
recast: 0.23.11
tslib: 2.8.1
- vinxi: 0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.1)
+ vinxi: 0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.0)
- '@vinxi/server-components@0.5.1(vinxi@0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.1))':
+ '@vinxi/server-components@0.5.1(vinxi@0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.0))':
dependencies:
- '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.1))
+ '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.0))
acorn: 8.14.1
acorn-loose: 8.5.0
acorn-typescript: 1.4.13(acorn@8.14.1)
astring: 1.9.0
magicast: 0.2.11
recast: 0.23.11
- vinxi: 0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.1)
+ vinxi: 0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.0)
'@virtual-grid/core@2.0.1': {}
@@ -19861,14 +20210,27 @@ snapshots:
'@virtual-grid/shared@2.0.1': {}
- '@vitejs/plugin-react@4.4.1(vite@6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1))':
+ '@vitejs/plugin-react@1.3.2':
+ dependencies:
+ '@babel/core': 7.27.1
+ '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.27.1)
+ '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.27.1)
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.1)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.27.1)
+ '@rollup/pluginutils': 4.2.1
+ react-refresh: 0.13.0
+ resolve: 1.22.10
+ transitivePeerDependencies:
+ - supports-color
+
+ '@vitejs/plugin-react@4.4.1(vite@6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0))':
dependencies:
'@babel/core': 7.27.1
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.1)
'@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.27.1)
'@types/babel__core': 7.20.5
react-refresh: 0.17.0
- vite: 6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)
+ vite: 6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)
transitivePeerDependencies:
- supports-color
@@ -20345,7 +20707,7 @@ snapshots:
'@babel/core': 7.27.1
'@babel/parser': 7.27.2
'@babel/traverse': 7.27.1
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
transitivePeerDependencies:
- supports-color
@@ -20375,7 +20737,7 @@ snapshots:
babel-plugin-jest-hoist@29.6.3:
dependencies:
'@babel/template': 7.27.2
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
'@types/babel__core': 7.20.5
'@types/babel__traverse': 7.20.7
@@ -20384,7 +20746,7 @@ snapshots:
'@babel/core': 7.27.1
'@babel/helper-module-imports': 7.18.6
'@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.1)
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
html-entities: 2.3.3
parse5: 7.3.0
validate-html-nesting: 1.2.2
@@ -21547,6 +21909,54 @@ snapshots:
esast-util-from-estree: 2.0.0
vfile-message: 4.0.2
+ esbuild-android-64@0.14.54:
+ optional: true
+
+ esbuild-android-arm64@0.14.54:
+ optional: true
+
+ esbuild-darwin-64@0.14.54:
+ optional: true
+
+ esbuild-darwin-arm64@0.14.54:
+ optional: true
+
+ esbuild-freebsd-64@0.14.54:
+ optional: true
+
+ esbuild-freebsd-arm64@0.14.54:
+ optional: true
+
+ esbuild-linux-32@0.14.54:
+ optional: true
+
+ esbuild-linux-64@0.14.54:
+ optional: true
+
+ esbuild-linux-arm64@0.14.54:
+ optional: true
+
+ esbuild-linux-arm@0.14.54:
+ optional: true
+
+ esbuild-linux-mips64le@0.14.54:
+ optional: true
+
+ esbuild-linux-ppc64le@0.14.54:
+ optional: true
+
+ esbuild-linux-riscv64@0.14.54:
+ optional: true
+
+ esbuild-linux-s390x@0.14.54:
+ optional: true
+
+ esbuild-netbsd-64@0.14.54:
+ optional: true
+
+ esbuild-openbsd-64@0.14.54:
+ optional: true
+
esbuild-register@3.6.0(esbuild@0.25.4):
dependencies:
debug: 4.4.1
@@ -21554,6 +21964,42 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ esbuild-sunos-64@0.14.54:
+ optional: true
+
+ esbuild-windows-32@0.14.54:
+ optional: true
+
+ esbuild-windows-64@0.14.54:
+ optional: true
+
+ esbuild-windows-arm64@0.14.54:
+ optional: true
+
+ esbuild@0.14.54:
+ optionalDependencies:
+ '@esbuild/linux-loong64': 0.14.54
+ esbuild-android-64: 0.14.54
+ esbuild-android-arm64: 0.14.54
+ esbuild-darwin-64: 0.14.54
+ esbuild-darwin-arm64: 0.14.54
+ esbuild-freebsd-64: 0.14.54
+ esbuild-freebsd-arm64: 0.14.54
+ esbuild-linux-32: 0.14.54
+ esbuild-linux-64: 0.14.54
+ esbuild-linux-arm: 0.14.54
+ esbuild-linux-arm64: 0.14.54
+ esbuild-linux-mips64le: 0.14.54
+ esbuild-linux-ppc64le: 0.14.54
+ esbuild-linux-riscv64: 0.14.54
+ esbuild-linux-s390x: 0.14.54
+ esbuild-netbsd-64: 0.14.54
+ esbuild-openbsd-64: 0.14.54
+ esbuild-sunos-64: 0.14.54
+ esbuild-windows-32: 0.14.54
+ esbuild-windows-64: 0.14.54
+ esbuild-windows-arm64: 0.14.54
+
esbuild@0.17.19:
optionalDependencies:
'@esbuild/android-arm': 0.17.19
@@ -21778,8 +22224,8 @@ snapshots:
'@typescript-eslint/parser': 5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
eslint: 9.30.1(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.30.1(jiti@2.4.2))
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.4.2))
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2))
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.30.1(jiti@2.4.2))
eslint-plugin-react: 7.37.5(eslint@9.30.1(jiti@2.4.2))
eslint-plugin-react-hooks: 4.6.2(eslint@9.30.1(jiti@2.4.2))
@@ -21807,33 +22253,33 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1):
+ eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.0(supports-color@5.5.0)
- eslint: 8.57.1
+ eslint: 9.30.1(jiti@2.4.2)
get-tsconfig: 4.10.0
is-bun-module: 2.0.0
stable-hash: 0.0.5
tinyglobby: 0.2.13
unrs-resolver: 1.7.2
optionalDependencies:
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2))
transitivePeerDependencies:
- supports-color
- eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.30.1(jiti@2.4.2)):
+ eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.0(supports-color@5.5.0)
- eslint: 9.30.1(jiti@2.4.2)
+ eslint: 8.57.1
get-tsconfig: 4.10.0
is-bun-module: 2.0.0
stable-hash: 0.0.5
tinyglobby: 0.2.13
unrs-resolver: 1.7.2
optionalDependencies:
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
@@ -21848,14 +22294,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.4.2)):
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
eslint: 9.30.1(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.30.1(jiti@2.4.2))
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2))
transitivePeerDependencies:
- supports-color
@@ -21899,7 +22345,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.4.2)):
+ eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -21910,7 +22356,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.30.1(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.4.2))
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -22813,6 +23259,8 @@ snapshots:
dependencies:
is-glob: 4.0.3
+ glob-regex@0.3.2: {}
+
glob-to-regexp@0.4.1: {}
glob@10.3.10:
@@ -23089,6 +23537,13 @@ snapshots:
dependencies:
lru-cache: 10.4.3
+ hot-reload-extension-vite@1.0.13:
+ dependencies:
+ ws: 8.18.2
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
html-encoding-sniffer@4.0.0:
dependencies:
whatwg-encoding: 3.1.1
@@ -23816,7 +24271,7 @@ snapshots:
'@babel/generator': 7.27.1
'@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.1)
'@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.27.1)
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
'@jest/expect-utils': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
@@ -23897,6 +24352,8 @@ snapshots:
jose@5.6.3: {}
+ js-confetti@0.12.0: {}
+
js-cookie@3.0.5: {}
js-tokens@4.0.0: {}
@@ -24196,13 +24653,13 @@ snapshots:
magicast@0.2.11:
dependencies:
'@babel/parser': 7.27.2
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
recast: 0.23.11
magicast@0.3.5:
dependencies:
'@babel/parser': 7.27.2
- '@babel/types': 7.27.1
+ '@babel/types': 7.27.6
source-map-js: 1.2.1
make-dir@3.1.0:
@@ -25732,6 +26189,14 @@ snapshots:
postcss: 8.5.3
ts-node: 10.9.2(@types/node@20.17.43)(typescript@5.8.3)
+ postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.15.17)(typescript@4.9.5)):
+ dependencies:
+ lilconfig: 3.1.3
+ yaml: 2.7.1
+ optionalDependencies:
+ postcss: 8.5.3
+ ts-node: 10.9.2(@types/node@22.15.17)(typescript@4.9.5)
+
postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.15.17)(typescript@5.8.3)):
dependencies:
lilconfig: 3.1.3
@@ -25979,6 +26444,12 @@ snapshots:
react: 19.1.0
tween-functions: 1.2.0
+ react-dom@18.3.1(react@18.3.1):
+ dependencies:
+ loose-envify: 1.4.0
+ react: 18.3.1
+ scheduler: 0.23.2
+
react-dom@19.1.0(react@19.1.0):
dependencies:
react: 19.1.0
@@ -26020,6 +26491,12 @@ snapshots:
- supports-color
- utf-8-validate
+ react-frame-component@5.2.7(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ prop-types: 15.8.1
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
react-hls-player@3.0.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
hls.js: 0.14.17
@@ -26057,6 +26534,8 @@ snapshots:
dependencies:
fast-deep-equal: 2.0.1
+ react-refresh@0.13.0: {}
+
react-refresh@0.17.0: {}
react-remove-scroll-bar@2.3.8(@types/react@18.3.21)(react@19.1.0):
@@ -26130,6 +26609,10 @@ snapshots:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
+ react@18.3.1:
+ dependencies:
+ loose-envify: 1.4.0
+
react@19.1.0: {}
read-cache@1.0.0:
@@ -26229,6 +26712,14 @@ snapshots:
unified: 11.0.5
vfile: 6.0.3
+ recrawl-sync@2.2.3:
+ dependencies:
+ '@cush/relative': 1.0.0
+ glob-regex: 0.3.2
+ slash: 3.0.0
+ sucrase: 3.35.0
+ tslib: 1.14.1
+
redent@3.0.0:
dependencies:
indent-string: 4.0.0
@@ -26422,6 +26913,10 @@ snapshots:
dependencies:
estree-walker: 0.6.1
+ rollup@2.77.3:
+ optionalDependencies:
+ fsevents: 2.3.3
+
rollup@4.40.2:
dependencies:
'@types/estree': 1.0.7
@@ -26497,6 +26992,10 @@ snapshots:
dependencies:
xmlchars: 2.2.0
+ scheduler@0.23.2:
+ dependencies:
+ loose-envify: 1.4.0
+
scheduler@0.26.0: {}
scule@1.3.0: {}
@@ -26978,16 +27477,16 @@ snapshots:
stoppable@1.1.0: {}
- storybook-solidjs-vite@1.0.0-beta.7(@storybook/test@8.6.12(storybook@8.6.12(prettier@3.5.3)))(solid-js@1.9.6)(storybook@8.6.12(prettier@3.5.3))(vite-plugin-solid@2.11.6(@testing-library/jest-dom@6.5.0)(solid-js@1.9.6)(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)))(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)):
+ storybook-solidjs-vite@1.0.0-beta.7(@storybook/test@8.6.12(storybook@8.6.12(prettier@3.5.3)))(solid-js@1.9.6)(storybook@8.6.12(prettier@3.5.3))(vite-plugin-solid@2.11.6(@testing-library/jest-dom@6.5.0)(solid-js@1.9.6)(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)))(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)):
dependencies:
- '@storybook/builder-vite': 9.2.0-alpha.2(storybook@8.6.12(prettier@3.5.3))(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1))
+ '@storybook/builder-vite': 9.2.0-alpha.2(storybook@8.6.12(prettier@3.5.3))(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0))
'@storybook/types': 9.0.0-alpha.1(storybook@8.6.12(prettier@3.5.3))
magic-string: 0.30.17
solid-js: 1.9.6
storybook: 8.6.12(prettier@3.5.3)
storybook-solidjs: 1.0.0-beta.7(@storybook/test@8.6.12(storybook@8.6.12(prettier@3.5.3)))(solid-js@1.9.6)(storybook@8.6.12(prettier@3.5.3))
- vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)
- vite-plugin-solid: 2.11.6(@testing-library/jest-dom@6.5.0)(solid-js@1.9.6)(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1))
+ vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)
+ vite-plugin-solid: 2.11.6(@testing-library/jest-dom@6.5.0)(solid-js@1.9.6)(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0))
transitivePeerDependencies:
- '@storybook/test'
@@ -27278,6 +27777,33 @@ snapshots:
transitivePeerDependencies:
- ts-node
+ tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.15.17)(typescript@4.9.5)):
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ arg: 5.0.2
+ chokidar: 3.6.0
+ didyoumean: 1.2.2
+ dlv: 1.1.3
+ fast-glob: 3.3.3
+ glob-parent: 6.0.2
+ is-glob: 4.0.3
+ jiti: 1.21.7
+ lilconfig: 3.1.3
+ micromatch: 4.0.8
+ normalize-path: 3.0.0
+ object-hash: 3.0.0
+ picocolors: 1.1.1
+ postcss: 8.5.3
+ postcss-import: 15.1.0(postcss@8.5.3)
+ postcss-js: 4.0.1(postcss@8.5.3)
+ postcss-load-config: 4.0.2(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.15.17)(typescript@4.9.5))
+ postcss-nested: 6.2.0(postcss@8.5.3)
+ postcss-selector-parser: 6.1.2
+ resolve: 1.22.10
+ sucrase: 3.35.0
+ transitivePeerDependencies:
+ - ts-node
+
tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.15.17)(typescript@5.8.3)):
dependencies:
'@alloc/quick-lru': 5.2.0
@@ -27496,6 +28022,25 @@ snapshots:
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
+ ts-node@10.9.2(@types/node@22.15.17)(typescript@4.9.5):
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1
+ '@tsconfig/node10': 1.0.11
+ '@tsconfig/node12': 1.0.11
+ '@tsconfig/node14': 1.0.3
+ '@tsconfig/node16': 1.0.4
+ '@types/node': 22.15.17
+ acorn: 8.15.0
+ acorn-walk: 8.3.4
+ arg: 4.1.3
+ create-require: 1.1.1
+ diff: 4.0.2
+ make-error: 1.3.6
+ typescript: 4.9.5
+ v8-compile-cache-lib: 3.0.1
+ yn: 3.1.1
+ optional: true
+
ts-node@10.9.2(@types/node@22.15.17)(typescript@5.8.3):
dependencies:
'@cspotcode/source-map-support': 0.8.1
@@ -27517,7 +28062,7 @@ snapshots:
ts-pattern@5.7.0: {}
- ts-pattern@5.8.0: {}
+ ts-pattern@5.7.1: {}
tsconfck@3.1.5(typescript@5.8.3):
optionalDependencies:
@@ -27530,6 +28075,12 @@ snapshots:
minimist: 1.2.8
strip-bom: 3.0.0
+ tsconfig-paths@4.2.0:
+ dependencies:
+ json5: 2.2.3
+ minimist: 1.2.8
+ strip-bom: 3.0.0
+
tslib@1.14.1: {}
tslib@2.6.2: {}
@@ -27638,6 +28189,8 @@ snapshots:
possible-typed-array-names: 1.1.0
reflect.getprototypeof: 1.0.10
+ typescript@4.9.5: {}
+
typescript@5.8.3: {}
ufo@1.6.1: {}
@@ -27839,11 +28392,11 @@ snapshots:
transitivePeerDependencies:
- rollup
- unplugin-fonts@1.3.1(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)):
+ unplugin-fonts@1.3.1(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)):
dependencies:
fast-glob: 3.3.3
unplugin: 2.0.0-beta.1
- vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)
+ vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)
unplugin-icons@0.19.3:
dependencies:
@@ -27989,6 +28542,11 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.21
+ use-sync-external-store@1.5.0(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ optional: true
+
use-sync-external-store@1.5.0(react@19.1.0):
dependencies:
react: 19.1.0
@@ -28053,7 +28611,7 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.2
- vinxi@0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.1):
+ vinxi@0.5.6(@planetscale/database@1.19.0)(@types/node@22.15.17)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(encoding@0.1.13)(ioredis@5.6.1)(jiti@2.4.2)(mysql2@3.14.1)(terser@5.39.0)(xml2js@0.6.2)(yaml@2.8.0):
dependencies:
'@babel/core': 7.27.1
'@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.1)
@@ -28087,7 +28645,7 @@ snapshots:
unctx: 2.4.1
unenv: 1.10.0
unstorage: 1.16.0(@planetscale/database@1.19.0)(db0@0.3.2(drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250507.0)(@opentelemetry/api@1.9.0)(@planetscale/database@1.19.0)(mysql2@3.14.1))(mysql2@3.14.1))(ioredis@5.6.1)
- vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)
+ vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)
zod: 3.25.76
transitivePeerDependencies:
- '@azure/app-configuration'
@@ -28149,7 +28707,7 @@ snapshots:
- supports-color
- terser
- vite-plugin-solid@2.11.6(@testing-library/jest-dom@6.5.0)(solid-js@1.9.6)(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)):
+ vite-plugin-solid@2.11.6(@testing-library/jest-dom@6.5.0)(solid-js@1.9.6)(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)):
dependencies:
'@babel/core': 7.27.1
'@types/babel__core': 7.20.5
@@ -28157,35 +28715,54 @@ snapshots:
merge-anything: 5.1.7
solid-js: 1.9.6
solid-refresh: 0.6.3(solid-js@1.9.6)
- vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)
- vitefu: 1.0.6(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1))
+ vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)
+ vitefu: 1.0.6(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0))
optionalDependencies:
'@testing-library/jest-dom': 6.5.0
transitivePeerDependencies:
- supports-color
- vite-tsconfig-paths@4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)):
+ vite-tsconfig-paths@3.6.0(vite@2.9.18):
+ dependencies:
+ debug: 4.4.1
+ globrex: 0.1.2
+ recrawl-sync: 2.2.3
+ tsconfig-paths: 4.2.0
+ vite: 2.9.18
+ transitivePeerDependencies:
+ - supports-color
+
+ vite-tsconfig-paths@4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)):
dependencies:
debug: 4.4.0(supports-color@5.5.0)
globrex: 0.1.2
tsconfck: 3.1.5(typescript@5.8.3)
optionalDependencies:
- vite: 6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)
+ vite: 6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)
transitivePeerDependencies:
- supports-color
- typescript
- vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)):
+ vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)):
dependencies:
debug: 4.4.0(supports-color@5.5.0)
globrex: 0.1.2
tsconfck: 3.1.5(typescript@5.8.3)
optionalDependencies:
- vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)
+ vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)
transitivePeerDependencies:
- supports-color
- typescript
+ vite@2.9.18:
+ dependencies:
+ esbuild: 0.14.54
+ postcss: 8.5.3
+ resolve: 1.22.10
+ rollup: 2.77.3
+ optionalDependencies:
+ fsevents: 2.3.3
+
vite@5.4.19(@types/node@22.15.17)(terser@5.39.0):
dependencies:
esbuild: 0.21.5
@@ -28196,7 +28773,7 @@ snapshots:
fsevents: 2.3.3
terser: 5.39.0
- vite@6.1.4(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1):
+ vite@6.1.4(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0):
dependencies:
esbuild: 0.24.2
postcss: 8.5.3
@@ -28206,9 +28783,9 @@ snapshots:
fsevents: 2.3.3
jiti: 2.4.2
terser: 5.39.0
- yaml: 2.8.1
+ yaml: 2.8.0
- vite@6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1):
+ vite@6.3.5(@types/node@20.17.43)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0):
dependencies:
esbuild: 0.25.4
fdir: 6.4.4(picomatch@4.0.2)
@@ -28221,9 +28798,9 @@ snapshots:
fsevents: 2.3.3
jiti: 2.4.2
terser: 5.39.0
- yaml: 2.8.1
+ yaml: 2.8.0
- vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1):
+ vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0):
dependencies:
esbuild: 0.25.4
fdir: 6.4.4(picomatch@4.0.2)
@@ -28236,11 +28813,11 @@ snapshots:
fsevents: 2.3.3
jiti: 2.4.2
terser: 5.39.0
- yaml: 2.8.1
+ yaml: 2.8.0
- vitefu@1.0.6(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)):
+ vitefu@1.0.6(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)):
optionalDependencies:
- vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.1)
+ vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(terser@5.39.0)(yaml@2.8.0)
vitest@2.1.9(@types/node@22.15.17)(jsdom@26.1.0)(terser@5.39.0):
dependencies:
@@ -28533,8 +29110,6 @@ snapshots:
yaml@2.8.0: {}
- yaml@2.8.1: {}
-
yargs-parser@21.1.1: {}
yargs@17.7.2:
@@ -28610,4 +29185,10 @@ snapshots:
zod@3.25.76: {}
+ zustand@5.0.7(@types/react@18.3.21)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)):
+ optionalDependencies:
+ '@types/react': 18.3.21
+ react: 18.3.1
+ use-sync-external-store: 1.5.0(react@18.3.1)
+
zwitch@2.0.4: {}
From 28e4ffa9e5012e3e9664a0060fdd79c0ac020e88 Mon Sep 17 00:00:00 2001
From: ameer2468 <33054370+ameer2468@users.noreply.github.com>
Date: Mon, 11 Aug 2025 19:57:56 +0300
Subject: [PATCH 08/36] correct queries
---
apps/loom-importer-extension/vite.config.ts | 8 +--
apps/web/app/api/import/loom.ts | 63 +++++++++++----------
2 files changed, 35 insertions(+), 36 deletions(-)
diff --git a/apps/loom-importer-extension/vite.config.ts b/apps/loom-importer-extension/vite.config.ts
index 0f26acd16..be8c46086 100644
--- a/apps/loom-importer-extension/vite.config.ts
+++ b/apps/loom-importer-extension/vite.config.ts
@@ -7,11 +7,9 @@ import hotReloadExtension from "hot-reload-extension-vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
- process.env.MODE !== "production"
- ? react({
- jsxRuntime: "classic",
- })
- : react(),
+ // Use automatic runtime in all modes so JSX doesn't require React in scope
+ // across all files (prevents 'React is not defined' in popup and content scripts).
+ react({ jsxRuntime: "automatic" }),
tsconfigPaths(),
hotReloadExtension({
log: true,
diff --git a/apps/web/app/api/import/loom.ts b/apps/web/app/api/import/loom.ts
index d93bd8227..4d30926d8 100644
--- a/apps/web/app/api/import/loom.ts
+++ b/apps/web/app/api/import/loom.ts
@@ -5,6 +5,7 @@ import {
spaceMembers,
videos,
sharedVideos,
+ organizationMembers,
} from "@cap/database/schema";
import { db } from "@cap/database";
import { eq, inArray, or } from "drizzle-orm";
@@ -55,16 +56,16 @@ app.post(
let targetSpaceId: string;
- const userSpaces = await db
- .select({ spaceId: spaces.id })
+ const userSpaces = await db()
+ .select({ spacesId: spaces.id })
.from(spaces)
.leftJoin(spaceMembers, eq(spaces.id, spaceMembers.spaceId))
.where(
- or(eq(spaces.ownerId, user.id), eq(spaceMembers.userId, user.id))
+ or(eq(spaces.createdById, user.id), eq(spaceMembers.userId, user.id))
);
const hasAccess = userSpaces.some(
- (space) => space.spaceId === body.selectedWorkspaceId
+ (space) => space.spacesId === body.selectedWorkspaceId
);
if (hasAccess) {
@@ -84,7 +85,7 @@ app.post(
targetSpaceId
);
- await addUsersToOwnerWorkspace(userIds, user.id, targetSpaceId);
+ await addUsersToOwnerOrganization(userIds, user.id, targetSpaceId);
await importVideosFromLoom(
body.videos,
@@ -116,12 +117,12 @@ app.post(
* Creates user accounts for Loom workspace members
*/
async function createUsersFromLoomWorkspaceMembers(
- workspaceMembers: WorkspaceMember[],
- workspaceId: string
+ organizationMembers: WorkspaceMember[],
+ organizationId: string
) {
- const emails = workspaceMembers.map((member) => member.email);
+ const emails = organizationMembers.map((member) => member.email);
- const existingUsers = await db
+ const existingUsers = await db()
.select({ id: users.id, email: users.email })
.from(users)
.where(inArray(users.email, emails));
@@ -129,7 +130,7 @@ async function createUsersFromLoomWorkspaceMembers(
const existingEmails = new Set(existingUsers.map((user) => user.email));
const newUserIds: string[] = existingUsers.map((user) => user.id);
- for (const member of workspaceMembers.filter(
+ for (const member of organizationMembers.filter(
(m) => !existingEmails.has(m.email)
)) {
const nameParts = member.name.split(" ");
@@ -137,13 +138,13 @@ async function createUsersFromLoomWorkspaceMembers(
const lastName = nameParts.slice(1).join(" ") || "";
const userId = nanoId();
- await db.insert(users).values({
+ await db().insert(users).values({
id: userId,
email: member.email,
name: firstName,
lastName: lastName,
inviteQuota: 1,
- activeSpaceId: workspaceId,
+ activeOrganizationId: organizationId,
});
newUserIds.push(userId);
@@ -153,17 +154,17 @@ async function createUsersFromLoomWorkspaceMembers(
}
/**
- * Adds users to the owner's workspace
+ * Adds users to the owner's organization
*/
-async function addUsersToOwnerWorkspace(
+async function addUsersToOwnerOrganization(
userIds: string[],
ownerId: string,
- spaceId: string
+ organizationId: string
) {
- const existingMembers = await db
- .select({ userId: spaceMembers.userId })
- .from(spaceMembers)
- .where(eq(spaceMembers.spaceId, spaceId));
+ const existingMembers = await db()
+ .select({ userId: organizationMembers.userId })
+ .from(organizationMembers)
+ .where(eq(organizationMembers.organizationId, organizationId));
const existingMemberIds = new Set(
existingMembers.map((member) => member.userId)
@@ -172,25 +173,25 @@ async function addUsersToOwnerWorkspace(
for (const userId of userIds.filter(
(id) => !existingMemberIds.has(id) && id !== ownerId
)) {
- await db.insert(spaceMembers).values({
+ await db().insert(organizationMembers).values({
id: nanoId(),
userId: userId,
- spaceId: spaceId,
+ organizationId: organizationId,
role: "member",
createdAt: new Date(),
updatedAt: new Date(),
});
- const user = await db
- .select({ activeSpaceId: users.activeSpaceId })
+ const user = await db()
+ .select({ activeOrganizationId: users.activeOrganizationId })
.from(users)
.where(eq(users.id, userId))
.then((results) => results[0]);
- if (!user?.activeSpaceId) {
- await db
+ if (!user?.activeOrganizationId) {
+ await db()
.update(users)
- .set({ activeSpaceId: spaceId })
+ .set({ activeOrganizationId: organizationId })
.where(eq(users.id, userId));
}
}
@@ -219,7 +220,7 @@ async function downloadVideoFromLoom(videoId: string) {
async function importVideosFromLoom(
loomVideos: Video[],
ownerId: string,
- spaceId: string,
+ organizationId: string,
userEmail: string
) {
for (const loomVideo of loomVideos) {
@@ -234,7 +235,7 @@ async function importVideosFromLoom(
}
// Otherwise try to find user by email
else if (owner && owner.email) {
- const existingOwner = await db
+ const existingOwner = await db()
.select({ id: users.id })
.from(users)
.where(eq(users.email, owner.email))
@@ -248,7 +249,7 @@ async function importVideosFromLoom(
const videoData = await downloadVideoFromLoom(loomVideo.id);
const videoId = nanoId();
- await db.insert(videos).values({
+ await db().insert(videos).values({
id: videoId,
ownerId: videoOwnerId,
name: loomVideo.title,
@@ -261,10 +262,10 @@ async function importVideosFromLoom(
});
if (videoOwnerId !== ownerId) {
- await db.insert(sharedVideos).values({
+ await db().insert(sharedVideos).values({
id: nanoId(),
videoId: videoId,
- spaceId: spaceId,
+ organizationId: organizationId,
sharedByUserId: ownerId,
sharedAt: new Date(),
});
From 224f9bcc8bea1e867b4a11bbb4af35eae9a58501 Mon Sep 17 00:00:00 2001
From: ameer2468 <33054370+ameer2468@users.noreply.github.com>
Date: Mon, 11 Aug 2025 19:58:34 +0300
Subject: [PATCH 09/36] Update loom.ts
---
apps/web/app/api/import/loom.ts | 24 +++++++++++++-----------
1 file changed, 13 insertions(+), 11 deletions(-)
diff --git a/apps/web/app/api/import/loom.ts b/apps/web/app/api/import/loom.ts
index 4d30926d8..4c166ea27 100644
--- a/apps/web/app/api/import/loom.ts
+++ b/apps/web/app/api/import/loom.ts
@@ -249,17 +249,19 @@ async function importVideosFromLoom(
const videoData = await downloadVideoFromLoom(loomVideo.id);
const videoId = nanoId();
- await db().insert(videos).values({
- id: videoId,
- ownerId: videoOwnerId,
- name: loomVideo.title,
- loomVideoId: loomVideo.id,
- public: true,
- metadata: videoData.metadata,
- source: { type: "desktopMP4" },
- createdAt: new Date(),
- updatedAt: new Date(),
- });
+ await db()
+ .insert(videos)
+ .values({
+ id: videoId,
+ ownerId: videoOwnerId,
+ name: loomVideo.title,
+ loomVideoId: loomVideo.id,
+ public: true,
+ metadata: videoData.metadata,
+ source: { type: "desktopMP4" },
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ });
if (videoOwnerId !== ownerId) {
await db().insert(sharedVideos).values({
From 88ae2670d823d3b6aabd57ab2762fe4c23bec2e3 Mon Sep 17 00:00:00 2001
From: Oscar Beaumont
Date: Tue, 12 Aug 2025 09:43:26 +0800
Subject: [PATCH 10/36] New recording flow (#831)
* start on new recording flow
* more work
* some area selection
* progress on window selection
* fix `createLicenseQuery`'s key
* format
* windows wip[
* wip
* escape to close overlay
* allow deselecting the capture
* wip
* wip
* wip
* fix up camera select logic
* wip
* wip
* bruh
* wip
* remove broken Windows z-index tests
* Windows z-index prototype
* windows focus improvements
* Fixed
* windows is pain
* overhaul code to be good and platform-agnostic
* nit
* fix
* merge `Main` and `NewMain` windows
* move new recording flow feature to frontend
* fix window size on spawn
* fix
* Fix screen focus state on Windows
* minor fixes to windows
* don't allow window selection to target a Cap window on Windows
* maybe fix Windows window-select implementation
* `get_topmost_at_cursor` Windows wip
* fix
* bad code but might work
* remove debug
* fix clippy
* implement display size API
* show screen name + resolution + fps
* better display name wip
* using displays localizedName
* fix resolution + app icon wip
* render window icon
* base64 in Rust + make icon a bit bigger
* allow adjusting the window capture area easily
* cleanup target select tokio task
* windows wip
* fix `Escape` being stolen from 3rd-party apps
* fix text color on windows
* overhaul Windows monitor name implementatiin
* improve overlay contrast
* drop text overrides for now
* fix experimental toggles from scrolling whole page
* Fix Windows application icon code
* make new recording flow feature more user-friendly
* fixes to new recording flow toggle
* wip: windows implementation
* fix up coord scaling logic
* Windows higher resolution app icon
* fix double registering the keyboard shortcut
* cleanup app icon logic for windows?
* code cleanup
* windows localized display name
* improve windows z-index handling
* Delete TODO
* remove flag
* a bunch more cleanup
* minor cleanup
* more cleanup
* more cleanup
* Make Windows compile again
* cargo fmt
* break
* fixup navigate
* remove logs
* fixes
* cleanup image handling
* more cleanup of Windows implemementation
* only display recording flow toggle in dev
* disable experimental toggle outright
* fix imports
* hicon_to_png_bytes_high_res
* remove nspanel from global
* cleanup for merge
---------
Co-authored-by: Brendan Allan
---
Cargo.lock | 44 +-
apps/desktop/package.json | 1 +
apps/desktop/src-tauri/Cargo.toml | 2 +
.../desktop/src-tauri/src/general_settings.rs | 34 +-
apps/desktop/src-tauri/src/hotkeys.rs | 8 +-
apps/desktop/src-tauri/src/lib.rs | 23 +-
.../src-tauri/src/target_select_overlay.rs | 206 +++
apps/desktop/src-tauri/src/tray.rs | 10 +-
apps/desktop/src-tauri/src/windows.rs | 84 +-
apps/desktop/src/App.tsx | 3 +
.../src/routes/(window-chrome)/(main).tsx | 9 +-
.../src/routes/(window-chrome)/new-main.tsx | 751 +++++++++
.../(window-chrome)/settings/experimental.tsx | 72 +-
.../(window-chrome)/settings/general.tsx | 2 +-
.../src/routes/target-select-overlay.tsx | 600 ++++++++
apps/desktop/src/utils/queries.ts | 44 +-
apps/desktop/src/utils/tauri.ts | 32 +-
apps/web/app/queries.ts | 0
crates/displays/Cargo.toml | 22 +-
crates/displays/src/bounds.rs | 101 ++
crates/displays/src/lib.rs | 158 +-
crates/displays/src/main.rs | 131 +-
crates/displays/src/platform/macos.rs | 379 ++++-
crates/displays/src/platform/mod.rs | 4 +-
crates/displays/src/platform/win.rs | 1359 ++++++++++++++++-
crates/media/src/feeds/camera.rs | 2 +-
packages/ui-solid/src/Button.tsx | 2 +-
packages/ui-solid/src/auto-imports.d.ts | 9 +
pnpm-lock.yaml | 40 +-
29 files changed, 4005 insertions(+), 127 deletions(-)
create mode 100644 apps/desktop/src-tauri/src/target_select_overlay.rs
create mode 100644 apps/desktop/src/routes/(window-chrome)/new-main.tsx
create mode 100644 apps/desktop/src/routes/target-select-overlay.tsx
delete mode 100644 apps/web/app/queries.ts
create mode 100644 crates/displays/src/bounds.rs
diff --git a/Cargo.lock b/Cargo.lock
index 6362f5186..72749058e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -188,7 +188,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55f533f8e0af236ffe5eb979b99381df3258853f00ba2e44b6e1955292c75227"
dependencies = [
"clipboard-win",
- "image",
+ "image 0.25.6",
"log",
"objc2 0.6.1",
"objc2-app-kit",
@@ -980,6 +980,7 @@ dependencies = [
"bytemuck",
"cap-audio",
"cap-camera",
+ "cap-displays",
"cap-editor",
"cap-export",
"cap-fail",
@@ -1004,7 +1005,7 @@ dependencies = [
"futures",
"futures-intrusive",
"global-hotkey",
- "image",
+ "image 0.25.6",
"keyed_priority_queue",
"lazy_static",
"log",
@@ -1064,7 +1065,13 @@ dependencies = [
name = "cap-displays"
version = "0.1.0"
dependencies = [
+ "cocoa 0.26.1",
+ "core-foundation 0.10.1",
"core-graphics 0.24.0",
+ "image 0.24.9",
+ "objc",
+ "serde",
+ "specta",
"windows 0.60.0",
"windows-sys 0.59.0",
]
@@ -1106,7 +1113,7 @@ dependencies = [
"clap",
"ffmpeg-next",
"futures",
- "image",
+ "image 0.25.6",
"inquire",
"mp4",
"serde",
@@ -1164,7 +1171,7 @@ dependencies = [
"flume",
"futures",
"gif",
- "image",
+ "image 0.25.6",
"indexmap 2.10.0",
"inquire",
"kameo",
@@ -1248,7 +1255,7 @@ dependencies = [
"flume",
"futures",
"hex",
- "image",
+ "image 0.25.6",
"objc",
"objc2-app-kit",
"relative-path",
@@ -1286,7 +1293,7 @@ dependencies = [
"futures",
"futures-intrusive",
"glyphon",
- "image",
+ "image 0.25.6",
"log",
"pretty_assertions",
"reactive_graph",
@@ -1538,7 +1545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afede46921767868c5c7f8f55202bdd8bec0bab6bc9605174200f45924f93c62"
dependencies = [
"clipboard-win",
- "image",
+ "image 0.25.6",
"objc2 0.6.1",
"objc2-app-kit",
"objc2-foundation 0.3.1",
@@ -3810,6 +3817,24 @@ dependencies = [
"icu_properties",
]
+[[package]]
+name = "image"
+version = "0.24.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
+dependencies = [
+ "bytemuck",
+ "byteorder",
+ "color_quant",
+ "exr",
+ "gif",
+ "jpeg-decoder",
+ "num-traits",
+ "png",
+ "qoi",
+ "tiff",
+]
+
[[package]]
name = "image"
version = "0.25.6"
@@ -4071,6 +4096,9 @@ name = "jpeg-decoder"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07"
+dependencies = [
+ "rayon",
+]
[[package]]
name = "js-sys"
@@ -7896,7 +7924,7 @@ dependencies = [
"heck 0.5.0",
"http",
"http-range",
- "image",
+ "image 0.25.6",
"jni",
"libc",
"log",
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index 625c6cab0..11df6dd64 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -28,6 +28,7 @@
"@solid-primitives/event-listener": "^2.3.3",
"@solid-primitives/history": "^0.1.5",
"@solid-primitives/memo": "^1.4.2",
+ "@solid-primitives/mouse": "^2.1.2",
"@solid-primitives/refs": "^1.0.8",
"@solid-primitives/resize-observer": "^2.0.26",
"@solid-primitives/scheduled": "^1.4.3",
diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml
index d6b39d985..4b585db18 100644
--- a/apps/desktop/src-tauri/Cargo.toml
+++ b/apps/desktop/src-tauri/Cargo.toml
@@ -88,6 +88,8 @@ cap-media = { path = "../../../crates/media" }
cap-flags = { path = "../../../crates/flags" }
cap-recording = { path = "../../../crates/recording" }
cap-export = { path = "../../../crates/export" }
+cap-displays = { path = "../../../crates/displays" }
+
flume.workspace = true
tracing-subscriber = "0.3.19"
tracing-appender = "0.2.3"
diff --git a/apps/desktop/src-tauri/src/general_settings.rs b/apps/desktop/src-tauri/src/general_settings.rs
index 62252e295..67a39e4c5 100644
--- a/apps/desktop/src-tauri/src/general_settings.rs
+++ b/apps/desktop/src-tauri/src/general_settings.rs
@@ -80,18 +80,38 @@ pub struct GeneralSettingsStore {
pub server_url: String,
#[serde(default)]
pub recording_countdown: Option,
- #[serde(default, alias = "open_editor_after_recording")]
- #[deprecated]
- _open_editor_after_recording: bool,
- #[deprecated = "can be removed when native camera preview is ready"]
- #[serde(default)]
+ // #[deprecated = "can be removed when native camera preview is ready"]
+ #[serde(
+ default = "default_enable_native_camera_preview",
+ skip_serializing_if = "no"
+ )]
pub enable_native_camera_preview: bool,
#[serde(default)]
pub auto_zoom_on_clicks: bool,
+ // #[deprecated = "can be removed when new recording flow is the default"]
+ #[serde(
+ default = "default_enable_new_recording_flow",
+ skip_serializing_if = "no"
+ )]
+ pub enable_new_recording_flow: bool,
#[serde(default)]
pub post_deletion_behaviour: PostDeletionBehaviour,
}
+fn default_enable_native_camera_preview() -> bool {
+ // TODO:
+ // cfg!(target_os = "macos")
+ false
+}
+
+fn default_enable_new_recording_flow() -> bool {
+ cfg!(debug_assertions)
+}
+
+fn no(_: &bool) -> bool {
+ false
+}
+
fn default_server_url() -> String {
std::option_env!("VITE_SERVER_URL")
.unwrap_or("https://cap.so")
@@ -127,9 +147,9 @@ impl Default for GeneralSettingsStore {
custom_cursor_capture: false,
server_url: default_server_url(),
recording_countdown: Some(3),
- _open_editor_after_recording: false,
- enable_native_camera_preview: false,
+ enable_native_camera_preview: default_enable_native_camera_preview(),
auto_zoom_on_clicks: false,
+ enable_new_recording_flow: default_enable_new_recording_flow(),
post_deletion_behaviour: PostDeletionBehaviour::DoNothing,
}
}
diff --git a/apps/desktop/src-tauri/src/hotkeys.rs b/apps/desktop/src-tauri/src/hotkeys.rs
index d4f5a99ac..0247fdd58 100644
--- a/apps/desktop/src-tauri/src/hotkeys.rs
+++ b/apps/desktop/src-tauri/src/hotkeys.rs
@@ -65,6 +65,9 @@ impl HotkeysStore {
}
}
+#[derive(Serialize, Type, tauri_specta::Event, Debug, Clone)]
+pub struct OnEscapePress;
+
pub type HotkeysState = Mutex;
pub fn init(app: &AppHandle) {
app.plugin(
@@ -74,6 +77,10 @@ pub fn init(app: &AppHandle) {
return;
}
+ if shortcut.key == Code::Escape {
+ OnEscapePress.emit(app).ok();
+ }
+
let state = app.state::();
let store = state.lock().unwrap();
@@ -90,7 +97,6 @@ pub fn init(app: &AppHandle) {
let store = HotkeysStore::get(app).unwrap().unwrap_or_default();
let global_shortcut = app.global_shortcut();
-
for hotkey in store.hotkeys.values() {
global_shortcut.register(Shortcut::from(*hotkey)).ok();
}
diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs
index fb38f62dd..1003710bd 100644
--- a/apps/desktop/src-tauri/src/lib.rs
+++ b/apps/desktop/src-tauri/src/lib.rs
@@ -16,6 +16,7 @@ mod permissions;
mod platform;
mod presets;
mod recording;
+mod target_select_overlay;
mod tray;
mod upload;
mod web_api;
@@ -69,6 +70,7 @@ use tauri::Window;
use tauri::{AppHandle, Manager, State, WindowEvent};
use tauri_plugin_deep_link::DeepLinkExt;
use tauri_plugin_dialog::DialogExt;
+use tauri_plugin_global_shortcut::GlobalShortcutExt;
use tauri_plugin_notification::{NotificationExt, PermissionState};
use tauri_plugin_opener::OpenerExt;
use tauri_plugin_shell::ShellExt;
@@ -1758,7 +1760,7 @@ async fn get_system_audio_waveforms(
#[tauri::command]
#[specta::specta]
async fn show_window(app: AppHandle, window: ShowCapWindow) -> Result<(), String> {
- window.show(&app).await.unwrap();
+ let _ = window.show(&app).await;
Ok(())
}
@@ -1951,7 +1953,9 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
captions::download_whisper_model,
captions::check_model_exists,
captions::delete_whisper_model,
- captions::export_captions_srt
+ captions::export_captions_srt,
+ target_select_overlay::open_target_select_overlays,
+ target_select_overlay::close_target_select_overlays,
])
.events(tauri_specta::collect_events![
RecordingOptionsChanged,
@@ -1971,7 +1975,9 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
UploadProgress,
captions::DownloadProgress,
recording::RecordingEvent,
- RecordingDeleted
+ RecordingDeleted,
+ target_select_overlay::TargetUnderCursor,
+ hotkeys::OnEscapePress
])
.error_handling(tauri_specta::ErrorHandlingMode::Throw)
.typ::()
@@ -2042,6 +2048,7 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
.with_denylist(&[
CapWindowId::Setup.label().as_str(),
"window-capture-occluder",
+ "target-select-overlay",
CapWindowId::CaptureArea.label().as_str(),
CapWindowId::Camera.label().as_str(),
CapWindowId::RecordingsOverlay.label().as_str(),
@@ -2053,6 +2060,7 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
label if label.starts_with("window-capture-occluder-") => {
"window-capture-occluder"
}
+ label if label.starts_with("target-select-overlay") => "target-select-overlay",
_ => label,
})
.build(),
@@ -2064,6 +2072,7 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
hotkeys::init(&app);
general_settings::init(&app);
fake_window::init(&app);
+ app.manage(target_select_overlay::WindowFocusManager::default());
app.manage(EditorWindowIds::default());
if let Ok(Some(auth)) = AuthStore::load(&app) {
@@ -2197,6 +2206,10 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
}
return;
}
+ CapWindowId::TargetSelectOverlay { display_id } => {
+ app.state::()
+ .destroy(&display_id, app.global_shortcut());
+ }
_ => {}
};
}
@@ -2256,7 +2269,9 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
}
} else {
let handle = handle.clone();
- tokio::spawn(async move { ShowCapWindow::Main.show(&handle).await });
+ tokio::spawn(async move {
+ let _ = ShowCapWindow::Main.show(&handle).await;
+ });
}
}
tauri::RunEvent::ExitRequested { code, api, .. } => {
diff --git a/apps/desktop/src-tauri/src/target_select_overlay.rs b/apps/desktop/src-tauri/src/target_select_overlay.rs
new file mode 100644
index 000000000..a9bf72810
--- /dev/null
+++ b/apps/desktop/src-tauri/src/target_select_overlay.rs
@@ -0,0 +1,206 @@
+use std::{
+ collections::HashMap,
+ str::FromStr,
+ sync::{Mutex, PoisonError},
+ time::Duration,
+};
+
+use base64::prelude::*;
+
+use crate::windows::{CapWindowId, ShowCapWindow};
+use cap_displays::{
+ DisplayId, WindowId,
+ bounds::{LogicalBounds, PhysicalSize},
+};
+use serde::Serialize;
+use specta::Type;
+use tauri::{AppHandle, Manager, WebviewWindow};
+use tauri_plugin_global_shortcut::{GlobalShortcut, GlobalShortcutExt};
+use tauri_specta::Event;
+use tokio::task::JoinHandle;
+use tracing::error;
+
+#[derive(tauri_specta::Event, Serialize, Type, Clone)]
+pub struct TargetUnderCursor {
+ display_id: Option,
+ window: Option,
+ screen: Option,
+}
+
+#[derive(Serialize, Type, Clone)]
+pub struct WindowUnderCursor {
+ id: WindowId,
+ app_name: String,
+ bounds: LogicalBounds,
+ icon: Option,
+}
+
+#[derive(Serialize, Type, Clone)]
+pub struct ScreenUnderCursor {
+ name: String,
+ physical_size: PhysicalSize,
+ refresh_rate: String,
+}
+
+#[specta::specta]
+#[tauri::command]
+pub async fn open_target_select_overlays(
+ app: AppHandle,
+ state: tauri::State<'_, WindowFocusManager>,
+) -> Result<(), String> {
+ let displays = cap_displays::Display::list()
+ .into_iter()
+ .map(|d| d.id())
+ .collect::>();
+ for display_id in displays {
+ let _ = ShowCapWindow::TargetSelectOverlay { display_id }
+ .show(&app)
+ .await;
+ }
+
+ let handle = tokio::spawn({
+ let app = app.clone();
+ async move {
+ loop {
+ let display = cap_displays::Display::get_containing_cursor();
+ let window = cap_displays::Window::get_topmost_at_cursor();
+
+ let _ = TargetUnderCursor {
+ display_id: display.map(|d| d.id()),
+ window: window.and_then(|w| {
+ Some(WindowUnderCursor {
+ id: w.id(),
+ bounds: w.bounds()?,
+ app_name: w.owner_name()?,
+ icon: w.app_icon().map(|bytes| {
+ format!("data:image/png;base64,{}", BASE64_STANDARD.encode(&bytes))
+ }),
+ })
+ }),
+ screen: display.map(|d| ScreenUnderCursor {
+ name: d.name(),
+ physical_size: d.physical_size(),
+ refresh_rate: d.refresh_rate().to_string(),
+ }),
+ }
+ .emit(&app);
+
+ tokio::time::sleep(Duration::from_millis(50)).await;
+ }
+ }
+ });
+
+ if let Some(task) = state
+ .task
+ .lock()
+ .unwrap_or_else(PoisonError::into_inner)
+ .replace(handle)
+ {
+ task.abort();
+ } else {
+ // If task is already set we know we have already registered this.
+ app.global_shortcut()
+ .register("Escape")
+ .map_err(|err| error!("Error registering global keyboard shortcut for Escape: {err}"))
+ .ok();
+ }
+
+ Ok(())
+}
+
+#[specta::specta]
+#[tauri::command]
+pub async fn close_target_select_overlays(
+ app: AppHandle,
+ // state: tauri::State<'_, WindowFocusManager>,
+) -> Result<(), String> {
+ for (id, window) in app.webview_windows() {
+ if let Ok(CapWindowId::TargetSelectOverlay { .. }) = CapWindowId::from_str(&id) {
+ let _ = window.close();
+ }
+ }
+
+ Ok(())
+}
+
+// Windows doesn't have a proper concept of window z-index's so we implement them in userspace :(
+#[derive(Default)]
+pub struct WindowFocusManager {
+ task: Mutex>>,
+ tasks: Mutex>>,
+}
+
+impl WindowFocusManager {
+ /// Called when a window is created to spawn it's task
+ pub fn spawn(&self, id: &DisplayId, window: WebviewWindow) {
+ let mut tasks = self.tasks.lock().unwrap_or_else(PoisonError::into_inner);
+ tasks.insert(
+ id.to_string(),
+ tokio::spawn(async move {
+ let app = window.app_handle();
+ loop {
+ let Some(cap_main) = CapWindowId::Main.get(app) else {
+ window.close().ok();
+ break;
+ };
+
+ // If the main window is minimized or not visible, close the overlay
+ //
+ // This is a workaround for the fact that the Cap main window
+ // is minimized when opening settings, etc instead of it being
+ // closed.
+ if cap_main.is_minimized().ok().unwrap_or_default()
+ || cap_main.is_visible().map(|v| !v).ok().unwrap_or_default()
+ {
+ window.close().ok();
+ break;
+ }
+
+ #[cfg(windows)]
+ {
+ let should_refocus = cap_main.is_focused().ok().unwrap_or_default()
+ || window.is_focused().unwrap_or_default();
+
+ // If a Cap window is not focused we know something is trying to steal the focus.
+ // We need to move the overlay above it. We don't use `always_on_top` on the overlay because we need the Cap window to stay above it.
+ if !should_refocus {
+ window.set_focus().ok();
+ }
+ }
+
+ tokio::time::sleep(std::time::Duration::from_millis(300)).await;
+ }
+ }),
+ );
+ }
+
+ /// Called when a specific overlay window is destroyed to cleanup it's resources
+ pub fn destroy(&self, id: &DisplayId, global_shortcut: &GlobalShortcut) {
+ let mut tasks = self.tasks.lock().unwrap_or_else(PoisonError::into_inner);
+ if let Some(task) = tasks.remove(&id.to_string()) {
+ let _ = task.abort();
+ }
+
+ // When all overlay windows are closed cleanup shared resources.
+ if tasks.is_empty() {
+ // Unregister keyboard shortcut
+ // This messes with other applications if we don't remove it.
+ global_shortcut
+ .unregister("Escape")
+ .map_err(|err| {
+ error!("Error unregistering global keyboard shortcut for Escape: {err}")
+ })
+ .ok();
+
+ // Shutdown the cursor tracking task
+ if let Some(task) = self
+ .task
+ .lock()
+ .unwrap_or_else(PoisonError::into_inner)
+ .take()
+ {
+ task.abort();
+ }
+ }
+ }
+}
diff --git a/apps/desktop/src-tauri/src/tray.rs b/apps/desktop/src-tauri/src/tray.rs
index 3d76112f8..dced37786 100644
--- a/apps/desktop/src-tauri/src/tray.rs
+++ b/apps/desktop/src-tauri/src/tray.rs
@@ -3,8 +3,10 @@ use crate::{
RecordingStarted, RecordingStopped, RequestNewScreenshot, RequestOpenSettings, recording,
};
-use std::sync::Arc;
-use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::{
+ Arc,
+ atomic::{AtomicBool, Ordering},
+};
use tauri::Manager;
use tauri::menu::{MenuId, PredefinedMenuItem};
use tauri::{
@@ -99,7 +101,9 @@ pub fn create_tray(app: &AppHandle) -> tauri::Result<()> {
move |app: &AppHandle, event| match TrayItem::try_from(event.id) {
Ok(TrayItem::OpenCap) => {
let app = app.clone();
- tokio::spawn(async move { ShowCapWindow::Main.show(&app).await });
+ tokio::spawn(async move {
+ let _ = ShowCapWindow::Main.show(&app).await;
+ });
}
Ok(TrayItem::TakeScreenshot) => {
let _ = RequestNewScreenshot.emit(&app_handle);
diff --git a/apps/desktop/src-tauri/src/windows.rs b/apps/desktop/src-tauri/src/windows.rs
index f9d47b996..41b4ad79c 100644
--- a/apps/desktop/src-tauri/src/windows.rs
+++ b/apps/desktop/src-tauri/src/windows.rs
@@ -1,8 +1,13 @@
#![allow(unused_mut)]
#![allow(unused_imports)]
-use crate::{App, ArcLock, fake_window, general_settings::AppTheme, permissions};
-use cap_flags::FLAGS;
+use crate::{
+ App, ArcLock, fake_window,
+ general_settings::{AppTheme, GeneralSettingsStore},
+ permissions,
+ target_select_overlay::WindowFocusManager,
+};
+use cap_displays::DisplayId;
use cap_media::{platform::logical_monitor_bounds, sources::CaptureScreen};
use futures::pin_mut;
use serde::Deserialize;
@@ -23,7 +28,7 @@ use tracing::debug;
#[cfg(target_os = "macos")]
const DEFAULT_TRAFFIC_LIGHTS_INSET: LogicalPosition = LogicalPosition::new(12.0, 12.0);
-#[derive(Clone)]
+#[derive(Clone, Deserialize, Type)]
pub enum CapWindowId {
// Contains onboarding + permissions
Setup,
@@ -32,6 +37,7 @@ pub enum CapWindowId {
Editor { id: u32 },
RecordingsOverlay,
WindowCaptureOccluder { screen_id: u32 },
+ TargetSelectOverlay { display_id: DisplayId },
CaptureArea,
Camera,
InProgressRecording,
@@ -67,6 +73,12 @@ impl FromStr for CapWindowId {
.parse::()
.map_err(|e| e.to_string())?,
},
+ s if s.starts_with("target-select-overlay-") => Self::TargetSelectOverlay {
+ display_id: s
+ .replace("target-select-overlay-", "")
+ .parse::()
+ .map_err(|e| e.to_string())?,
+ },
_ => return Err(format!("unknown window label: {s}")),
})
}
@@ -83,6 +95,9 @@ impl std::fmt::Display for CapWindowId {
write!(f, "window-capture-occluder-{screen_id}")
}
Self::CaptureArea => write!(f, "capture-area"),
+ Self::TargetSelectOverlay { display_id } => {
+ write!(f, "target-select-overlay-{display_id}")
+ }
Self::InProgressRecording => write!(f, "in-progress-recording"),
Self::RecordingsOverlay => write!(f, "recordings-overlay"),
Self::Upgrade => write!(f, "upgrade"),
@@ -138,7 +153,8 @@ impl CapWindowId {
Self::Camera
| Self::WindowCaptureOccluder { .. }
| Self::CaptureArea
- | Self::RecordingsOverlay => None,
+ | Self::RecordingsOverlay
+ | Self::TargetSelectOverlay { .. } => None,
_ => Some(None),
}
}
@@ -165,6 +181,7 @@ pub enum ShowCapWindow {
Editor { project_path: PathBuf },
RecordingsOverlay,
WindowCaptureOccluder { screen_id: u32 },
+ TargetSelectOverlay { display_id: DisplayId },
CaptureArea { screen_id: u32 },
Camera,
InProgressRecording { countdown: Option },
@@ -174,7 +191,7 @@ pub enum ShowCapWindow {
impl ShowCapWindow {
pub async fn show(&self, app: &AppHandle) -> tauri::Result {
- if let Self::Editor { project_path } = self {
+ if let Self::Editor { project_path } = &self {
let state = app.state::();
let mut s = state.ids.lock().unwrap();
if !s.iter().any(|(path, _)| path == project_path) {
@@ -207,18 +224,68 @@ impl ShowCapWindow {
.build()?,
Self::Main => {
if permissions::do_permissions_check(false).necessary_granted() {
- self.window_builder(app, "/")
+ let new_recording_flow = GeneralSettingsStore::get(&app)
+ .ok()
+ .flatten()
+ .map(|s| s.enable_new_recording_flow)
+ .unwrap_or_default();
+
+ let window = self
+ .window_builder(app, if new_recording_flow { "/new-main" } else { "/" })
.resizable(false)
.maximized(false)
.maximizable(false)
.always_on_top(true)
.visible_on_all_workspaces(true)
.center()
- .build()?
+ .build()?;
+
+ if new_recording_flow {
+ #[cfg(target_os = "macos")]
+ crate::platform::set_window_level(window.as_ref().window(), 50);
+ }
+
+ window
} else {
Box::pin(Self::Setup.show(app)).await?
}
}
+ Self::TargetSelectOverlay { display_id } => {
+ let Some(display) = cap_displays::Display::from_id(display_id.clone()) else {
+ return Err(tauri::Error::WindowNotFound);
+ };
+
+ let size = display.raw_handle().logical_size();
+ let position = display.raw_handle().logical_position();
+
+ let mut window_builder = self
+ .window_builder(
+ app,
+ format!("/target-select-overlay?displayId={display_id}"),
+ )
+ .maximized(false)
+ .resizable(false)
+ .fullscreen(false)
+ .shadow(false)
+ .always_on_top(cfg!(target_os = "macos"))
+ .visible_on_all_workspaces(true)
+ .skip_taskbar(true)
+ .inner_size(size.width(), size.height())
+ .position(position.x(), position.y())
+ .transparent(true);
+
+ let window = window_builder.build()?;
+
+ app.state::()
+ .spawn(display_id, window.clone());
+
+ #[cfg(target_os = "macos")]
+ {
+ crate::platform::set_window_level(window.as_ref().window(), 45);
+ }
+
+ window
+ }
Self::Settings { page } => {
// Hide main window when settings window opens
if let Some(main) = CapWindowId::Main.get(app) {
@@ -554,6 +621,9 @@ impl ShowCapWindow {
CapWindowId::Editor { id }
}
ShowCapWindow::RecordingsOverlay => CapWindowId::RecordingsOverlay,
+ ShowCapWindow::TargetSelectOverlay { display_id } => CapWindowId::TargetSelectOverlay {
+ display_id: display_id.clone(),
+ },
ShowCapWindow::WindowCaptureOccluder { screen_id } => {
CapWindowId::WindowCaptureOccluder {
screen_id: *screen_id,
diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx
index 37010abad..ec198a941 100644
--- a/apps/desktop/src/App.tsx
+++ b/apps/desktop/src/App.tsx
@@ -27,6 +27,9 @@ import { CapErrorBoundary } from "./components/CapErrorBoundary";
const queryClient = new QueryClient({
defaultOptions: {
+ queries: {
+ experimental_prefetchInRender: true,
+ },
mutations: {
onError: (e) => {
message(`Error\n${e}`);
diff --git a/apps/desktop/src/routes/(window-chrome)/(main).tsx b/apps/desktop/src/routes/(window-chrome)/(main).tsx
index d446c8bc4..81f96855e 100644
--- a/apps/desktop/src/routes/(window-chrome)/(main).tsx
+++ b/apps/desktop/src/routes/(window-chrome)/(main).tsx
@@ -64,6 +64,13 @@ function Page() {
const currentRecording = createCurrentRecordingQuery();
const generalSettings = generalSettingsStore.createQuery();
+ // We do this on focus so the window doesn't get revealed when toggling the setting
+ const navigate = useNavigate();
+ createEventListener(window, "focus", () => {
+ if (generalSettings.data?.enableNewRecordingFlow === true)
+ navigate("/new-main");
+ });
+
const isRecording = () => !!currentRecording.data;
const license = createLicenseQuery();
@@ -489,7 +496,6 @@ function useRequestPermission() {
if (type === "camera") {
await commands.resetCameraPermissions();
} else if (type === "microphone") {
- console.log("wowzers");
await commands.resetMicrophonePermissions();
}
await commands.requestPermission(type);
@@ -522,6 +528,7 @@ import {
useRecordingOptions,
} from "./OptionsContext";
import { createTauriEventListener } from "~/utils/createEventListener";
+import { createEventListener } from "@solid-primitives/event-listener";
let hasChecked = false;
function createUpdateCheck() {
diff --git a/apps/desktop/src/routes/(window-chrome)/new-main.tsx b/apps/desktop/src/routes/(window-chrome)/new-main.tsx
new file mode 100644
index 000000000..22cc15844
--- /dev/null
+++ b/apps/desktop/src/routes/(window-chrome)/new-main.tsx
@@ -0,0 +1,751 @@
+import { useNavigate } from "@solidjs/router";
+import {
+ createMutation,
+ createQuery,
+ useQueryClient,
+} from "@tanstack/solid-query";
+import { getVersion } from "@tauri-apps/api/app";
+import {
+ getCurrentWindow,
+ LogicalSize,
+ primaryMonitor,
+ Window,
+} from "@tauri-apps/api/window";
+import { cx } from "cva";
+import {
+ ComponentProps,
+ createEffect,
+ createResource,
+ createSignal,
+ onCleanup,
+ onMount,
+ Show,
+} from "solid-js";
+import { createStore, reconcile } from "solid-js/store";
+
+import Tooltip from "~/components/Tooltip";
+import { trackEvent } from "~/utils/analytics";
+import {
+ createCameraMutation,
+ createCurrentRecordingQuery,
+ createLicenseQuery,
+ getPermissions,
+ listAudioDevices,
+ listScreens,
+ listVideoDevices,
+ listWindows,
+} from "~/utils/queries";
+import {
+ CameraInfo,
+ type CaptureScreen,
+ commands,
+ DeviceOrModelID,
+ events,
+ ScreenCaptureTarget,
+} from "~/utils/tauri";
+
+function getWindowSize() {
+ return {
+ width: 270,
+ height: 255,
+ };
+}
+
+const findCamera = (cameras: CameraInfo[], id: DeviceOrModelID) => {
+ return cameras.find((c) => {
+ if (!id) return false;
+ return "DeviceID" in id
+ ? id.DeviceID === c.device_id
+ : id.ModelID === c.model_id;
+ });
+};
+
+export default function () {
+ const generalSettings = generalSettingsStore.createQuery();
+
+ // We do this on focus so the window doesn't get revealed when toggling the setting
+ const navigate = useNavigate();
+ createEventListener(window, "focus", () => {
+ if (generalSettings.data?.enableNewRecordingFlow === false) navigate("/");
+ });
+
+ return (
+
+
+
+ );
+}
+
+function Page() {
+ const { rawOptions, setOptions } = useRecordingOptions();
+
+ const license = createLicenseQuery();
+
+ createUpdateCheck();
+
+ onMount(async () => {
+ // Enforce window size with multiple safeguards
+ const currentWindow = getCurrentWindow();
+
+ // We resize the window on mount as the user could be switching to the new recording flow
+ // which has a differently sized window.
+ const size = getWindowSize();
+ currentWindow.setSize(new LogicalSize(size.width, size.height));
+
+ // Check size when app regains focus
+ const unlistenFocus = currentWindow.onFocusChanged(
+ ({ payload: focused }) => {
+ if (focused) {
+ const size = getWindowSize();
+
+ currentWindow.setSize(new LogicalSize(size.width, size.height));
+ }
+ }
+ );
+
+ // Listen for resize events
+ const unlistenResize = currentWindow.onResized(() => {
+ const size = getWindowSize();
+
+ currentWindow.setSize(new LogicalSize(size.width, size.height));
+ });
+
+ onCleanup(async () => {
+ (await unlistenFocus)?.();
+ (await unlistenResize)?.();
+ });
+
+ const monitor = await primaryMonitor();
+ if (!monitor) return;
+ });
+
+ createEffect(() => {
+ if (rawOptions.targetMode) commands.openTargetSelectOverlays();
+ else commands.closeTargetSelectOverlays();
+ });
+
+ const screens = createQuery(() => listScreens);
+ const windows = createQuery(() => listWindows);
+ const cameras = createQuery(() => listVideoDevices);
+ const mics = createQuery(() => listAudioDevices);
+
+ cameras.promise.then((cameras) => {
+ if (rawOptions.cameraID && findCamera(cameras, rawOptions.cameraID)) {
+ setOptions("cameraLabel", null);
+ }
+ });
+
+ mics.promise.then((mics) => {
+ if (rawOptions.micName && !mics.includes(rawOptions.micName)) {
+ setOptions("micName", null);
+ }
+ });
+
+ // these options take the raw config values and combine them with the available options,
+ // allowing us to define fallbacks if the selected options aren't actually available
+ const options = {
+ screen: () => {
+ let screen;
+
+ if (rawOptions.captureTarget.variant === "screen") {
+ const screenId = rawOptions.captureTarget.id;
+ screen =
+ screens.data?.find((s) => s.id === screenId) ?? screens.data?.[0];
+ } else if (rawOptions.captureTarget.variant === "area") {
+ const screenId = rawOptions.captureTarget.screen;
+ screen =
+ screens.data?.find((s) => s.id === screenId) ?? screens.data?.[0];
+ }
+
+ return screen;
+ },
+ window: () => {
+ let win;
+
+ if (rawOptions.captureTarget.variant === "window") {
+ const windowId = rawOptions.captureTarget.id;
+ win = windows.data?.find((s) => s.id === windowId) ?? windows.data?.[0];
+ }
+
+ return win;
+ },
+ camera: () => {
+ if (!rawOptions.cameraID) return undefined;
+ return findCamera(cameras.data || [], rawOptions.cameraID);
+ },
+
+ micName: () => mics.data?.find((name) => name === rawOptions.micName),
+ target: (): ScreenCaptureTarget => {
+ switch (rawOptions.captureTarget.variant) {
+ case "screen":
+ return { variant: "screen", id: options.screen()?.id ?? -1 };
+ case "window":
+ return { variant: "window", id: options.window()?.id ?? -1 };
+ case "area":
+ return {
+ variant: "area",
+ bounds: rawOptions.captureTarget.bounds,
+ screen: options.screen()?.id ?? -1,
+ };
+ }
+ },
+ };
+
+ // if target is window and no windows are available, switch to screen capture
+ createEffect(() => {
+ if (options.target().variant === "window" && windows.data?.length === 0) {
+ setOptions(
+ "captureTarget",
+ reconcile({
+ variant: "screen",
+ id: options.screen()?.id ?? -1,
+ })
+ );
+ }
+ });
+
+ const setMicInput = createMutation(() => ({
+ mutationFn: async (name: string | null) => {
+ await commands.setMicInput(name);
+ setOptions("micName", name);
+ },
+ }));
+
+ const setCamera = createCameraMutation();
+
+ onMount(() => {
+ if (rawOptions.cameraID && "ModelID" in rawOptions.cameraID)
+ setCamera.mutate({ ModelID: rawOptions.cameraID.ModelID });
+ else if (rawOptions.cameraID && "DeviceID" in rawOptions.cameraID)
+ setCamera.mutate({ DeviceID: rawOptions.cameraID.DeviceID });
+ else setCamera.mutate(null);
+ });
+
+ return (
+
+
+
+
Settings}>
+ {
+ await commands.showWindow({ Settings: { page: "general" } });
+ getCurrentWindow().hide();
+ }}
+ class="flex items-center justify-center w-5 h-5 -ml-[1.5px]"
+ >
+
+
+
+
Previous Recordings}>
+ {
+ await commands.showWindow({ Settings: { page: "recordings" } });
+ getCurrentWindow().hide();
+ }}
+ class="flex justify-center items-center w-5 h-5"
+ >
+
+
+
+
+
+
+
+ commands.showWindow("Upgrade")}
+ class="flex relative justify-center items-center w-5 h-5"
+ >
+
+
+
+
+
+ {import.meta.env.DEV && (
+
{
+ new WebviewWindow("debug", { url: "/debug" });
+ }}
+ class="flex justify-center items-center w-5 h-5"
+ >
+
+
+ )}
+
+
+
+
+ setOptions("targetMode", (v) => (v === "screen" ? null : "screen"))
+ }
+ name="Screen"
+ />
+
+ setOptions("targetMode", (v) => (v === "window" ? null : "window"))
+ }
+ name="Window"
+ />
+
+ setOptions("targetMode", (v) => (v === "area" ? null : "area"))
+ }
+ name="Area"
+ />
+
+
{
+ if (!c) setCamera.mutate(null);
+ else if (c.model_id) setCamera.mutate({ ModelID: c.model_id });
+ else setCamera.mutate({ DeviceID: c.device_id });
+ }}
+ />
+ setMicInput.mutate(v)}
+ />
+
+
+ );
+}
+
+function useRequestPermission() {
+ const queryClient = useQueryClient();
+
+ async function requestPermission(type: "camera" | "microphone") {
+ try {
+ if (type === "camera") {
+ await commands.resetCameraPermissions();
+ } else if (type === "microphone") {
+ await commands.resetMicrophonePermissions();
+ }
+ await commands.requestPermission(type);
+ await queryClient.refetchQueries(getPermissions);
+ } catch (error) {
+ console.error(`Failed to get ${type} permission:`, error);
+ }
+ }
+
+ return requestPermission;
+}
+
+import { makePersisted } from "@solid-primitives/storage";
+import { CheckMenuItem, Menu, PredefinedMenuItem } from "@tauri-apps/api/menu";
+import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
+import * as dialog from "@tauri-apps/plugin-dialog";
+import { type as ostype } from "@tauri-apps/plugin-os";
+import * as updater from "@tauri-apps/plugin-updater";
+
+import { generalSettingsStore } from "~/store";
+import { apiClient } from "~/utils/web-api";
+import {
+ RecordingOptionsProvider,
+ useRecordingOptions,
+} from "./OptionsContext";
+import type { Component } from "solid-js";
+import { WindowChromeHeader } from "./Context";
+import { createEventListener } from "@solid-primitives/event-listener";
+
+let hasChecked = false;
+function createUpdateCheck() {
+ if (import.meta.env.DEV) return;
+
+ const navigate = useNavigate();
+
+ onMount(async () => {
+ if (hasChecked) return;
+ hasChecked = true;
+
+ await new Promise((res) => setTimeout(res, 1000));
+
+ const update = await updater.check();
+ if (!update) return;
+
+ const shouldUpdate = await dialog.confirm(
+ `Version ${update.version} of Cap is available, would you like to install it?`,
+ { title: "Update Cap", okLabel: "Update", cancelLabel: "Ignore" }
+ );
+
+ if (!shouldUpdate) return;
+ navigate("/update");
+ });
+}
+
+const NO_CAMERA = "No Camera";
+
+function CameraSelect(props: {
+ disabled?: boolean;
+ options: CameraInfo[];
+ value: CameraInfo | null;
+ onChange: (camera: CameraInfo | null) => void;
+}) {
+ const currentRecording = createCurrentRecordingQuery();
+ const permissions = createQuery(() => getPermissions);
+ const requestPermission = useRequestPermission();
+
+ const permissionGranted = () =>
+ permissions?.data?.camera === "granted" ||
+ permissions?.data?.camera === "notNeeded";
+
+ const onChange = (cameraLabel: CameraInfo | null) => {
+ if (!cameraLabel && permissions?.data?.camera !== "granted")
+ return requestPermission("camera");
+
+ props.onChange(cameraLabel);
+
+ trackEvent("camera_selected", {
+ camera_name: cameraLabel?.display_name ?? null,
+ enabled: !!cameraLabel,
+ });
+ };
+
+ return (
+
+ {
+ Promise.all([
+ CheckMenuItem.new({
+ text: NO_CAMERA,
+ checked: props.value === null,
+ action: () => onChange(null),
+ }),
+ PredefinedMenuItem.new({ item: "Separator" }),
+ ...props.options.map((o) =>
+ CheckMenuItem.new({
+ text: o.display_name,
+ checked: o === props.value,
+ action: () => onChange(o),
+ })
+ ),
+ ])
+ .then((items) => Menu.new({ items }))
+ .then((m) => {
+ m.popup();
+ });
+ }}
+ >
+
+
+ {props.value?.display_name ?? NO_CAMERA}
+
+ requestPermission("camera")}
+ onClick={(e) => {
+ if (!props.options) return;
+ if (props.value !== null) {
+ e.stopPropagation();
+ props.onChange(null);
+ }
+ }}
+ />
+
+
+ );
+}
+
+const NO_MICROPHONE = "No Microphone";
+
+function MicrophoneSelect(props: {
+ disabled?: boolean;
+ options: string[];
+ value: string | null;
+ onChange: (micName: string | null) => void;
+}) {
+ const DB_SCALE = 40;
+
+ const permissions = createQuery(() => getPermissions);
+ const currentRecording = createCurrentRecordingQuery();
+
+ const [dbs, setDbs] = createSignal();
+ const [isInitialized, setIsInitialized] = createSignal(false);
+
+ const requestPermission = useRequestPermission();
+
+ const permissionGranted = () =>
+ permissions?.data?.microphone === "granted" ||
+ permissions?.data?.microphone === "notNeeded";
+
+ type Option = { name: string };
+
+ const handleMicrophoneChange = async (item: Option | null) => {
+ if (!props.options) return;
+ props.onChange(item ? item.name : null);
+ if (!item) setDbs();
+
+ trackEvent("microphone_selected", {
+ microphone_name: item?.name ?? null,
+ enabled: !!item,
+ });
+ };
+
+ const result = events.audioInputLevelChange.listen((dbs) => {
+ if (!props.value) setDbs();
+ else setDbs(dbs.payload);
+ });
+
+ onCleanup(() => result.then((unsub) => unsub()));
+
+ // visual audio level from 0 -> 1
+ const audioLevel = () =>
+ Math.pow(1 - Math.max((dbs() ?? 0) + DB_SCALE, 0) / DB_SCALE, 0.5);
+
+ // Initialize audio input if needed - only once when component mounts
+ onMount(() => {
+ if (!props.value || !permissionGranted() || isInitialized()) return;
+
+ setIsInitialized(true);
+ });
+
+ return (
+
+
{
+ Promise.all([
+ CheckMenuItem.new({
+ text: NO_MICROPHONE,
+ checked: props.value === null,
+ action: () => handleMicrophoneChange(null),
+ }),
+ PredefinedMenuItem.new({ item: "Separator" }),
+ ...(props.options ?? []).map((name) =>
+ CheckMenuItem.new({
+ text: name,
+ checked: name === props.value,
+ action: () => handleMicrophoneChange({ name: name }),
+ })
+ ),
+ ])
+ .then((items) => Menu.new({ items }))
+ .then((m) => {
+ m.popup();
+ });
+ }}
+ >
+
+ {(_) => (
+
+ )}
+
+
+
+ {props.value ?? NO_MICROPHONE}
+
+ requestPermission("microphone")}
+ onClick={(e) => {
+ if (props.value !== null) {
+ e.stopPropagation();
+ props.onChange(null);
+ }
+ }}
+ />
+
+
+ );
+}
+
+function SystemAudio() {
+ const { rawOptions, setOptions } = useRecordingOptions();
+ const currentRecording = createCurrentRecordingQuery();
+
+ return (
+ {
+ if (!rawOptions) return;
+ setOptions({ captureSystemAudio: !rawOptions.captureSystemAudio });
+ }}
+ disabled={!!currentRecording.data}
+ class="relative flex flex-row items-center h-[2rem] px-[0.375rem] gap-[0.375rem] border rounded-lg border-gray-3 w-full disabled:text-gray-11 transition-colors KSelect overflow-hidden z-10"
+ >
+
+
+
+
+ {rawOptions.captureSystemAudio
+ ? "Record System Audio"
+ : "No System Audio"}
+
+
+ {rawOptions.captureSystemAudio ? "On" : "Off"}
+
+
+ );
+}
+
+function TargetSelectInfoPill(props: {
+ value: T | null;
+ permissionGranted: boolean;
+ requestPermission: () => void;
+ onClick: (e: MouseEvent) => void;
+}) {
+ return (
+ {
+ if (!props.permissionGranted || props.value === null) return;
+
+ e.stopPropagation();
+ }}
+ onClick={(e) => {
+ if (!props.permissionGranted) {
+ props.requestPermission();
+ return;
+ }
+
+ props.onClick(e);
+ }}
+ >
+ {!props.permissionGranted
+ ? "Request Permission"
+ : props.value !== null
+ ? "On"
+ : "Off"}
+
+ );
+}
+
+function InfoPill(
+ props: ComponentProps<"button"> & { variant: "blue" | "red" }
+) {
+ return (
+
+ );
+}
+
+function ChangelogButton() {
+ const [changelogState, setChangelogState] = makePersisted(
+ createStore({
+ hasUpdate: false,
+ lastOpenedVersion: "",
+ changelogClicked: false,
+ }),
+ { name: "changelogState" }
+ );
+
+ const [currentVersion] = createResource(() => getVersion());
+
+ const [changelogStatus] = createResource(
+ () => currentVersion(),
+ async (version) => {
+ if (!version) {
+ return { hasUpdate: false };
+ }
+ const response = await apiClient.desktop.getChangelogStatus({
+ query: { version },
+ });
+ if (response.status === 200) return response.body;
+ return null;
+ }
+ );
+
+ const handleChangelogClick = () => {
+ commands.showWindow({ Settings: { page: "changelog" } });
+ getCurrentWindow().hide();
+ const version = currentVersion();
+ if (version) {
+ setChangelogState({
+ hasUpdate: false,
+ lastOpenedVersion: version,
+ changelogClicked: true,
+ });
+ }
+ };
+
+ createEffect(() => {
+ if (changelogStatus.state === "ready" && currentVersion()) {
+ const hasUpdate = changelogStatus()?.hasUpdate || false;
+ if (
+ hasUpdate === true &&
+ changelogState.lastOpenedVersion !== currentVersion()
+ ) {
+ setChangelogState({
+ hasUpdate: true,
+ lastOpenedVersion: currentVersion(),
+ changelogClicked: false,
+ });
+ }
+ }
+ });
+
+ return (
+
+
+
+ {changelogState.hasUpdate && (
+
+ )}
+
+
+ );
+}
+
+function TargetTypeButton(
+ props: {
+ selected: boolean;
+ Component: Component>;
+ name: string;
+ } & ComponentProps<"div">
+) {
+ return (
+
+ );
+}
diff --git a/apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx b/apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx
index 5db9e0d30..d9c2e3363 100644
--- a/apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx
+++ b/apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx
@@ -2,8 +2,9 @@ import { createResource, Show } from "solid-js";
import { createStore } from "solid-js/store";
import { generalSettingsStore } from "~/store";
-import { type GeneralSettingsStore } from "~/utils/tauri";
+import { commands, type GeneralSettingsStore } from "~/utils/tauri";
import { ToggleSetting } from "./Setting";
+import { getAllWindows } from "@tauri-apps/api/window";
export default function ExperimentalSettings() {
const [store] = createResource(() => generalSettingsStore.get());
@@ -19,11 +20,11 @@ function Inner(props: { initialStore: GeneralSettingsStore | null }) {
const [settings, setSettings] = createStore(
props.initialStore ?? {
uploadIndividualFiles: false,
- openEditorAfterRecording: false,
hideDockIcon: false,
autoCreateShareableLink: false,
enableNotifications: true,
enableNativeCameraPreview: false,
+ enableNewRecordingFlow: false,
autoZoomOnClicks: false,
}
);
@@ -51,27 +52,52 @@ function Inner(props: { initialStore: GeneralSettingsStore | null }) {
expected.
-
-
handleChange("customCursorCapture", value)}
- />
-
- handleChange("enableNativeCameraPreview", value)
- }
- />
- handleChange("autoZoomOnClicks", value)}
- />
+
+
Recording Features
+
+ handleChange("customCursorCapture", value)}
+ />
+
+ handleChange("enableNativeCameraPreview", value)
+ }
+ />
+ {
+ handleChange("autoZoomOnClicks", value);
+ // This is bad code, but I just want the UI to not jank and can't seem to find the issue.
+ setTimeout(
+ () => window.scrollTo({ top: 0, behavior: "instant" }),
+ 5
+ );
+ }}
+ />
+ {/*{import.meta.env.DEV && (
+ {
+ handleChange("enableNewRecordingFlow", value);
+ // This is bad code, but I just want the UI to not jank and can't seem to find the issue.
+ setTimeout(
+ () => window.scrollTo({ top: 0, behavior: "instant" }),
+ 5
+ );
+ }}
+ />
+ )}*/}
+
diff --git a/apps/desktop/src/routes/(window-chrome)/settings/general.tsx b/apps/desktop/src/routes/(window-chrome)/settings/general.tsx
index 7add49fed..9fe97ec8b 100644
--- a/apps/desktop/src/routes/(window-chrome)/settings/general.tsx
+++ b/apps/desktop/src/routes/(window-chrome)/settings/general.tsx
@@ -112,11 +112,11 @@ function Inner(props: { initialStore: GeneralSettingsStore | null }) {
const [settings, setSettings] = createStore(
props.initialStore ?? {
uploadIndividualFiles: false,
- openEditorAfterRecording: false,
hideDockIcon: false,
autoCreateShareableLink: false,
enableNotifications: true,
enableNativeCameraPreview: false,
+ enableNewRecordingFlow: false,
autoZoomOnClicks: false,
}
);
diff --git a/apps/desktop/src/routes/target-select-overlay.tsx b/apps/desktop/src/routes/target-select-overlay.tsx
new file mode 100644
index 000000000..b3e02a3ac
--- /dev/null
+++ b/apps/desktop/src/routes/target-select-overlay.tsx
@@ -0,0 +1,600 @@
+import { Button } from "@cap/ui-solid";
+import {
+ ComponentProps,
+ createEffect,
+ createRoot,
+ createSignal,
+ JSX,
+ Match,
+ onCleanup,
+ Show,
+ Switch,
+} from "solid-js";
+import { useSearchParams } from "@solidjs/router";
+import { createStore, reconcile } from "solid-js/store";
+import {
+ createEventListener,
+ createEventListenerMap,
+} from "@solid-primitives/event-listener";
+import { cx } from "cva";
+
+import { createOptionsQuery } from "~/utils/queries";
+import {
+ commands,
+ events,
+ ScreenCaptureTarget,
+ TargetUnderCursor,
+} from "~/utils/tauri";
+import { getCurrentWindow } from "@tauri-apps/api/window";
+
+export default function () {
+ const [params] = useSearchParams<{ displayId: string }>();
+ const { rawOptions, setOptions } = createOptionsQuery();
+
+ const [targetUnderCursor, setTargetUnderCursor] =
+ createStore({
+ display_id: null,
+ window: null,
+ screen: null,
+ });
+
+ const unsubTargetUnderCursor = events.targetUnderCursor.listen((event) => {
+ setTargetUnderCursor(reconcile(event.payload));
+ });
+ onCleanup(() => unsubTargetUnderCursor.then((unsub) => unsub()));
+
+ const [bounds, _setBounds] = createStore({
+ position: { x: 0, y: 0 },
+ size: { width: 400, height: 300 },
+ });
+
+ const setBounds = (newBounds: typeof bounds) => {
+ newBounds.position.x = Math.max(0, newBounds.position.x);
+ newBounds.position.y = Math.max(0, newBounds.position.y);
+ newBounds.size.width = Math.min(
+ window.innerWidth - newBounds.position.x,
+ newBounds.size.width
+ );
+ newBounds.size.height = Math.min(
+ window.innerHeight - newBounds.position.y,
+ newBounds.size.height
+ );
+
+ _setBounds(newBounds);
+ };
+
+ // We do this so any Cap window, (or external in the case of a bug) that are focused can trigger the close shortcut
+ const unsubOnEscapePress = events.onEscapePress.listen(() =>
+ setOptions("targetMode", null)
+ );
+ onCleanup(() => unsubOnEscapePress.then((f) => f()));
+
+ createEffect(() => {
+ if (rawOptions.captureTarget === undefined) getCurrentWindow().close();
+ });
+
+ // This prevents browser keyboard shortcuts from firing.
+ // Eg. on Windows Ctrl+P would open the print dialog without this
+ createEventListener(document, "keydown", (e) => e.preventDefault());
+
+ return (
+
+
+ {(_) => (
+
+ {(screenUnderCursor) => (
+
+
+ {screenUnderCursor.name}
+
+
+ {`${screenUnderCursor.physical_size.width}x${screenUnderCursor.physical_size.height} · ${screenUnderCursor.refresh_rate}FPS`}
+
+
+
+
+ )}
+
+ )}
+
+
+
+ {(windowUnderCursor) => (
+
+
+
+
+ {(icon) => (
+
+ )}
+
+
+ {windowUnderCursor.app_name}
+
+
+ {`${windowUnderCursor.bounds.size.width}x${windowUnderCursor.bounds.size.height}`}
+
+
+
+
+
{
+ setBounds(windowUnderCursor.bounds);
+ setOptions({
+ targetMode: "area",
+ });
+ }}
+ >
+ Adjust recording area
+
+
+
+ )}
+
+
+
+ {(_) => {
+ const [dragging, setDragging] = createSignal(false);
+
+ function createOnMouseDown(
+ onDrag: (
+ startBounds: typeof bounds,
+ delta: { x: number; y: number }
+ ) => void
+ ) {
+ return (downEvent: MouseEvent) => {
+ const startBounds = {
+ position: { ...bounds.position },
+ size: { ...bounds.size },
+ };
+
+ createRoot((dispose) => {
+ createEventListenerMap(window, {
+ mouseup: () => dispose(),
+ mousemove: (moveEvent) => {
+ onDrag(startBounds, {
+ x: Math.max(
+ -startBounds.position.x,
+ moveEvent.clientX - downEvent.clientX
+ ),
+ y: Math.max(
+ -startBounds.position.y,
+ moveEvent.clientY - downEvent.clientY
+ ),
+ });
+ },
+ });
+ });
+ };
+ }
+
+ function ResizeHandles() {
+ return (
+ <>
+ {/* Top Left Button */}
+ {
+ const width = startBounds.size.width - delta.x;
+ const limitedWidth = Math.max(width, 150);
+
+ const height = startBounds.size.height - delta.y;
+ const limitedHeight = Math.max(height, 150);
+
+ setBounds({
+ position: {
+ x:
+ startBounds.position.x +
+ delta.x -
+ (limitedWidth - width),
+ y:
+ startBounds.position.y +
+ delta.y -
+ (limitedHeight - height),
+ },
+ size: {
+ width: limitedWidth,
+ height: limitedHeight,
+ },
+ });
+ })}
+ />
+
+ {/* Top Right Button */}
+ {
+ const width = startBounds.size.width + delta.x;
+ const limitedWidth = Math.max(width, 150);
+
+ const height = startBounds.size.height - delta.y;
+ const limitedHeight = Math.max(height, 150);
+
+ setBounds({
+ position: {
+ x: startBounds.position.x,
+ y:
+ startBounds.position.y +
+ delta.y -
+ (limitedHeight - height),
+ },
+ size: {
+ width: limitedWidth,
+ height: limitedHeight,
+ },
+ });
+ })}
+ />
+
+ {/* Bottom Left Button */}
+ {
+ const width = startBounds.size.width - delta.x;
+ const limitedWidth = Math.max(width, 150);
+
+ const height = startBounds.size.height + delta.y;
+ const limitedHeight = Math.max(height, 150);
+
+ setBounds({
+ position: {
+ x:
+ startBounds.position.x +
+ delta.x -
+ (limitedWidth - width),
+ y: startBounds.position.y,
+ },
+ size: {
+ width: limitedWidth,
+ height: limitedHeight,
+ },
+ });
+ })}
+ />
+
+ {/* Bottom Right Button */}
+ {
+ const width = startBounds.size.width + delta.x;
+ const limitedWidth = Math.max(width, 150);
+
+ const height = startBounds.size.height + delta.y;
+ const limitedHeight = Math.max(height, 150);
+
+ setBounds({
+ position: {
+ x: startBounds.position.x,
+ y: startBounds.position.y,
+ },
+ size: {
+ width: limitedWidth,
+ height: limitedHeight,
+ },
+ });
+ })}
+ />
+
+ {/* Top Edge Button */}
+ {
+ const height = startBounds.size.height - delta.y;
+ const limitedHeight = Math.max(height, 150);
+
+ setBounds({
+ position: {
+ x: startBounds.position.x,
+ y:
+ startBounds.position.y +
+ delta.y -
+ (limitedHeight - height),
+ },
+ size: {
+ width: startBounds.size.width,
+ height: limitedHeight,
+ },
+ });
+ })}
+ />
+
+ {/* Right Edge Button */}
+ {
+ setBounds({
+ position: {
+ x: startBounds.position.x,
+ y: startBounds.position.y,
+ },
+ size: {
+ width: Math.max(150, startBounds.size.width + delta.x),
+ height: startBounds.size.height,
+ },
+ });
+ })}
+ />
+
+ {/* Bottom Edge Button */}
+ {
+ setBounds({
+ position: {
+ x: startBounds.position.x,
+ y: startBounds.position.y,
+ },
+ size: {
+ width: startBounds.size.width,
+ height: Math.max(
+ 150,
+ startBounds.size.height + delta.y
+ ),
+ },
+ });
+ })}
+ />
+
+ {/* Left Edge Button */}
+ {
+ const width = startBounds.size.width - delta.x;
+ const limitedWidth = Math.max(150, width);
+
+ setBounds({
+ position: {
+ x:
+ startBounds.position.x +
+ delta.x -
+ (limitedWidth - width),
+ y: startBounds.position.y,
+ },
+ size: {
+ width: limitedWidth,
+ height: startBounds.size.height,
+ },
+ });
+ })}
+ />
+ >
+ );
+ }
+
+ function Occluders() {
+ return (
+ <>
+ {/* Left */}
+
+ {/* Right */}
+
+ {/* Top center */}
+
+ {/* Bottom center */}
+
+ >
+ );
+ }
+
+ return (
+
+
+
+
{
+ setDragging(true);
+ const startPosition = { ...bounds.position };
+
+ createRoot((dispose) => {
+ createEventListenerMap(window, {
+ mousemove: (moveEvent) => {
+ const newPosition = {
+ x:
+ startPosition.x +
+ moveEvent.clientX -
+ downEvent.clientX,
+ y:
+ startPosition.y +
+ moveEvent.clientY -
+ downEvent.clientY,
+ };
+
+ if (newPosition.x < 0) newPosition.x = 0;
+ if (newPosition.y < 0) newPosition.y = 0;
+ if (
+ newPosition.x + bounds.size.width >
+ window.innerWidth
+ )
+ newPosition.x = window.innerWidth - bounds.size.width;
+ if (
+ newPosition.y + bounds.size.height >
+ window.innerHeight
+ )
+ newPosition.y =
+ window.innerHeight - bounds.size.height;
+
+ _setBounds("position", newPosition);
+ },
+ mouseup: () => {
+ setDragging(false);
+ dispose();
+ },
+ });
+ });
+ }}
+ >
+
+
+
+
+
+
+
+
Click and drag area to record
+
+ );
+ }}
+
+
+ );
+}
+
+function RecordingControls(props: { target: ScreenCaptureTarget }) {
+ const { rawOptions } = createOptionsQuery();
+
+ return (
+ {
+ commands.startRecording({
+ capture_target: props.target,
+ mode: rawOptions.mode,
+ capture_system_audio: rawOptions.captureSystemAudio,
+ });
+ }}
+ >
+ Start Recording
+
+ );
+}
+
+function ResizeHandle(
+ props: Omit, "style"> & { style?: JSX.CSSProperties }
+) {
+ return (
+
+ );
+}
+
+function getDisplayId(displayId: string | undefined) {
+ const id = Number(displayId);
+ if (Number.isNaN(id)) return 0;
+ return id;
+}
diff --git a/apps/desktop/src/utils/queries.ts b/apps/desktop/src/utils/queries.ts
index f0c9fb10f..34491c423 100644
--- a/apps/desktop/src/utils/queries.ts
+++ b/apps/desktop/src/utils/queries.ts
@@ -51,7 +51,7 @@ const getCurrentRecording = queryOptions({
queryFn: () => commands.getCurrentRecording().then((d) => d[0]),
});
-const listVideoDevices = queryOptions({
+export const listVideoDevices = queryOptions({
queryKey: ["videoDevices"] as const,
queryFn: () => commands.listCameras(),
refetchInterval: 1000,
@@ -87,23 +87,29 @@ export const getPermissions = queryOptions({
export function createOptionsQuery() {
const PERSIST_KEY = "recording-options-query";
- const [state, setState] = makePersisted(
- createStore<{
- captureTarget: ScreenCaptureTarget;
- micName: string | null;
- mode: RecordingMode;
- captureSystemAudio?: boolean;
- cameraID?: DeviceOrModelID | null;
- /** @deprecated */
- cameraLabel: string | null;
- }>({
- captureTarget: { variant: "screen", id: 0 },
- micName: null,
- cameraLabel: null,
- mode: "studio",
- }),
- { name: PERSIST_KEY }
- );
+ const [_state, _setState] = createStore<{
+ captureTarget: ScreenCaptureTarget;
+ micName: string | null;
+ mode: RecordingMode;
+ captureSystemAudio?: boolean;
+ targetMode?: "screen" | "window" | "area" | null;
+ cameraID?: DeviceOrModelID | null;
+ /** @deprecated */
+ cameraLabel: string | null;
+ }>({
+ captureTarget: { variant: "screen", id: 0 },
+ micName: null,
+ cameraLabel: null,
+ mode: "studio",
+ });
+
+ createEventListener(window, "storage", (e) => {
+ if (e.key === PERSIST_KEY) _setState(JSON.parse(e.newValue ?? "{}"));
+ });
+
+ const [state, setState] = makePersisted([_state, _setState], {
+ name: PERSIST_KEY,
+ });
createEventListener(window, "storage", (e) => {
if (e.key === PERSIST_KEY) setState(JSON.parse(e.newValue ?? "{}"));
@@ -122,7 +128,7 @@ export function createCurrentRecordingQuery() {
export function createLicenseQuery() {
const query = createQuery(() => ({
- queryKey: ["bruh"],
+ queryKey: ["licenseQuery"],
queryFn: async () => {
const settings = await generalSettingsStore.get();
const auth = await authStore.get();
diff --git a/apps/desktop/src/utils/tauri.ts b/apps/desktop/src/utils/tauri.ts
index f8d5ca6b9..9e54d4dcd 100644
--- a/apps/desktop/src/utils/tauri.ts
+++ b/apps/desktop/src/utils/tauri.ts
@@ -250,6 +250,12 @@ async deleteWhisperModel(modelPath: string) : Promise {
*/
async exportCaptionsSrt(videoId: string) : Promise {
return await TAURI_INVOKE("export_captions_srt", { videoId });
+},
+async openTargetSelectOverlays() : Promise {
+ return await TAURI_INVOKE("open_target_select_overlays");
+},
+async closeTargetSelectOverlays() : Promise {
+ return await TAURI_INVOKE("close_target_select_overlays");
}
}
@@ -265,6 +271,7 @@ editorStateChanged: EditorStateChanged,
newNotification: NewNotification,
newScreenshotAdded: NewScreenshotAdded,
newStudioRecordingAdded: NewStudioRecordingAdded,
+onEscapePress: OnEscapePress,
recordingDeleted: RecordingDeleted,
recordingEvent: RecordingEvent,
recordingOptionsChanged: RecordingOptionsChanged,
@@ -274,6 +281,7 @@ renderFrameEvent: RenderFrameEvent,
requestNewScreenshot: RequestNewScreenshot,
requestOpenSettings: RequestOpenSettings,
requestStartRecording: RequestStartRecording,
+targetUnderCursor: TargetUnderCursor,
uploadProgress: UploadProgress
}>({
audioInputLevelChange: "audio-input-level-change",
@@ -284,6 +292,7 @@ editorStateChanged: "editor-state-changed",
newNotification: "new-notification",
newScreenshotAdded: "new-screenshot-added",
newStudioRecordingAdded: "new-studio-recording-added",
+onEscapePress: "on-escape-press",
recordingDeleted: "recording-deleted",
recordingEvent: "recording-event",
recordingOptionsChanged: "recording-options-changed",
@@ -293,6 +302,7 @@ renderFrameEvent: "render-frame-event",
requestNewScreenshot: "request-new-screenshot",
requestOpenSettings: "request-open-settings",
requestStartRecording: "request-start-recording",
+targetUnderCursor: "target-under-cursor",
uploadProgress: "upload-progress"
})
@@ -344,6 +354,7 @@ export type CursorMeta = { imagePath: string; hotspot: XY; shape?: strin
export type CursorType = "pointer" | "circle"
export type Cursors = { [key in string]: string } | { [key in string]: CursorMeta }
export type DeviceOrModelID = { DeviceID: string } | { ModelID: ModelIDType }
+export type DisplayId = string
export type DownloadProgress = { progress: number; message: string }
export type EditorStateChanged = { playhead_position: number }
export type ExportCompression = "Minimal" | "Social" | "Web" | "Potato"
@@ -352,15 +363,7 @@ export type ExportSettings = ({ format: "Mp4" } & Mp4ExportSettings) | ({ format
export type FileType = "recording" | "screenshot"
export type Flags = { captions: boolean }
export type FramesRendered = { renderedCount: number; totalFrames: number; type: "FramesRendered" }
-export type GeneralSettingsStore = { instanceId?: string; uploadIndividualFiles?: boolean; hideDockIcon?: boolean; hapticsEnabled?: boolean; autoCreateShareableLink?: boolean; enableNotifications?: boolean; disableAutoOpenLinks?: boolean; hasCompletedStartup?: boolean; theme?: AppTheme; commercialLicense?: CommercialLicense | null; lastVersion?: string | null; windowTransparency?: boolean; postStudioRecordingBehaviour?: PostStudioRecordingBehaviour; mainWindowRecordingStartBehaviour?: MainWindowRecordingStartBehaviour; customCursorCapture?: boolean; serverUrl?: string; recordingCountdown?: number | null;
-/**
- * @deprecated
- */
-openEditorAfterRecording?: boolean;
-/**
- * @deprecated can be removed when native camera preview is ready
- */
-enableNativeCameraPreview?: boolean; autoZoomOnClicks?: boolean; postDeletionBehaviour?: PostDeletionBehaviour }
+export type GeneralSettingsStore = { instanceId?: string; uploadIndividualFiles?: boolean; hideDockIcon?: boolean; hapticsEnabled?: boolean; autoCreateShareableLink?: boolean; enableNotifications?: boolean; disableAutoOpenLinks?: boolean; hasCompletedStartup?: boolean; theme?: AppTheme; commercialLicense?: CommercialLicense | null; lastVersion?: string | null; windowTransparency?: boolean; postStudioRecordingBehaviour?: PostStudioRecordingBehaviour; mainWindowRecordingStartBehaviour?: MainWindowRecordingStartBehaviour; customCursorCapture?: boolean; serverUrl?: string; recordingCountdown?: number | null; enableNativeCameraPreview: boolean; autoZoomOnClicks?: boolean; enableNewRecordingFlow: boolean; postDeletionBehaviour?: PostDeletionBehaviour }
export type GifExportSettings = { fps: number; resolution_base: XY }
export type HapticPattern = "Alignment" | "LevelChange" | "Generic"
export type HapticPerformanceTime = "Default" | "Now" | "DrawCompleted"
@@ -370,6 +373,9 @@ export type HotkeysConfiguration = { show: boolean }
export type HotkeysStore = { hotkeys: { [key in HotkeyAction]: Hotkey } }
export type InstantRecordingMeta = { fps: number; sample_rate: number | null }
export type JsonValue = [T]
+export type LogicalBounds = { position: LogicalPosition; size: LogicalSize }
+export type LogicalPosition = { x: number; y: number }
+export type LogicalSize = { width: number; height: number }
export type MainWindowRecordingStartBehaviour = "close" | "minimise"
export type ModelIDType = string
export type Mp4ExportSettings = { fps: number; resolution_base: XY; compression: ExportCompression }
@@ -381,6 +387,8 @@ export type NewStudioRecordingAdded = { path: string }
export type OSPermission = "screenRecording" | "camera" | "microphone" | "accessibility"
export type OSPermissionStatus = "notNeeded" | "empty" | "granted" | "denied"
export type OSPermissionsCheck = { screenRecording: OSPermissionStatus; microphone: OSPermissionStatus; camera: OSPermissionStatus; accessibility: OSPermissionStatus }
+export type OnEscapePress = null
+export type PhysicalSize = { width: number; height: number }
export type Plan = { upgraded: boolean; manual: boolean; last_checked: number }
export type Platform = "MacOS" | "Windows"
export type PostDeletionBehaviour = "doNothing" | "reopenRecordingWindow"
@@ -404,15 +412,17 @@ export type RequestOpenSettings = { page: string }
export type RequestStartRecording = null
export type S3UploadMeta = { id: string }
export type ScreenCaptureTarget = { variant: "window"; id: number } | { variant: "screen"; id: number } | { variant: "area"; screen: number; bounds: Bounds }
+export type ScreenUnderCursor = { name: string; physical_size: PhysicalSize; refresh_rate: string }
export type SegmentRecordings = { display: Video; camera: Video | null; mic: Audio | null; system_audio: Audio | null }
export type SerializedEditorInstance = { framesSocketUrl: string; recordingDuration: number; savedProjectConfig: ProjectConfiguration; recordings: ProjectRecordingsMeta; path: string }
export type ShadowConfiguration = { size: number; opacity: number; blur: number }
export type SharingMeta = { id: string; link: string }
-export type ShowCapWindow = "Setup" | "Main" | { Settings: { page: string | null } } | { Editor: { project_path: string } } | "RecordingsOverlay" | { WindowCaptureOccluder: { screen_id: number } } | { CaptureArea: { screen_id: number } } | "Camera" | { InProgressRecording: { countdown: number | null } } | "Upgrade" | "ModeSelect"
+export type ShowCapWindow = "Setup" | "Main" | { Settings: { page: string | null } } | { Editor: { project_path: string } } | "RecordingsOverlay" | { WindowCaptureOccluder: { screen_id: number } } | { TargetSelectOverlay: { display_id: DisplayId } } | { CaptureArea: { screen_id: number } } | "Camera" | { InProgressRecording: { countdown: number | null } } | "Upgrade" | "ModeSelect"
export type SingleSegment = { display: VideoMeta; camera?: VideoMeta | null; audio?: AudioMeta | null; cursor?: string | null }
export type StartRecordingInputs = { capture_target: ScreenCaptureTarget; capture_system_audio?: boolean; mode: RecordingMode }
export type StereoMode = "stereo" | "monoL" | "monoR"
export type StudioRecordingMeta = { segment: SingleSegment } | { inner: MultipleSegments }
+export type TargetUnderCursor = { display_id: DisplayId | null; window: WindowUnderCursor | null; screen: ScreenUnderCursor | null }
export type TimelineConfiguration = { segments: TimelineSegment[]; zoomSegments: ZoomSegment[] }
export type TimelineSegment = { recordingSegment?: number; timescale: number; start: number; end: number }
export type UploadMode = { Initial: { pre_created_video: VideoUploadInfo | null } } | "Reupload"
@@ -426,6 +436,8 @@ export type VideoMeta = { path: string; fps?: number;
start_time?: number | null }
export type VideoRecordingMetadata = { duration: number; size: number }
export type VideoUploadInfo = { id: string; link: string; config: S3UploadMeta }
+export type WindowId = string
+export type WindowUnderCursor = { id: WindowId; app_name: string; bounds: LogicalBounds; icon: string | null }
export type XY = { x: T; y: T }
export type ZoomMode = "auto" | { manual: { x: number; y: number } }
export type ZoomSegment = { start: number; end: number; amount: number; mode: ZoomMode }
diff --git a/apps/web/app/queries.ts b/apps/web/app/queries.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/crates/displays/Cargo.toml b/crates/displays/Cargo.toml
index 4b42eed0c..8f4ffd5a8 100644
--- a/crates/displays/Cargo.toml
+++ b/crates/displays/Cargo.toml
@@ -7,15 +7,29 @@ edition = "2024"
workspace = true
[dependencies]
+serde = { version = "1.0.219", features = ["derive"] }
+specta.workspace = true
+image = "0.24"
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.24.0"
+core-foundation = "0.10.0"
+cocoa = "0.26.0"
+objc = "0.2.7"
[target.'cfg(target_os= "windows")'.dependencies]
windows = { workspace = true, features = [
- "Win32_Foundation",
- "Win32_System",
- "Win32_UI_WindowsAndMessaging",
- "Win32_Graphics_Gdi",
+ "Win32_Foundation",
+ "Win32_System",
+ "Win32_System_Threading",
+ "Win32_System_Registry",
+ "Win32_System_Com",
+ "Win32_System_Wmi",
+ "Win32_UI_WindowsAndMessaging",
+ "Win32_UI_Shell",
+ "Win32_UI_HiDpi",
+ "Win32_Graphics_Gdi",
+ "Win32_Storage_FileSystem",
+ "Win32_Devices_Display",
] }
windows-sys = { workspace = true }
diff --git a/crates/displays/src/bounds.rs b/crates/displays/src/bounds.rs
new file mode 100644
index 000000000..724ba59e1
--- /dev/null
+++ b/crates/displays/src/bounds.rs
@@ -0,0 +1,101 @@
+use serde::Serialize;
+use specta::Type;
+
+#[derive(Clone, Copy, Debug, Type, Serialize)]
+pub struct LogicalBounds {
+ pub(crate) position: LogicalPosition,
+ pub(crate) size: LogicalSize,
+}
+
+impl LogicalBounds {
+ pub fn new(position: LogicalPosition, size: LogicalSize) -> Self {
+ Self { position, size }
+ }
+
+ pub fn position(&self) -> LogicalPosition {
+ self.position
+ }
+
+ pub fn size(&self) -> LogicalSize {
+ self.size
+ }
+
+ pub fn contains_point(&self, point: LogicalPosition) -> bool {
+ point.x() >= self.position.x()
+ && point.x() < self.position.x() + self.size.width()
+ && point.y() >= self.position.y()
+ && point.y() < self.position.y() + self.size.height()
+ }
+}
+
+#[derive(Clone, Copy, Debug, Type, Serialize)]
+pub struct LogicalSize {
+ pub(crate) width: f64,
+ pub(crate) height: f64,
+}
+
+impl LogicalSize {
+ pub fn width(&self) -> f64 {
+ self.width
+ }
+
+ pub fn height(&self) -> f64 {
+ self.height
+ }
+}
+
+#[derive(Clone, Copy, Debug, Type, Serialize)]
+pub struct PhysicalSize {
+ pub(crate) width: f64,
+ pub(crate) height: f64,
+}
+
+impl PhysicalSize {
+ pub fn new(width: f64, height: f64) -> Self {
+ Self { width, height }
+ }
+
+ pub fn width(&self) -> f64 {
+ self.width
+ }
+
+ pub fn height(&self) -> f64 {
+ self.height
+ }
+}
+
+#[derive(Clone, Copy, Debug, Type, Serialize)]
+pub struct LogicalPosition {
+ pub(crate) x: f64,
+ pub(crate) y: f64,
+}
+
+impl LogicalPosition {
+ pub fn x(&self) -> f64 {
+ self.x
+ }
+
+ pub fn y(&self) -> f64 {
+ self.y
+ }
+}
+
+#[derive(Clone, Copy, Debug, Type, Serialize)]
+pub struct PhysicalPosition {
+ pub(crate) x: f64,
+ pub(crate) y: f64,
+}
+
+impl PhysicalPosition {
+ pub fn new(x: f64, y: f64) -> Self {
+ Self { x, y }
+ }
+
+ pub fn x(&self) -> f64 {
+ self.x
+ }
+
+ pub fn y(&self) -> f64 {
+ self.y
+ }
+}
diff --git a/crates/displays/src/lib.rs b/crates/displays/src/lib.rs
index 69572f3e5..9ad6b69d2 100644
--- a/crates/displays/src/lib.rs
+++ b/crates/displays/src/lib.rs
@@ -1,6 +1,12 @@
-mod platform;
+pub mod bounds;
+pub mod platform;
-pub use platform::DisplayImpl;
+use std::str::FromStr;
+
+use bounds::{LogicalBounds, PhysicalSize};
+pub use platform::{DisplayIdImpl, DisplayImpl, WindowIdImpl, WindowImpl};
+use serde::{Deserialize, Serialize};
+use specta::Type;
#[derive(Clone, Copy)]
pub struct Display(DisplayImpl);
@@ -13,4 +19,152 @@ impl Display {
pub fn raw_handle(&self) -> &DisplayImpl {
&self.0
}
+
+ pub fn id(&self) -> DisplayId {
+ DisplayId(self.0.raw_id())
+ }
+
+ pub fn from_id(id: DisplayId) -> Option {
+ Self::list().into_iter().find(|d| d.id() == id)
+ }
+
+ pub fn get_containing_cursor() -> Option {
+ DisplayImpl::get_containing_cursor().map(Self)
+ }
+
+ pub fn name(&self) -> String {
+ self.0.name()
+ }
+
+ pub fn physical_size(&self) -> PhysicalSize {
+ self.0.physical_size()
+ }
+
+ pub fn refresh_rate(&self) -> f64 {
+ self.0.refresh_rate()
+ }
+}
+
+#[derive(Serialize, Deserialize, Type, Clone, PartialEq)]
+pub struct DisplayId(
+ #[serde(with = "serde_display_id")]
+ #[specta(type = String)]
+ DisplayIdImpl,
+);
+
+impl std::fmt::Display for DisplayId {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl FromStr for DisplayId {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result {
+ s.parse::().map(Self)
+ }
+}
+
+mod serde_display_id {
+ use serde::{Deserialize, Deserializer, Serializer};
+
+ use crate::platform::DisplayIdImpl;
+
+ pub fn serialize(this: &DisplayIdImpl, serializer: S) -> Result
+ where
+ S: Serializer,
+ {
+ serializer.serialize_str(&this.to_string())
+ }
+
+ pub fn deserialize<'de, D>(deserializer: D) -> Result
+ where
+ D: Deserializer<'de>,
+ {
+ let s = String::deserialize(deserializer)?;
+ s.parse::().map_err(serde::de::Error::custom)
+ }
+}
+
+#[derive(Clone, Copy)]
+pub struct Window(WindowImpl);
+
+impl Window {
+ pub fn list() -> Vec {
+ WindowImpl::list().into_iter().map(Self).collect()
+ }
+
+ pub fn list_containing_cursor() -> Vec {
+ WindowImpl::list_containing_cursor()
+ .into_iter()
+ .map(Self)
+ .collect()
+ }
+
+ pub fn get_topmost_at_cursor() -> Option {
+ WindowImpl::get_topmost_at_cursor().map(Self)
+ }
+
+ pub fn id(&self) -> WindowId {
+ WindowId(self.0.id())
+ }
+
+ pub fn bounds(&self) -> Option {
+ self.0.bounds()
+ }
+
+ pub fn owner_name(&self) -> Option {
+ self.0.owner_name()
+ }
+
+ pub fn app_icon(&self) -> Option> {
+ self.0.app_icon()
+ }
+
+ pub fn raw_handle(&self) -> &WindowImpl {
+ &self.0
+ }
+}
+
+#[derive(Serialize, Deserialize, Type, Clone, PartialEq)]
+pub struct WindowId(
+ #[serde(with = "serde_window_id")]
+ #[specta(type = String)]
+ WindowIdImpl,
+);
+
+impl std::fmt::Display for WindowId {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl FromStr for WindowId {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result {
+ s.parse::().map(Self)
+ }
+}
+
+mod serde_window_id {
+ use serde::{Deserialize, Deserializer, Serializer};
+
+ use crate::WindowIdImpl;
+
+ pub fn serialize(this: &WindowIdImpl, serializer: S) -> Result
+ where
+ S: Serializer,
+ {
+ serializer.serialize_str(&this.to_string())
+ }
+
+ pub fn deserialize<'de, D>(deserializer: D) -> Result
+ where
+ D: Deserializer<'de>,
+ {
+ let s = String::deserialize(deserializer)?;
+ s.parse::().map_err(serde::de::Error::custom)
+ }
}
diff --git a/crates/displays/src/main.rs b/crates/displays/src/main.rs
index e7a11a969..1d04899e2 100644
--- a/crates/displays/src/main.rs
+++ b/crates/displays/src/main.rs
@@ -1,3 +1,132 @@
+use std::time::Duration;
+
fn main() {
- println!("Hello, world!");
+ // Test display functionality
+ println!("=== Display Information ===");
+ for (index, display) in cap_displays::Display::list().iter().enumerate() {
+ println!("Display {}: {}", index + 1, display.name());
+ println!(" ID: {}", display.id());
+
+ let logical_size = display.raw_handle().logical_size();
+ let physical_size = display.physical_size();
+ let refresh_rate = display.refresh_rate();
+
+ println!(
+ " Logical Resolution: {}x{}",
+ logical_size.width(),
+ logical_size.height()
+ );
+ println!(
+ " Physical Resolution: {}x{}",
+ physical_size.width(),
+ physical_size.height()
+ );
+
+ if refresh_rate > 0.0 {
+ println!(" Refresh Rate: {} Hz", refresh_rate);
+ } else {
+ println!(" Refresh Rate: Unknown");
+ }
+
+ // Check if this is the main display
+ let main_display_id = cap_displays::Display::list().get(0).map(|d| d.id());
+
+ if let Some(main_id) = main_display_id {
+ if display.id() == main_id {
+ println!(" Type: Primary Display");
+ } else {
+ println!(" Type: Secondary Display");
+ }
+ } else {
+ println!(" Type: Unknown");
+ }
+
+ println!();
+ }
+
+ if let Some(cursor_display) = cap_displays::Display::get_containing_cursor() {
+ println!("🖱️ Cursor is currently on: {}", cursor_display.name());
+ println!();
+ }
+
+ // Test window functionality
+ println!("=== Windows Under Cursor ===");
+ let windows = cap_displays::Window::list_containing_cursor();
+
+ if windows.is_empty() {
+ println!("No windows found under cursor");
+ } else {
+ println!("Found {} window(s) under cursor:", windows.len());
+ for (index, window) in windows.iter().take(5).enumerate() {
+ // Limit to first 5 windows
+ println!("\nWindow {}: {}", index + 1, window.id());
+
+ if let Some(bounds) = window.bounds() {
+ println!(
+ " Bounds: {}x{} at ({}, {})",
+ bounds.size().width(),
+ bounds.size().height(),
+ bounds.position().x(),
+ bounds.position().y()
+ );
+ }
+
+ if let Some(owner) = window.owner_name() {
+ println!(" Application: {}", owner);
+ } else {
+ println!(" Application: Unknown");
+ }
+
+ // Test icon functionality
+ match window.app_icon() {
+ Some(icon_data) => {
+ println!(" Icon (Standard): {} bytes", icon_data.len());
+ println!(" Format: PNG (Raw bytes)");
+ println!(" Size: {} bytes", icon_data.len());
+ }
+ None => println!(" Icon (Standard): Not available"),
+ }
+ }
+ }
+
+ println!("\n=== Topmost Window Icon Test ===");
+ if let Some(topmost) = cap_displays::Window::get_topmost_at_cursor() {
+ if let Some(owner) = topmost.owner_name() {
+ println!("Testing icon extraction for: {}", owner);
+
+ match topmost.app_icon() {
+ Some(icon_data) => {
+ println!(" ✅ Icon found: {} bytes", icon_data.len());
+ println!(" Format: PNG (Raw bytes)");
+ println!(" Size: {} bytes", icon_data.len());
+ }
+ None => println!(" ❌ No icon found"),
+ }
+ }
+ }
+
+ println!("\n=== Live Monitoring (Press Ctrl+C to exit) ===");
+ println!("Monitoring window levels under cursor...\n");
+
+ loop {
+ let mut relevant_windows = cap_displays::WindowImpl::list_containing_cursor()
+ .into_iter()
+ .filter_map(|window| {
+ let level = window.level()?;
+ level.lt(&5).then_some((window, level))
+ })
+ .collect::>();
+
+ relevant_windows.sort_by(|a, b| b.1.cmp(&a.1));
+
+ // Print current topmost window info
+ if let Some((topmost_window, level)) = relevant_windows.first() {
+ if let Some(owner) = topmost_window.owner_name() {
+ print!("\rTopmost: {} (level: {}) ", owner, level);
+ std::io::Write::flush(&mut std::io::stdout()).unwrap();
+ }
+ }
+
+ std::thread::sleep(Duration::from_millis(100));
+ }
}
diff --git a/crates/displays/src/platform/macos.rs b/crates/displays/src/platform/macos.rs
index 4d6d62b65..324036ef1 100644
--- a/crates/displays/src/platform/macos.rs
+++ b/crates/displays/src/platform/macos.rs
@@ -1,4 +1,15 @@
-use core_graphics::display::CGDisplay;
+use std::{ffi::c_void, str::FromStr};
+
+use core_foundation::{base::FromVoid, number::CFNumber, string::CFString};
+use core_graphics::{
+ display::{
+ CFDictionary, CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
+ CGRect, kCGWindowListOptionIncludingWindow,
+ },
+ window::{CGWindowID, kCGWindowBounds, kCGWindowLayer, kCGWindowNumber, kCGWindowOwnerName},
+};
+
+use crate::bounds::{LogicalBounds, LogicalPosition, LogicalSize, PhysicalSize};
#[derive(Clone, Copy)]
pub struct DisplayImpl(CGDisplay);
@@ -19,4 +30,370 @@ impl DisplayImpl {
pub fn inner(&self) -> CGDisplay {
self.0
}
+
+ pub fn raw_id(&self) -> DisplayIdImpl {
+ DisplayIdImpl(self.0.id)
+ }
+
+ pub fn from_id(id: String) -> Option {
+ let parsed_id = id.parse::().ok()?;
+ Self::list().into_iter().find(|d| d.0.id == parsed_id)
+ }
+
+ pub fn logical_size(&self) -> LogicalSize {
+ let rect = unsafe { CGDisplayBounds(self.0.id) };
+
+ LogicalSize {
+ width: rect.size.width,
+ height: rect.size.height,
+ }
+ }
+
+ pub fn logical_position(&self) -> LogicalPosition {
+ let rect = unsafe { CGDisplayBounds(self.0.id) };
+
+ LogicalPosition {
+ x: rect.origin.x,
+ y: rect.origin.y,
+ }
+ }
+
+ pub fn get_containing_cursor() -> Option {
+ let cursor = get_cursor_position()?;
+
+ Self::list().into_iter().find(|display| {
+ let bounds = LogicalBounds {
+ position: display.logical_position(),
+ size: display.logical_size(),
+ };
+ bounds.contains_point(cursor)
+ })
+ }
+
+ pub fn physical_size(&self) -> PhysicalSize {
+ let mode = unsafe { CGDisplayCopyDisplayMode(self.0.id) };
+ if mode.is_null() {
+ return PhysicalSize {
+ width: 0.0,
+ height: 0.0,
+ };
+ }
+
+ let width = unsafe { core_graphics::display::CGDisplayModeGetWidth(mode) };
+ let height = unsafe { core_graphics::display::CGDisplayModeGetHeight(mode) };
+
+ unsafe { core_graphics::display::CGDisplayModeRelease(mode) };
+
+ PhysicalSize {
+ width: width as f64,
+ height: height as f64,
+ }
+ }
+
+ pub fn refresh_rate(&self) -> f64 {
+ let mode = unsafe { CGDisplayCopyDisplayMode(self.0.id) };
+ if mode.is_null() {
+ return 0.0;
+ }
+
+ let refresh_rate = unsafe { core_graphics::display::CGDisplayModeGetRefreshRate(mode) };
+
+ unsafe { core_graphics::display::CGDisplayModeRelease(mode) };
+
+ refresh_rate
+ }
+
+ pub fn name(&self) -> String {
+ use cocoa::appkit::NSScreen;
+ use cocoa::base::{id, nil};
+ use cocoa::foundation::{NSArray, NSDictionary, NSString};
+ use objc::{msg_send, *};
+ use std::ffi::CStr;
+
+ unsafe {
+ let screens = NSScreen::screens(nil);
+ let screen_count = NSArray::count(screens);
+
+ for i in 0..screen_count {
+ let screen: *mut objc::runtime::Object = screens.objectAtIndex(i);
+
+ let device_description = NSScreen::deviceDescription(screen);
+ let num = NSDictionary::valueForKey_(
+ device_description,
+ NSString::alloc(nil).init_str("NSScreenNumber"),
+ ) as id;
+
+ let num_value: u32 = msg_send![num, unsignedIntValue];
+
+ if num_value == self.0.id {
+ let name: id = msg_send![screen, localizedName];
+ if !name.is_null() {
+ let name = CStr::from_ptr(NSString::UTF8String(name))
+ .to_string_lossy()
+ .to_string();
+ return name;
+ }
+ }
+ }
+
+ // Fallback to generic name with display ID
+ format!("Display {}", self.0.id)
+ }
+ }
+}
+
+fn get_cursor_position() -> Option {
+ let event_source = core_graphics::event_source::CGEventSource::new(
+ core_graphics::event_source::CGEventSourceStateID::Private,
+ )
+ .ok()?;
+
+ let event = core_graphics::event::CGEvent::new(event_source).ok()?;
+ let location = event.location();
+
+ Some(LogicalPosition {
+ x: location.x,
+ y: location.y,
+ })
+}
+
+#[derive(Clone, Copy)]
+pub struct WindowImpl(CGWindowID);
+
+impl WindowImpl {
+ pub fn list() -> Vec {
+ use core_graphics::window::{
+ kCGNullWindowID, kCGWindowListExcludeDesktopElements, kCGWindowListOptionOnScreenOnly,
+ };
+
+ let windows = core_graphics::window::copy_window_info(
+ kCGWindowListExcludeDesktopElements | kCGWindowListOptionOnScreenOnly,
+ kCGNullWindowID,
+ );
+
+ let Some(windows) = windows else {
+ return vec![];
+ };
+
+ let mut ret = vec![];
+
+ for window in windows.iter() {
+ let window_dict =
+ unsafe { CFDictionary::::from_void(*window) };
+
+ let Some(number) = (unsafe {
+ window_dict
+ .find(kCGWindowNumber)
+ .and_then(|v| CFNumber::from_void(*v).to_i64().map(|v| v as u32))
+ }) else {
+ continue;
+ };
+
+ ret.push(WindowImpl(number));
+ }
+
+ ret
+ }
+
+ pub fn list_containing_cursor() -> Vec {
+ let Some(cursor) = get_cursor_position() else {
+ return vec![];
+ };
+
+ Self::list()
+ .into_iter()
+ .filter_map(|window| {
+ let bounds = window.bounds()?;
+ bounds.contains_point(cursor).then_some(window)
+ })
+ .collect()
+ }
+
+ pub fn get_topmost_at_cursor() -> Option {
+ let mut windows_with_level = Self::list_containing_cursor()
+ .into_iter()
+ .filter_map(|window| {
+ let level = window.level()?;
+ if level > 5 {
+ return None;
+ }
+ Some((window, level))
+ })
+ .collect::>();
+
+ windows_with_level.sort_by(|a, b| b.1.cmp(&a.1));
+
+ windows_with_level.first().map(|(window, _)| *window)
+ }
+
+ pub fn id(&self) -> WindowIdImpl {
+ WindowIdImpl(self.0)
+ }
+
+ pub fn level(&self) -> Option {
+ let windows =
+ core_graphics::window::copy_window_info(kCGWindowListOptionIncludingWindow, self.0)?;
+
+ let window_dict =
+ unsafe { CFDictionary::::from_void(*windows.get(0)?) };
+
+ unsafe {
+ window_dict
+ .find(kCGWindowLayer)
+ .and_then(|v| CFNumber::from_void(*v).to_i32())
+ }
+ }
+
+ pub fn owner_name(&self) -> Option {
+ let windows =
+ core_graphics::window::copy_window_info(kCGWindowListOptionIncludingWindow, self.0)?;
+
+ let window_dict =
+ unsafe { CFDictionary::::from_void(*windows.get(0)?) };
+
+ unsafe {
+ window_dict
+ .find(kCGWindowOwnerName)
+ .map(|v| CFString::from_void(*v).to_string())
+ }
+ }
+
+ pub fn bounds(&self) -> Option {
+ let windows =
+ core_graphics::window::copy_window_info(kCGWindowListOptionIncludingWindow, self.0)?;
+
+ let window_dict =
+ unsafe { CFDictionary::::from_void(*windows.get(0)?) };
+
+ unsafe {
+ window_dict
+ .find(kCGWindowBounds)
+ .and_then(|v| CGRect::from_dict_representation(&*CFDictionary::from_void(*v)))
+ }
+ .map(|rect| LogicalBounds {
+ position: LogicalPosition {
+ x: rect.origin.x,
+ y: rect.origin.y,
+ },
+ size: LogicalSize {
+ width: rect.size.width,
+ height: rect.size.height,
+ },
+ })
+ }
+
+ pub fn app_icon(&self) -> Option> {
+ use cocoa::base::{id, nil};
+ use cocoa::foundation::{NSArray, NSAutoreleasePool, NSString};
+ use objc::{class, msg_send, sel, sel_impl};
+
+ let owner_name = self.owner_name()?;
+
+ unsafe {
+ let pool = NSAutoreleasePool::new(nil);
+
+ let workspace_class = class!(NSWorkspace);
+ let workspace: id = msg_send![workspace_class, sharedWorkspace];
+ let running_apps: id = msg_send![workspace, runningApplications];
+ let app_count = NSArray::count(running_apps);
+
+ let result = (0..app_count).find_map(|i| {
+ let app: id = running_apps.objectAtIndex(i);
+ let localized_name: id = msg_send![app, localizedName];
+
+ if localized_name.is_null() {
+ return None;
+ }
+
+ let name_str = NSString::UTF8String(localized_name);
+ if name_str.is_null() {
+ return None;
+ }
+
+ let name = std::ffi::CStr::from_ptr(name_str)
+ .to_string_lossy()
+ .to_string();
+
+ if name != owner_name {
+ return None;
+ }
+
+ let icon: id = msg_send![app, icon];
+ if icon.is_null() {
+ return None;
+ }
+
+ let tiff_data: id = msg_send![icon, TIFFRepresentation];
+ if tiff_data.is_null() {
+ return None;
+ }
+
+ let bitmap_rep_class = class!(NSBitmapImageRep);
+ let bitmap_rep: id = msg_send![bitmap_rep_class, imageRepWithData: tiff_data];
+ if bitmap_rep.is_null() {
+ return None;
+ }
+
+ let png_data: id = msg_send![
+ bitmap_rep,
+ representationUsingType: 4u64 // NSBitmapImageFileTypePNG
+ properties: nil
+ ];
+ if png_data.is_null() {
+ return None;
+ }
+
+ let length: usize = msg_send![png_data, length];
+ let bytes_ptr: *const u8 = msg_send![png_data, bytes];
+
+ if bytes_ptr.is_null() || length == 0 {
+ return None;
+ }
+
+ let bytes = std::slice::from_raw_parts(bytes_ptr, length);
+ Some(bytes.to_vec())
+ });
+
+ pool.drain();
+ result
+ }
+ }
+}
+
+#[derive(Clone, PartialEq, Debug)]
+pub struct DisplayIdImpl(CGDirectDisplayID);
+
+impl std::fmt::Display for DisplayIdImpl {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl FromStr for DisplayIdImpl {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result {
+ s.parse()
+ .map(Self)
+ .map_err(|_| "Invalid display ID".to_string())
+ }
+}
+
+#[derive(Clone, PartialEq, Debug)]
+pub struct WindowIdImpl(CGWindowID);
+
+impl std::fmt::Display for WindowIdImpl {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl FromStr for WindowIdImpl {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result {
+ s.parse()
+ .map(Self)
+ .map_err(|_| "Invalid window ID".to_string())
+ }
}
diff --git a/crates/displays/src/platform/mod.rs b/crates/displays/src/platform/mod.rs
index c257c96e5..bfe03249b 100644
--- a/crates/displays/src/platform/mod.rs
+++ b/crates/displays/src/platform/mod.rs
@@ -1,9 +1,9 @@
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "macos")]
-pub use macos::DisplayImpl;
+pub use macos::*;
#[cfg(windows)]
mod win;
#[cfg(windows)]
-pub use win::DisplayImpl;
+pub use win::*;
diff --git a/crates/displays/src/platform/win.rs b/crates/displays/src/platform/win.rs
index db78e9959..a09ea60a3 100644
--- a/crates/displays/src/platform/win.rs
+++ b/crates/displays/src/platform/win.rs
@@ -1,15 +1,79 @@
+use std::{mem, str::FromStr};
+
use windows::{
Win32::{
- Foundation::{FALSE, LPARAM, RECT},
- Graphics::Gdi::{EnumDisplayMonitors, GetMonitorInfoW, HDC, HMONITOR, MONITORINFOEXW},
+ Devices::Display::{
+ DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME, DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME,
+ DISPLAYCONFIG_DEVICE_INFO_HEADER, DISPLAYCONFIG_MODE_INFO, DISPLAYCONFIG_PATH_INFO,
+ DISPLAYCONFIG_SOURCE_DEVICE_NAME, DISPLAYCONFIG_TARGET_DEVICE_NAME,
+ DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS, DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY,
+ DisplayConfigGetDeviceInfo, GetDisplayConfigBufferSizes, QDC_ONLY_ACTIVE_PATHS,
+ QueryDisplayConfig,
+ },
+ Foundation::{CloseHandle, HWND, LPARAM, POINT, RECT, TRUE, WIN32_ERROR, WPARAM},
+ Graphics::Gdi::{
+ BI_RGB, BITMAP, BITMAPINFO, BITMAPINFOHEADER, CreateCompatibleBitmap,
+ CreateCompatibleDC, CreateSolidBrush, DEVMODEW, DIB_RGB_COLORS, DeleteDC, DeleteObject,
+ ENUM_CURRENT_SETTINGS, EnumDisplayMonitors, EnumDisplaySettingsW, FillRect, GetDC,
+ GetDIBits, GetMonitorInfoW, GetObjectA, HBRUSH, HDC, HGDIOBJ, HMONITOR,
+ MONITOR_DEFAULTTONEAREST, MONITOR_DEFAULTTONULL, MONITORINFOEXW, MonitorFromPoint,
+ ReleaseDC, SelectObject,
+ },
+ Storage::FileSystem::{GetFileVersionInfoSizeW, GetFileVersionInfoW, VerQueryValueW},
+ System::{
+ Registry::{
+ HKEY, HKEY_LOCAL_MACHINE, KEY_READ, REG_BINARY, REG_SZ, RegCloseKey, RegEnumKeyExW,
+ RegOpenKeyExW, RegQueryValueExW,
+ },
+ Threading::{
+ GetCurrentProcessId, OpenProcess, PROCESS_NAME_FORMAT,
+ PROCESS_QUERY_LIMITED_INFORMATION, QueryFullProcessImageNameW,
+ },
+ },
+ UI::{
+ HiDpi::GetDpiForWindow,
+ Shell::ExtractIconExW,
+ WindowsAndMessaging::{
+ DI_FLAGS, DestroyIcon, DrawIconEx, EnumWindows, GCLP_HICON, GW_HWNDNEXT,
+ GWL_EXSTYLE, GetClassLongPtrW, GetClassNameW, GetCursorPos, GetIconInfo,
+ GetLayeredWindowAttributes, GetWindow, GetWindowLongW, GetWindowRect,
+ GetWindowThreadProcessId, HICON, ICONINFO, IsIconic, IsWindowVisible, SendMessageW,
+ WM_GETICON, WS_EX_LAYERED, WS_EX_TOPMOST, WS_EX_TRANSPARENT, WindowFromPoint,
+ },
+ },
},
- core::BOOL,
+ core::{BOOL, PCWSTR, PWSTR},
};
+use crate::bounds::{LogicalBounds, LogicalPosition, LogicalSize, PhysicalSize};
+
#[derive(Clone, Copy)]
pub struct DisplayImpl(HMONITOR);
impl DisplayImpl {
+ pub fn primary() -> Self {
+ // Find the primary monitor by checking the MONITORINFOF_PRIMARY flag
+ const MONITORINFOF_PRIMARY: u32 = 1u32;
+
+ for display in Self::list() {
+ let mut info = MONITORINFOEXW::default();
+ info.monitorInfo.cbSize = mem::size_of::() as u32;
+
+ unsafe {
+ if GetMonitorInfoW(display.0, &mut info as *mut _ as *mut _).as_bool() {
+ if (info.monitorInfo.dwFlags & MONITORINFOF_PRIMARY) != 0 {
+ return display;
+ }
+ }
+ }
+ }
+
+ // Fallback to the old method if no primary monitor is found
+ let point = POINT { x: 0, y: 0 };
+ let monitor = unsafe { MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST) };
+ Self(monitor)
+ }
+
pub fn list() -> Vec {
unsafe extern "system" fn monitor_enum_proc(
hmonitor: HMONITOR,
@@ -17,13 +81,9 @@ impl DisplayImpl {
_lprc_clip: *mut RECT,
lparam: LPARAM,
) -> BOOL {
- unsafe {
- let list = &mut *(lparam.0 as *mut Vec);
-
- list.push(DisplayImpl(hmonitor));
-
- FALSE
- }
+ let list = unsafe { &mut *(lparam.0 as *mut Vec) };
+ list.push(DisplayImpl(hmonitor));
+ TRUE
}
let mut list = vec![];
@@ -32,25 +92,1286 @@ impl DisplayImpl {
None,
None,
Some(monitor_enum_proc),
- LPARAM(core::ptr::addr_of_mut!(list) as isize),
+ LPARAM(std::ptr::addr_of_mut!(list) as isize),
);
- };
+ }
list
}
- pub fn bounds(&self) -> Option {
- let mut minfo = MONITORINFOEXW::default();
+ pub fn inner(&self) -> HMONITOR {
+ self.0
+ }
+
+ pub fn raw_id(&self) -> DisplayIdImpl {
+ DisplayIdImpl(self.0.0 as u64)
+ }
+
+ pub fn from_id(id: String) -> Option {
+ let parsed_id = id.parse::().ok()?;
+ Self::list().into_iter().find(|d| d.raw_id().0 == parsed_id)
+ }
+
+ pub fn logical_size(&self) -> LogicalSize {
+ let mut info = MONITORINFOEXW::default();
+ info.monitorInfo.cbSize = mem::size_of::() as u32;
+
+ unsafe {
+ if GetMonitorInfoW(self.0, &mut info as *mut _ as *mut _).as_bool() {
+ let rect = info.monitorInfo.rcMonitor;
+ LogicalSize {
+ width: (rect.right - rect.left) as f64,
+ height: (rect.bottom - rect.top) as f64,
+ }
+ } else {
+ LogicalSize {
+ width: 0.0,
+ height: 0.0,
+ }
+ }
+ }
+ }
+
+ pub fn logical_position(&self) -> LogicalPosition {
+ let mut info = MONITORINFOEXW::default();
+ info.monitorInfo.cbSize = mem::size_of::() as u32;
+
+ unsafe {
+ if GetMonitorInfoW(self.0, &mut info as *mut _ as *mut _).as_bool() {
+ let rect = info.monitorInfo.rcMonitor;
+ LogicalPosition {
+ x: rect.left as f64,
+ y: rect.top as f64,
+ }
+ } else {
+ LogicalPosition { x: 0.0, y: 0.0 }
+ }
+ }
+ }
+
+ pub fn get_containing_cursor() -> Option {
+ let cursor = get_cursor_position()?;
+ let point = POINT {
+ x: cursor.x() as i32,
+ y: cursor.y() as i32,
+ };
+
+ let monitor = unsafe { MonitorFromPoint(point, MONITOR_DEFAULTTONULL) };
+ if monitor.0 as usize != 0 {
+ Some(Self(monitor))
+ } else {
+ None
+ }
+ }
+
+ pub fn physical_size(&self) -> PhysicalSize {
+ let mut info = MONITORINFOEXW::default();
+ info.monitorInfo.cbSize = mem::size_of::() as u32;
- minfo.monitorInfo.cbSize = std::mem::size_of::() as u32;
unsafe {
- GetMonitorInfoW(self.0, &mut minfo as *mut MONITORINFOEXW as *mut _)
+ if GetMonitorInfoW(self.0, &mut info as *mut _ as *mut _).as_bool() {
+ let device_name = info.szDevice;
+ let mut devmode = DEVMODEW::default();
+ devmode.dmSize = mem::size_of::() as u16;
+
+ if EnumDisplaySettingsW(
+ PCWSTR(device_name.as_ptr()),
+ ENUM_CURRENT_SETTINGS,
+ &mut devmode,
+ )
.as_bool()
- .then_some(minfo.monitorInfo.rcMonitor)
+ {
+ PhysicalSize {
+ width: devmode.dmPelsWidth as f64,
+ height: devmode.dmPelsHeight as f64,
+ }
+ } else {
+ PhysicalSize {
+ width: 0.0,
+ height: 0.0,
+ }
+ }
+ } else {
+ PhysicalSize {
+ width: 0.0,
+ height: 0.0,
+ }
+ }
+ }
+ }
+
+ pub fn refresh_rate(&self) -> f64 {
+ let mut info = MONITORINFOEXW::default();
+ info.monitorInfo.cbSize = mem::size_of::() as u32;
+
+ unsafe {
+ if GetMonitorInfoW(self.0, &mut info as *mut _ as *mut _).as_bool() {
+ let device_name = info.szDevice;
+ let mut devmode = DEVMODEW::default();
+ devmode.dmSize = mem::size_of::() as u16;
+
+ if EnumDisplaySettingsW(
+ PCWSTR(device_name.as_ptr()),
+ ENUM_CURRENT_SETTINGS,
+ &mut devmode,
+ )
+ .as_bool()
+ {
+ devmode.dmDisplayFrequency as f64
+ } else {
+ 0.0
+ }
+ } else {
+ 0.0
+ }
+ }
+ }
+
+ pub fn name(&self) -> String {
+ // First try the modern DisplayConfig API for friendly names
+ if let Some(friendly_name) = self.get_friendly_name_from_displayconfig() {
+ return friendly_name;
+ }
+
+ // Try WMI query for better localized names
+ if let Some(wmi_name) = self.get_friendly_name_from_wmi() {
+ return wmi_name;
+ }
+
+ // Fallback to the existing registry method
+ let mut info = MONITORINFOEXW::default();
+ info.monitorInfo.cbSize = mem::size_of::() as u32;
+
+ unsafe {
+ if GetMonitorInfoW(self.0, &mut info as *mut _ as *mut _).as_bool() {
+ // Convert the device name from wide string to String
+ let device_name = &info.szDevice;
+ let null_pos = device_name
+ .iter()
+ .position(|&c| c == 0)
+ .unwrap_or(device_name.len());
+ let device_name_str = String::from_utf16_lossy(&device_name[..null_pos]);
+
+ // Try to get friendly name from registry
+ if let Some(friendly_name) = self.get_friendly_name_from_registry(&device_name_str)
+ {
+ return friendly_name;
+ }
+
+ // Try EDID-based name lookup as final fallback before device name
+ if let Some(edid_name) = self.get_friendly_name_from_edid(&device_name_str) {
+ return edid_name;
+ }
+
+ // Final fallback to device name
+ device_name_str
+ } else {
+ format!("Unknown Display")
+ }
+ }
+ }
+
+ fn get_friendly_name_from_displayconfig(&self) -> Option {
+ unsafe {
+ // Get the device name first
+ let mut info = MONITORINFOEXW::default();
+ info.monitorInfo.cbSize = mem::size_of::() as u32;
+
+ if !GetMonitorInfoW(self.0, &mut info as *mut _ as *mut _).as_bool() {
+ return None;
+ }
+
+ let device_name = &info.szDevice;
+ let null_pos = device_name
+ .iter()
+ .position(|&c| c == 0)
+ .unwrap_or(device_name.len());
+ let device_name_str = String::from_utf16_lossy(&device_name[..null_pos]);
+
+ // Get display configuration
+ let mut num_paths = 0u32;
+ let mut num_modes = 0u32;
+
+ if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut num_paths, &mut num_modes)
+ != WIN32_ERROR(0)
+ {
+ return None;
+ }
+
+ let mut paths = vec![DISPLAYCONFIG_PATH_INFO::default(); num_paths as usize];
+ let mut modes = vec![DISPLAYCONFIG_MODE_INFO::default(); num_modes as usize];
+
+ if QueryDisplayConfig(
+ QDC_ONLY_ACTIVE_PATHS,
+ &mut num_paths,
+ paths.as_mut_ptr(),
+ &mut num_modes,
+ modes.as_mut_ptr(),
+ None,
+ ) != WIN32_ERROR(0)
+ {
+ return None;
+ }
+
+ // Find the matching path for our monitor
+ for path in &paths {
+ // Get source device name to match with our monitor
+ let mut source_name = DISPLAYCONFIG_SOURCE_DEVICE_NAME {
+ header: DISPLAYCONFIG_DEVICE_INFO_HEADER {
+ r#type: DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME,
+ size: mem::size_of::() as u32,
+ adapterId: path.sourceInfo.adapterId,
+ id: path.sourceInfo.id,
+ },
+ viewGdiDeviceName: [0; 32],
+ };
+
+ if DisplayConfigGetDeviceInfo(&mut source_name.header as *mut _ as *mut _) != 0 {
+ continue;
+ }
+
+ let source_device_name = String::from_utf16_lossy(&source_name.viewGdiDeviceName);
+ let source_null_pos = source_device_name
+ .chars()
+ .position(|c| c == '\0')
+ .unwrap_or(source_device_name.len());
+ let source_trimmed = &source_device_name[..source_null_pos];
+
+ // Check if this matches our monitor
+ if source_trimmed == device_name_str {
+ // Get the target (monitor) friendly name
+ let mut target_name = DISPLAYCONFIG_TARGET_DEVICE_NAME {
+ header: DISPLAYCONFIG_DEVICE_INFO_HEADER {
+ r#type: DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME,
+ size: mem::size_of::() as u32,
+ adapterId: path.sourceInfo.adapterId,
+ id: path.targetInfo.id,
+ },
+ flags: DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS::default(),
+ outputTechnology: DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY::default(),
+ edidManufactureId: 0,
+ edidProductCodeId: 0,
+ connectorInstance: 0,
+ monitorFriendlyDeviceName: [0; 64],
+ monitorDevicePath: [0; 128],
+ };
+
+ if DisplayConfigGetDeviceInfo(&mut target_name.header as *mut _ as *mut _) == 0
+ {
+ let friendly_name =
+ String::from_utf16_lossy(&target_name.monitorFriendlyDeviceName);
+ let null_pos = friendly_name
+ .chars()
+ .position(|c| c == '\0')
+ .unwrap_or(friendly_name.len());
+ let trimmed_name = friendly_name[..null_pos].trim();
+
+ if !trimmed_name.is_empty() && trimmed_name != "Generic PnP Monitor" {
+ return Some(trimmed_name.to_string());
+ }
+ }
+ }
+ }
}
+
+ None
}
- pub fn id(&self) -> u32 {
- self.0.0 as u32
+ fn get_friendly_name_from_wmi(&self) -> Option {
+ unsafe {
+ // Get the device name first for matching
+ let mut info = MONITORINFOEXW::default();
+ info.monitorInfo.cbSize = mem::size_of::() as u32;
+
+ if !GetMonitorInfoW(self.0, &mut info as *mut _ as *mut _).as_bool() {
+ return None;
+ }
+
+ let device_name = &info.szDevice;
+ let null_pos = device_name
+ .iter()
+ .position(|&c| c == 0)
+ .unwrap_or(device_name.len());
+ let device_name_str = String::from_utf16_lossy(&device_name[..null_pos]);
+
+ // Try alternative registry paths for monitor information
+ let alt_registry_paths = [
+ format!(
+ "SYSTEM\\CurrentControlSet\\Control\\GraphicsDrivers\\Configuration\\{}",
+ device_name_str.replace("\\\\.\\", "")
+ ),
+ format!(
+ "SYSTEM\\CurrentControlSet\\Hardware Profiles\\Current\\System\\CurrentControlSet\\Control\\VIDEO\\{}",
+ device_name_str.replace("\\\\.\\DISPLAY", "")
+ ),
+ ];
+
+ for registry_path in &alt_registry_paths {
+ let registry_path_wide: Vec = registry_path
+ .encode_utf16()
+ .chain(std::iter::once(0))
+ .collect();
+
+ let mut key: HKEY = HKEY::default();
+ if RegOpenKeyExW(
+ HKEY_LOCAL_MACHINE,
+ PCWSTR(registry_path_wide.as_ptr()),
+ Some(0),
+ KEY_READ,
+ &mut key,
+ )
+ .is_ok()
+ {
+ // Try to get monitor description from alternative locations
+ let value_names = ["Monitor_Name", "Description", "FriendlyName"];
+
+ for value_name in &value_names {
+ let value_name_wide = format!("{}\0", value_name)
+ .encode_utf16()
+ .collect::>();
+ let mut buffer = [0u16; 512];
+ let mut buffer_size = (buffer.len() * 2) as u32;
+ let mut value_type = REG_SZ;
+
+ if RegQueryValueExW(
+ key,
+ PCWSTR(value_name_wide.as_ptr()),
+ None,
+ Some(&mut value_type),
+ Some(buffer.as_mut_ptr() as *mut u8),
+ Some(&mut buffer_size),
+ )
+ .is_ok()
+ {
+ let null_pos =
+ buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len());
+ let desc = String::from_utf16_lossy(&buffer[..null_pos]);
+ let cleaned_name = desc.trim().to_string();
+
+ if !cleaned_name.is_empty() && cleaned_name != "Default Monitor" {
+ let _ = RegCloseKey(key);
+ return Some(cleaned_name);
+ }
+ }
+ }
+ let _ = RegCloseKey(key);
+ }
+ }
+ }
+ None
+ }
+
+ fn get_friendly_name_from_registry(&self, device_name: &str) -> Option {
+ unsafe {
+ // Try multiple registry paths for better name resolution
+ let registry_paths = [
+ format!(
+ "SYSTEM\\CurrentControlSet\\Enum\\DISPLAY\\{}",
+ device_name.replace("\\\\.\\", "")
+ ),
+ format!(
+ "SYSTEM\\CurrentControlSet\\Control\\Class\\{{4d36e96e-e325-11ce-bfc1-08002be10318}}"
+ ),
+ ];
+
+ for registry_path in ®istry_paths {
+ let registry_path_wide: Vec = registry_path
+ .encode_utf16()
+ .chain(std::iter::once(0))
+ .collect();
+
+ let mut key: HKEY = HKEY::default();
+ if RegOpenKeyExW(
+ HKEY_LOCAL_MACHINE,
+ PCWSTR(registry_path_wide.as_ptr()),
+ Some(0),
+ KEY_READ,
+ &mut key,
+ )
+ .is_ok()
+ {
+ // Try multiple value names for better localization
+ let value_names = ["FriendlyName", "DeviceDesc", "DriverDesc"];
+
+ for value_name in &value_names {
+ let value_name_wide = format!("{}\0", value_name)
+ .encode_utf16()
+ .collect::>();
+ let mut buffer = [0u16; 512];
+ let mut buffer_size = (buffer.len() * 2) as u32;
+ let mut value_type = REG_SZ;
+
+ if RegQueryValueExW(
+ key,
+ PCWSTR(value_name_wide.as_ptr()),
+ None,
+ Some(&mut value_type),
+ Some(buffer.as_mut_ptr() as *mut u8),
+ Some(&mut buffer_size),
+ )
+ .is_ok()
+ {
+ let null_pos =
+ buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len());
+ let desc = String::from_utf16_lossy(&buffer[..null_pos]);
+
+ // Clean up the description
+ let cleaned_name = if let Some(semicolon_pos) = desc.rfind(';') {
+ desc[semicolon_pos + 1..].trim().to_string()
+ } else {
+ desc.trim().to_string()
+ };
+
+ if !cleaned_name.is_empty()
+ && !cleaned_name.contains("PCI\\VEN_")
+ && cleaned_name != "Generic PnP Monitor"
+ {
+ let _ = RegCloseKey(key);
+ return Some(cleaned_name);
+ }
+ }
+ }
+ let _ = RegCloseKey(key);
+ }
+ }
+ }
+ None
+ }
+
+ fn get_friendly_name_from_edid(&self, device_name: &str) -> Option {
+ unsafe {
+ // Registry path for EDID data
+ let edid_path = format!(
+ "SYSTEM\\CurrentControlSet\\Enum\\DISPLAY\\{}",
+ device_name.replace("\\\\.\\", "")
+ );
+ let edid_path_wide: Vec =
+ edid_path.encode_utf16().chain(std::iter::once(0)).collect();
+
+ let mut key: HKEY = HKEY::default();
+ if RegOpenKeyExW(
+ HKEY_LOCAL_MACHINE,
+ PCWSTR(edid_path_wide.as_ptr()),
+ Some(0),
+ KEY_READ,
+ &mut key,
+ )
+ .is_ok()
+ {
+ // Enumerate subkeys to find device instances
+ let mut index = 0;
+ loop {
+ let mut subkey_name = [0u16; 256];
+ let mut subkey_size = subkey_name.len() as u32;
+
+ if RegEnumKeyExW(
+ key,
+ index,
+ Some(PWSTR(subkey_name.as_mut_ptr())),
+ &mut subkey_size,
+ None,
+ Some(PWSTR::null()),
+ None,
+ None,
+ )
+ .is_err()
+ {
+ break;
+ }
+
+ // Open device parameters subkey
+ let subkey_str = String::from_utf16_lossy(&subkey_name[..subkey_size as usize]);
+ let params_path = format!("{}\\Device Parameters", subkey_str);
+ let params_path_wide: Vec = params_path
+ .encode_utf16()
+ .chain(std::iter::once(0))
+ .collect();
+
+ let mut params_key: HKEY = HKEY::default();
+ if RegOpenKeyExW(
+ key,
+ PCWSTR(params_path_wide.as_ptr()),
+ Some(0),
+ KEY_READ,
+ &mut params_key,
+ )
+ .is_ok()
+ {
+ // Read EDID data
+ let edid_value = "EDID\0".encode_utf16().collect::>();
+ let mut edid_buffer = [0u8; 256];
+ let mut edid_size = edid_buffer.len() as u32;
+ let mut value_type = REG_BINARY;
+
+ if RegQueryValueExW(
+ params_key,
+ PCWSTR(edid_value.as_ptr()),
+ None,
+ Some(&mut value_type),
+ Some(edid_buffer.as_mut_ptr()),
+ Some(&mut edid_size),
+ )
+ .is_ok()
+ && edid_size >= 128
+ {
+ // Parse EDID for monitor name (descriptor blocks start at offset 54)
+ for i in (54..126).step_by(18) {
+ if i + 18 > edid_buffer.len() {
+ break;
+ }
+ if edid_buffer[i] == 0
+ && edid_buffer[i + 1] == 0
+ && edid_buffer[i + 2] == 0
+ && edid_buffer[i + 3] == 0xFC
+ {
+ // Monitor name descriptor found
+ if i + 18 <= edid_buffer.len() {
+ let name_bytes = &edid_buffer[i + 5..i + 18];
+ let name_str = String::from_utf8_lossy(name_bytes);
+ let name =
+ name_str.trim_end_matches('\0').trim().to_string();
+
+ if !name.is_empty() {
+ let _ = RegCloseKey(params_key);
+ let _ = RegCloseKey(key);
+ return Some(name);
+ }
+ }
+ }
+ }
+ }
+ let _ = RegCloseKey(params_key);
+ }
+ index += 1;
+ }
+ let _ = RegCloseKey(key);
+ }
+ }
+ None
+ }
+}
+
+fn get_cursor_position() -> Option {
+ let mut point = POINT { x: 0, y: 0 };
+ unsafe {
+ if GetCursorPos(&mut point).is_ok() {
+ Some(LogicalPosition {
+ x: point.x as f64,
+ y: point.y as f64,
+ })
+ } else {
+ None
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+pub struct WindowImpl(HWND);
+
+impl WindowImpl {
+ pub fn list() -> Vec {
+ struct EnumContext {
+ list: Vec,
+ current_process_id: u32,
+ }
+
+ unsafe extern "system" fn enum_windows_proc(hwnd: HWND, lparam: LPARAM) -> BOOL {
+ let context = unsafe { &mut *(lparam.0 as *mut EnumContext) };
+
+ if is_window_valid_for_enumeration(hwnd, context.current_process_id) {
+ context.list.push(WindowImpl(hwnd));
+ }
+
+ TRUE
+ }
+
+ let mut context = EnumContext {
+ list: vec![],
+ current_process_id: unsafe { GetCurrentProcessId() },
+ };
+
+ unsafe {
+ let _ = EnumWindows(
+ Some(enum_windows_proc),
+ LPARAM(std::ptr::addr_of_mut!(context) as isize),
+ );
+ }
+
+ context.list
+ }
+
+ pub fn get_topmost_at_cursor() -> Option {
+ let cursor = get_cursor_position()?;
+ let point = POINT {
+ x: cursor.x() as i32,
+ y: cursor.y() as i32,
+ };
+
+ unsafe {
+ // Use WindowFromPoint first as a quick check
+ let hwnd_at_point = WindowFromPoint(point);
+ if hwnd_at_point != HWND(std::ptr::null_mut()) {
+ let current_process_id = GetCurrentProcessId();
+
+ // Walk up the Z-order chain to find the topmost valid window
+ let mut current_hwnd = hwnd_at_point;
+
+ loop {
+ // Check if this window is valid for our purposes
+ if is_window_valid_for_topmost_selection(
+ current_hwnd,
+ current_process_id,
+ point,
+ ) {
+ return Some(Self(current_hwnd));
+ }
+
+ // Move to the next window in Z-order (towards background)
+ current_hwnd =
+ GetWindow(current_hwnd, GW_HWNDNEXT).unwrap_or(HWND(std::ptr::null_mut()));
+ if current_hwnd == HWND(std::ptr::null_mut()) {
+ break;
+ }
+
+ // Check if this window still contains the point
+ if !is_point_in_window(current_hwnd, point) {
+ continue;
+ }
+ }
+ }
+
+ // Fallback to enumeration if WindowFromPoint fails
+ Self::get_topmost_at_cursor_fallback(point)
+ }
+ }
+
+ fn get_topmost_at_cursor_fallback(point: POINT) -> Option {
+ struct HitTestData {
+ pt: POINT,
+ candidates: Vec,
+ current_process_id: u32,
+ }
+
+ unsafe extern "system" fn enum_windows_proc(hwnd: HWND, lparam: LPARAM) -> BOOL {
+ let data = unsafe { &mut *(lparam.0 as *mut HitTestData) };
+
+ if is_window_valid_for_topmost_selection(hwnd, data.current_process_id, data.pt) {
+ data.candidates.push(hwnd);
+ }
+
+ TRUE
+ }
+
+ let mut data = HitTestData {
+ pt: point,
+ candidates: Vec::new(),
+ current_process_id: unsafe { GetCurrentProcessId() },
+ };
+
+ unsafe {
+ let _ = EnumWindows(
+ Some(enum_windows_proc),
+ LPARAM(std::ptr::addr_of_mut!(data) as isize),
+ );
+
+ // Sort candidates by Z-order (topmost first)
+ data.candidates.sort_by(|&a, &b| {
+ // Use GetWindowLong to check topmost status
+ let a_topmost = (GetWindowLongW(a, GWL_EXSTYLE) & WS_EX_TOPMOST.0 as i32) != 0;
+ let b_topmost = (GetWindowLongW(b, GWL_EXSTYLE) & WS_EX_TOPMOST.0 as i32) != 0;
+
+ match (a_topmost, b_topmost) {
+ (true, false) => std::cmp::Ordering::Less, // a is more topmost
+ (false, true) => std::cmp::Ordering::Greater, // b is more topmost
+ _ => std::cmp::Ordering::Equal, // Same topmost level
+ }
+ });
+
+ data.candidates.first().map(|&hwnd| Self(hwnd))
+ }
+ }
+
+ pub fn list_containing_cursor() -> Vec {
+ let Some(cursor) = get_cursor_position() else {
+ return vec![];
+ };
+
+ Self::list()
+ .into_iter()
+ .filter_map(|window| {
+ let bounds = window.bounds()?;
+ bounds.contains_point(cursor).then_some(window)
+ })
+ .collect()
+ }
+
+ pub fn id(&self) -> WindowIdImpl {
+ WindowIdImpl(self.0.0 as u64)
+ }
+
+ pub fn level(&self) -> Option {
+ unsafe {
+ // Windows doesn't have the same level concept as macOS, but we can approximate
+ // using extended window styles and z-order information
+ let ex_style = GetWindowLongW(self.0, GWL_EXSTYLE);
+
+ // Check if window has topmost style
+ if (ex_style & WS_EX_TOPMOST.0 as i32) != 0 {
+ Some(3) // Higher level for topmost windows
+ } else {
+ Some(0) // Normal level for regular windows
+ }
+ }
+ }
+
+ pub fn owner_name(&self) -> Option {
+ unsafe {
+ let mut process_id = 0u32;
+ GetWindowThreadProcessId(self.0, Some(&mut process_id));
+
+ if process_id == 0 {
+ return None;
+ }
+
+ let process_handle =
+ OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, process_id).ok()?;
+
+ let mut buffer = [0u16; 1024];
+ let mut buffer_size = buffer.len() as u32;
+
+ let result = QueryFullProcessImageNameW(
+ process_handle,
+ PROCESS_NAME_FORMAT::default(),
+ PWSTR(buffer.as_mut_ptr()),
+ &mut buffer_size,
+ );
+
+ let _ = CloseHandle(process_handle);
+
+ if result.is_ok() && buffer_size > 0 {
+ let path_str = String::from_utf16_lossy(&buffer[..buffer_size as usize]);
+
+ // Try to get the friendly name from version info first
+ if let Some(friendly_name) = self.get_file_description(&path_str) {
+ return Some(friendly_name);
+ }
+
+ // Fallback to file stem
+ std::path::Path::new(&path_str)
+ .file_stem()
+ .map(|stem| stem.to_string_lossy().into_owned())
+ } else {
+ None
+ }
+ }
+ }
+
+ fn get_file_description(&self, file_path: &str) -> Option {
+ unsafe {
+ let wide_path: Vec = file_path.encode_utf16().chain(std::iter::once(0)).collect();
+
+ let size = GetFileVersionInfoSizeW(PCWSTR(wide_path.as_ptr()), None);
+ if size == 0 {
+ return None;
+ }
+
+ let mut buffer = vec![0u8; size as usize];
+ if !GetFileVersionInfoW(
+ PCWSTR(wide_path.as_ptr()),
+ Some(0),
+ size,
+ buffer.as_mut_ptr() as *mut _,
+ )
+ .is_ok()
+ {
+ return None;
+ }
+
+ let mut len = 0u32;
+ let mut value_ptr: *mut u16 = std::ptr::null_mut();
+
+ let query = "\\StringFileInfo\\040904B0\\FileDescription\0"
+ .encode_utf16()
+ .collect::>();
+
+ if VerQueryValueW(
+ buffer.as_ptr() as *const _,
+ PCWSTR(query.as_ptr()),
+ &mut value_ptr as *mut _ as *mut *mut _,
+ &mut len,
+ )
+ .as_bool()
+ && !value_ptr.is_null()
+ && len > 0
+ {
+ let slice = std::slice::from_raw_parts(value_ptr, len as usize - 1);
+ Some(String::from_utf16_lossy(slice))
+ } else {
+ None
+ }
+ }
+ }
+
+ pub fn app_icon(&self) -> Option> {
+ unsafe {
+ // Try multiple approaches to get the highest quality icon
+ let mut best_result = None;
+ let mut best_size = 0;
+
+ // Method 1: Try to get the window's large icon first
+ let large_icon = SendMessageW(
+ self.0,
+ WM_GETICON,
+ Some(WPARAM(1usize)),
+ Some(LPARAM(0isize)),
+ ); // ICON_BIG = 1
+
+ if large_icon.0 != 0 {
+ if let Some(result) = self.hicon_to_png_bytes_high_res(HICON(large_icon.0 as _)) {
+ best_result = Some(result.0);
+ best_size = result.1;
+ }
+ }
+
+ // Method 2: Try executable file extraction with priority on larger icons
+ if let Some(exe_path) = self.get_executable_path() {
+ let wide_path: Vec =
+ exe_path.encode_utf16().chain(std::iter::once(0)).collect();
+
+ // Try extracting icons from multiple indices
+ for icon_index in 0..6 {
+ let mut large_icon: HICON = HICON::default();
+ let mut small_icon: HICON = HICON::default();
+
+ let extracted = ExtractIconExW(
+ PCWSTR(wide_path.as_ptr()),
+ icon_index,
+ Some(&mut large_icon),
+ Some(&mut small_icon),
+ 1,
+ );
+
+ if extracted > 0 {
+ // Try large icon first
+ if !large_icon.is_invalid() {
+ if let Some(result) = self.hicon_to_png_bytes_high_res(large_icon) {
+ if result.1 > best_size {
+ best_result = Some(result.0);
+ best_size = result.1;
+ }
+ }
+ let _ = DestroyIcon(large_icon);
+ }
+
+ // Try small icon if we haven't found anything good yet
+ if !small_icon.is_invalid() && best_size < 64 {
+ if let Some(result) = self.hicon_to_png_bytes_high_res(small_icon) {
+ if result.1 > best_size {
+ best_result = Some(result.0);
+ best_size = result.1;
+ }
+ }
+ let _ = DestroyIcon(small_icon);
+ }
+ }
+ }
+ }
+
+ // Method 3: Try small window icon if we still don't have anything good
+ if best_size < 32 {
+ let small_icon = SendMessageW(
+ self.0,
+ WM_GETICON,
+ Some(WPARAM(0usize)),
+ Some(LPARAM(0isize)),
+ ); // ICON_SMALL = 0
+
+ if small_icon.0 != 0 {
+ if let Some(result) = self.hicon_to_png_bytes_high_res(HICON(small_icon.0 as _))
+ {
+ if result.1 > best_size {
+ best_result = Some(result.0);
+ best_size = result.1;
+ }
+ }
+ }
+ }
+
+ // Method 4: Try class icon as last resort
+ if best_size < 32 {
+ let class_icon = GetClassLongPtrW(self.0, GCLP_HICON) as isize;
+ if class_icon != 0 {
+ if let Some(result) = self.hicon_to_png_bytes_high_res(HICON(class_icon as _)) {
+ if result.1 > best_size {
+ best_result = Some(result.0);
+ }
+ }
+ }
+ }
+
+ best_result
+ }
+ }
+
+ fn get_executable_path(&self) -> Option {
+ unsafe {
+ let mut process_id = 0u32;
+ GetWindowThreadProcessId(self.0, Some(&mut process_id));
+
+ if process_id == 0 {
+ return None;
+ }
+
+ let process_handle =
+ OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, process_id).ok()?;
+
+ let mut buffer = [0u16; 1024];
+ let mut buffer_size = buffer.len() as u32;
+
+ let result = QueryFullProcessImageNameW(
+ process_handle,
+ PROCESS_NAME_FORMAT::default(),
+ PWSTR(buffer.as_mut_ptr()),
+ &mut buffer_size,
+ );
+
+ let _ = CloseHandle(process_handle);
+
+ if result.is_ok() && buffer_size > 0 {
+ Some(String::from_utf16_lossy(&buffer[..buffer_size as usize]))
+ } else {
+ None
+ }
+ }
+ }
+
+ fn get_icon_size(&self, icon: HICON) -> Option<(i32, i32)> {
+ unsafe {
+ let mut icon_info = ICONINFO::default();
+ if !GetIconInfo(icon, &mut icon_info).is_ok() {
+ return None;
+ }
+
+ // Get bitmap info to determine actual size
+ let mut bitmap_info = BITMAP::default();
+ let result = GetObjectA(
+ HGDIOBJ(icon_info.hbmColor.0),
+ mem::size_of::() as i32,
+ Some(&mut bitmap_info as *mut _ as *mut _),
+ );
+
+ // Clean up bitmap handles
+ let _ = DeleteObject(icon_info.hbmColor.into());
+ let _ = DeleteObject(icon_info.hbmMask.into());
+
+ if result > 0 {
+ Some((bitmap_info.bmWidth, bitmap_info.bmHeight))
+ } else {
+ None
+ }
+ }
+ }
+
+ fn hicon_to_png_bytes_high_res(&self, icon: HICON) -> Option<(Vec, i32)> {
+ unsafe {
+ // Get icon info to determine actual size
+ let mut icon_info = ICONINFO::default();
+ if !GetIconInfo(icon, &mut icon_info).is_ok() {
+ return None;
+ }
+
+ // Get device context
+ let screen_dc = GetDC(Some(HWND::default()));
+ let mem_dc = CreateCompatibleDC(Some(screen_dc));
+
+ // Get the native icon size to prioritize it
+ let native_size = self.get_icon_size(icon);
+
+ // Always try for the highest resolution possible, starting with the largest sizes
+ let mut sizes = vec![2048, 1024, 512, 256, 128, 96, 64, 48, 32, 24, 16];
+
+ // If we have native size info, prioritize it
+ if let Some((width, height)) = native_size {
+ let native_dim = width.max(height);
+ const MAX_SIZE: i32 = 4096;
+ if !sizes.contains(&native_dim) && native_dim > 0 && native_dim <= MAX_SIZE {
+ sizes.insert(0, native_dim);
+ }
+ }
+
+ let mut best_result = None;
+ let mut best_size = 0;
+
+ for &size in &sizes {
+ let width = size;
+ let height = size;
+
+ // Create bitmap info for this size
+ let mut bitmap_info = BITMAPINFO {
+ bmiHeader: BITMAPINFOHEADER {
+ biSize: mem::size_of::() as u32,
+ biWidth: width,
+ biHeight: -height, // Top-down DIB
+ biPlanes: 1,
+ biBitCount: 32, // 32 bits per pixel (BGRA)
+ biCompression: BI_RGB.0,
+ biSizeImage: 0,
+ biXPelsPerMeter: 0,
+ biYPelsPerMeter: 0,
+ biClrUsed: 0,
+ biClrImportant: 0,
+ },
+ bmiColors: [Default::default(); 1],
+ };
+
+ // Create a bitmap
+ let bitmap = CreateCompatibleBitmap(screen_dc, width, height);
+ if bitmap.is_invalid() {
+ continue;
+ }
+
+ let old_bitmap = SelectObject(mem_dc, bitmap.into());
+
+ // Fill with transparent background
+ let brush = CreateSolidBrush(windows::Win32::Foundation::COLORREF(0));
+ let rect = RECT {
+ left: 0,
+ top: 0,
+ right: width,
+ bottom: height,
+ };
+ let _ = FillRect(mem_dc, &rect, brush);
+ let _ = DeleteObject(brush.into());
+
+ // Draw the icon onto the bitmap with proper scaling
+ let draw_result = DrawIconEx(
+ mem_dc,
+ 0,
+ 0,
+ icon,
+ width,
+ height,
+ 0,
+ Some(HBRUSH::default()),
+ DI_FLAGS(0x0003), // DI_NORMAL
+ );
+
+ if draw_result.is_ok() {
+ // Get bitmap bits
+ let mut buffer = vec![0u8; (width * height * 4) as usize];
+ let result = GetDIBits(
+ mem_dc,
+ bitmap,
+ 0,
+ height as u32,
+ Some(buffer.as_mut_ptr() as *mut _),
+ &mut bitmap_info,
+ DIB_RGB_COLORS,
+ );
+
+ if result > 0 {
+ // Check if we have any non-transparent pixels
+ let has_content = buffer.chunks_exact(4).any(|chunk| chunk[3] != 0);
+
+ if has_content {
+ // Convert BGRA to RGBA
+ for chunk in buffer.chunks_exact_mut(4) {
+ chunk.swap(0, 2); // Swap B and R
+ }
+
+ // Create PNG using the image crate
+ if let Some(img) =
+ image::RgbaImage::from_raw(width as u32, height as u32, buffer)
+ {
+ let mut png_data = Vec::new();
+ if img
+ .write_to(
+ &mut std::io::Cursor::new(&mut png_data),
+ image::ImageFormat::Png,
+ )
+ .is_ok()
+ {
+ // Keep the result if it's our first success or if this size is larger
+ if best_result.is_none() || size > best_size {
+ best_result = Some((png_data, size));
+ best_size = size;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Cleanup for this iteration
+ let _ = SelectObject(mem_dc, old_bitmap);
+ let _ = DeleteObject(bitmap.into());
+ }
+
+ // Cleanup
+ let _ = DeleteDC(mem_dc);
+ let _ = ReleaseDC(Some(HWND::default()), screen_dc);
+ let _ = DeleteObject(icon_info.hbmColor.into());
+ let _ = DeleteObject(icon_info.hbmMask.into());
+
+ best_result
+ }
+ }
+
+ pub fn bounds(&self) -> Option {
+ let mut rect = RECT::default();
+ unsafe {
+ if GetWindowRect(self.0, &mut rect).is_ok() {
+ // Get DPI scaling factor to convert physical to logical coordinates
+ const BASE_DPI: f64 = 96.0;
+ let dpi = match GetDpiForWindow(self.0) {
+ 0 => BASE_DPI as u32,
+ dpi => dpi,
+ } as f64;
+ let scale_factor = dpi / BASE_DPI;
+
+ Some(LogicalBounds {
+ position: LogicalPosition {
+ x: rect.left as f64 / scale_factor,
+ y: rect.top as f64 / scale_factor,
+ },
+ size: LogicalSize {
+ width: (rect.right - rect.left) as f64 / scale_factor,
+ height: (rect.bottom - rect.top) as f64 / scale_factor,
+ },
+ })
+ } else {
+ None
+ }
+ }
+ }
+}
+
+fn is_window_valid_for_enumeration(hwnd: HWND, current_process_id: u32) -> bool {
+ unsafe {
+ // Skip invisible or minimized windows
+ if !IsWindowVisible(hwnd).as_bool() || IsIconic(hwnd).as_bool() {
+ return false;
+ }
+
+ // Skip own process windows
+ let mut process_id = 0u32;
+ GetWindowThreadProcessId(hwnd, Some(&mut process_id));
+ process_id != current_process_id
+ }
+}
+
+fn is_window_valid_for_topmost_selection(
+ hwnd: HWND,
+ current_process_id: u32,
+ point: POINT,
+) -> bool {
+ unsafe {
+ // Skip invisible or minimized windows
+ if !IsWindowVisible(hwnd).as_bool() || IsIconic(hwnd).as_bool() {
+ return false;
+ }
+
+ // Skip own process windows (includes overlays)
+ let mut process_id = 0u32;
+ GetWindowThreadProcessId(hwnd, Some(&mut process_id));
+ if process_id == current_process_id {
+ return false;
+ }
+
+ // Check if point is actually in this window
+ if !is_point_in_window(hwnd, point) {
+ return false;
+ }
+
+ // Skip certain window classes that should be ignored
+ let mut class_name = [0u16; 256];
+ let len = GetClassNameW(hwnd, &mut class_name);
+ if len > 0 {
+ let class_name_str = String::from_utf16_lossy(&class_name[..len as usize]);
+ match class_name_str.as_str() {
+ "Shell_TrayWnd" | "Button" | "Tooltip" | "ToolTips_Class32" => return false,
+ _ => {}
+ }
+ }
+
+ // Skip windows with certain extended styles
+ let ex_style = GetWindowLongW(hwnd, GWL_EXSTYLE) as u32;
+ if (ex_style & WS_EX_TRANSPARENT.0) != 0 || (ex_style & WS_EX_LAYERED.0) != 0 {
+ // Allow layered windows only if they have proper alpha
+ if (ex_style & WS_EX_LAYERED.0) != 0 {
+ let mut alpha = 0u8;
+ let mut color_key = 0u32;
+ let mut flags = 0u32;
+ if GetLayeredWindowAttributes(
+ hwnd,
+ Some(&mut color_key as *mut u32 as *mut _),
+ Some(&mut alpha),
+ Some(&mut flags as *mut u32 as *mut _),
+ )
+ .is_ok()
+ {
+ if alpha < 50 {
+ // Skip nearly transparent windows
+ return false;
+ }
+ }
+ } else {
+ return false; // Skip fully transparent windows
+ }
+ }
+
+ true
+ }
+}
+
+fn is_point_in_window(hwnd: HWND, point: POINT) -> bool {
+ unsafe {
+ let mut rect = RECT::default();
+ if GetWindowRect(hwnd, &mut rect).is_ok() {
+ point.x >= rect.left
+ && point.x < rect.right
+ && point.y >= rect.top
+ && point.y < rect.bottom
+ } else {
+ false
+ }
+ }
+}
+
+#[derive(Clone, PartialEq, Debug)]
+pub struct DisplayIdImpl(u64);
+
+impl std::fmt::Display for DisplayIdImpl {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl FromStr for DisplayIdImpl {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result {
+ s.parse()
+ .map(Self)
+ .map_err(|_| "Invalid display ID".to_string())
+ }
+}
+
+#[derive(Clone, PartialEq, Debug)]
+pub struct WindowIdImpl(u64);
+
+impl std::fmt::Display for WindowIdImpl {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl FromStr for WindowIdImpl {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result {
+ s.parse()
+ .map(Self)
+ .map_err(|_| "Invalid window ID".to_string())
}
}
diff --git a/crates/media/src/feeds/camera.rs b/crates/media/src/feeds/camera.rs
index 4c6411161..213389089 100644
--- a/crates/media/src/feeds/camera.rs
+++ b/crates/media/src/feeds/camera.rs
@@ -90,7 +90,7 @@ impl CameraFeed {
SetupCameraError::Initialisation
);
- let camera_info = find_camera(&selected_camera).unwrap();
+ let camera_info = find_camera(&selected_camera).ok_or(SetupCameraError::CameraNotFound)?;
let (control, control_receiver) = flume::bounded(1);
let (ready_tx, ready_rx) = oneshot::channel();
diff --git a/packages/ui-solid/src/Button.tsx b/packages/ui-solid/src/Button.tsx
index 983bed47b..c7b286d98 100644
--- a/packages/ui-solid/src/Button.tsx
+++ b/packages/ui-solid/src/Button.tsx
@@ -12,7 +12,7 @@ const styles = cva(
variant: {
blue: "bg-blue-9 text-white hover:bg-blue-10 disabled:bg-gray-6 disabled:text-gray-9 outline-blue-300 disabled:outline-blue-10",
primary:
- "bg-gray-12 text-gray-1 hover:bg-gray-11 disabled:bg-gray-6 disabled:text-gray-9 outline-blue-300 disabled:bg-gray-4 disabled:dark:text-gray-9",
+ "bg-gray-12 text-gray-1 hover:bg-gray-11 enabled:hover:bg-blue-8 disabled:bg-gray-6 disabled:text-gray-9 outline-blue-300 disabled:bg-gray-4 disabled:dark:text-gray-9",
secondary:
"bg-gray-4 enabled:hover:bg-gray-5 text-gray-500 disabled:bg-gray-6 disabled:text-gray-9 outline-blue-300 disabled:outline-blue-10",
destructive:
diff --git a/packages/ui-solid/src/auto-imports.d.ts b/packages/ui-solid/src/auto-imports.d.ts
index 6d1f4abd4..b36055084 100644
--- a/packages/ui-solid/src/auto-imports.d.ts
+++ b/packages/ui-solid/src/auto-imports.d.ts
@@ -57,7 +57,11 @@ declare global {
const IconCapUpload: typeof import('~icons/cap/upload.jsx')['default']
const IconCapZoomIn: typeof import('~icons/cap/zoom-in.jsx')['default']
const IconCapZoomOut: typeof import('~icons/cap/zoom-out.jsx')['default']
+ const IconFa6SolidDisplay: typeof import('~icons/fa6-solid/display.jsx')['default']
const IconHugeiconsEaseCurveControlPoints: typeof import('~icons/hugeicons/ease-curve-control-points.jsx')['default']
+ const IconIcBaselineMonitor: typeof import("~icons/ic/baseline-monitor.jsx")["default"]
+ const IconIcRoundSearch: typeof import("~icons/ic/round-search.jsx")["default"]
+ const IconLucideAppWindowMac: typeof import('~icons/lucide/app-window-mac.jsx')['default']
const IconLucideBell: typeof import('~icons/lucide/bell.jsx')['default']
const IconLucideBug: typeof import('~icons/lucide/bug.jsx')['default']
const IconLucideCheck: typeof import('~icons/lucide/check.jsx')['default']
@@ -79,5 +83,10 @@ declare global {
const IconLucideUnplug: typeof import('~icons/lucide/unplug.jsx')['default']
const IconLucideVolume2: typeof import('~icons/lucide/volume2.jsx')['default']
const IconLucideVolumeX: typeof import('~icons/lucide/volume-x.jsx')['default']
+ const IconLucideX: typeof import('~icons/lucide/x.jsx')['default']
+ const IconMaterialSymbolsLightScreenshotFrame2: typeof import('~icons/material-symbols-light/screenshot-frame2.jsx')['default']
+ const IconMaterialSymbolsLightScreenshotFrame2MaterialSymbolsScreenshotFrame2Rounded: typeof import('~icons/material-symbols-light/screenshot-frame2-material-symbols-screenshot-frame2-rounded.jsx')['default']
+ const IconMaterialSymbolsScreenshotFrame2Rounded: typeof import('~icons/material-symbols/screenshot-frame2-rounded.jsx')['default']
+ const IconMdiMonitor: typeof import('~icons/mdi/monitor.jsx')['default']
const IconPhMonitorBold: typeof import('~icons/ph/monitor-bold.jsx')['default']
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7672780d3..556d9c4bc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -80,6 +80,9 @@ importers:
'@solid-primitives/memo':
specifier: ^1.4.2
version: 1.4.2(solid-js@1.9.6)
+ '@solid-primitives/mouse':
+ specifier: ^2.1.2
+ version: 2.1.2(solid-js@1.9.6)
'@solid-primitives/refs':
specifier: ^1.0.8
version: 1.1.1(solid-js@1.9.6)
@@ -5365,6 +5368,11 @@ packages:
peerDependencies:
solid-js: ^1.6.12
+ '@solid-primitives/mouse@2.1.2':
+ resolution: {integrity: sha512-ov4K3eQCybN0bN4ZzBA3mFGuj2AJmgP9kOJaGZtTtjJ6uvI6LYd8pfv+Tm6gb3BlaIlVurLMuOU30mapSaJjhA==}
+ peerDependencies:
+ solid-js: ^1.6.12
+
'@solid-primitives/props@3.2.1':
resolution: {integrity: sha512-SuTuCctLLZbUL1QyWamQGWSWPIgoc/gXt5kL8P2yLhb51f9Dj+WHxU0shXBjzx7z+hDc5KtheQgM4NnJqQJi2A==}
peerDependencies:
@@ -18547,6 +18555,14 @@ snapshots:
'@solid-primitives/utils': 6.3.1(solid-js@1.9.6)
solid-js: 1.9.6
+ '@solid-primitives/mouse@2.1.2(solid-js@1.9.6)':
+ dependencies:
+ '@solid-primitives/event-listener': 2.4.1(solid-js@1.9.6)
+ '@solid-primitives/rootless': 1.5.1(solid-js@1.9.6)
+ '@solid-primitives/static-store': 0.1.1(solid-js@1.9.6)
+ '@solid-primitives/utils': 6.3.1(solid-js@1.9.6)
+ solid-js: 1.9.6
+
'@solid-primitives/props@3.2.1(solid-js@1.9.6)':
dependencies:
'@solid-primitives/utils': 6.3.1(solid-js@1.9.6)
@@ -21778,8 +21794,8 @@ snapshots:
'@typescript-eslint/parser': 5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
eslint: 9.30.1(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.30.1(jiti@2.4.2))
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.4.2))
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2))
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.30.1(jiti@2.4.2))
eslint-plugin-react: 7.37.5(eslint@9.30.1(jiti@2.4.2))
eslint-plugin-react-hooks: 4.6.2(eslint@9.30.1(jiti@2.4.2))
@@ -21807,33 +21823,33 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1):
+ eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.0(supports-color@5.5.0)
- eslint: 8.57.1
+ eslint: 9.30.1(jiti@2.4.2)
get-tsconfig: 4.10.0
is-bun-module: 2.0.0
stable-hash: 0.0.5
tinyglobby: 0.2.13
unrs-resolver: 1.7.2
optionalDependencies:
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2))
transitivePeerDependencies:
- supports-color
- eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.30.1(jiti@2.4.2)):
+ eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.0(supports-color@5.5.0)
- eslint: 9.30.1(jiti@2.4.2)
+ eslint: 8.57.1
get-tsconfig: 4.10.0
is-bun-module: 2.0.0
stable-hash: 0.0.5
tinyglobby: 0.2.13
unrs-resolver: 1.7.2
optionalDependencies:
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
@@ -21848,14 +21864,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.4.2)):
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
eslint: 9.30.1(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.30.1(jiti@2.4.2))
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2))
transitivePeerDependencies:
- supports-color
@@ -21899,7 +21915,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.4.2)):
+ eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -21910,7 +21926,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.30.1(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.4.2))
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
From ee6377fc0fa5a1b1cc803fcb700cdd5a6481b1cb Mon Sep 17 00:00:00 2001
From: Brendan Allan
Date: Tue, 12 Aug 2025 10:15:38 +0800
Subject: [PATCH 11/36] Start using Effect for HTTP and RPC (#869)
* implement some stuff in effect
* some more stuff
* properly port /api/playlist
* cleanup
* cleanup
* better service handling
* replace some actions with rpc and add dedicated packages
* remove effect http summary
* remove unused code
* maybe fix build
* fix build and try axiom tracing
* cloudfront private key
* address a bunch of feedback
* lockfile
* cleanup folder actions
* oops
* remove duplicateFolder
* don't run rust CI if rust didn't change
* whoops
* wat
---
.github/workflows/ci.yml | 24 +
apps/desktop/package.json | 2 +-
apps/web/README.md | 9 +-
apps/web/actions/folders/deleteFolder.ts | 51 --
apps/web/actions/folders/duplicateFolder.ts | 107 ----
apps/web/actions/folders/getChildFolders.ts | 37 --
.../actions/folders/getFolderBreadcrumb.ts | 30 --
apps/web/actions/folders/getFolderById.ts | 20 -
...zation.ts => get-organization-sso-data.ts} | 10 +-
apps/web/actions/organization/update-space.ts | 9 +-
apps/web/actions/videos/delete.ts | 64 ---
apps/web/actions/videos/duplicate.ts | 74 ---
apps/web/actions/videos/get-status.ts | 144 ++++--
apps/web/app/(org)/dashboard/caps/Caps.tsx | 177 ++++---
.../caps/components/CapCard/CapCard.tsx | 88 ++--
.../dashboard/caps/components/Folder.tsx | 108 ++--
.../caps/components/FoldersDropdown.tsx | 75 +--
.../caps/components/SelectedCapsBar.tsx | 9 +-
apps/web/app/(org)/dashboard/caps/page.tsx | 56 +-
.../[id]/components/FolderVideosSection.tsx | 231 +++++----
.../app/(org)/dashboard/folder/[id]/page.tsx | 24 +-
.../dashboard/spaces/[spaceId]/SharedCaps.tsx | 24 +-
.../[spaceId]/components/SharedCapCard.tsx | 8 +-
.../[spaceId]/folder/[folderId]/page.tsx | 29 +-
.../(org)/dashboard/spaces/[spaceId]/page.tsx | 71 +--
apps/web/app/(org)/login/form.tsx | 86 +++-
apps/web/app/Layout/providers.tsx | 2 +-
apps/web/app/api/desktop/[...route]/video.ts | 2 +-
apps/web/app/api/erpc/route.ts | 18 +
apps/web/app/api/playlist/route.ts | 418 ++++++++-------
.../app/api/upload/[...route]/multipart.ts | 4 +-
apps/web/app/api/video/delete/route.ts | 124 ++---
apps/web/app/embed/[videoId]/page.tsx | 7 +-
apps/web/app/s/[videoId]/page.tsx | 2 +-
apps/web/lib/EffectRuntime.ts | 22 +
apps/web/lib/Rpcs.ts | 17 +
apps/web/lib/effect-react-query.ts | 483 ++++++++++++++++++
.../getVideosByFolderId.ts => lib/folder.ts} | 131 ++++-
apps/web/lib/server.ts | 62 +++
apps/web/lib/tracing.ts | 29 ++
.../web/{actions/videos => lib}/transcribe.ts | 2 -
apps/web/next.config.mjs | 8 +-
apps/web/package.json | 11 +
apps/web/tsconfig.json | 57 +--
apps/web/utils/auth.ts | 5 +-
apps/web/utils/video/ffmpeg/helpers.ts | 4 +-
apps/web/utils/web-api.ts | 9 +-
package.json | 3 +-
packages/config/base.tsconfig.json | 7 +-
packages/database/auth/auth-options.tsx | 16 +-
packages/database/dub.ts | 2 +-
packages/database/emails/config.ts | 10 -
packages/database/package.json | 1 +
packages/database/schema.ts | 37 +-
packages/database/tsconfig.json | 8 +-
packages/env/package.json | 2 +-
packages/env/tsconfig.json | 9 +-
packages/ui/tsconfig.json | 4 +
packages/utils/tsconfig.json | 4 +
packages/web-api-contract-effect/package.json | 5 +-
packages/web-api-contract-effect/src/index.ts | 30 +-
packages/web-backend/README.md | 36 ++
packages/web-backend/package.json | 23 +
packages/web-backend/src/Auth.ts | 89 ++++
packages/web-backend/src/Database.ts | 15 +
.../web-backend/src/Folders/FoldersPolicy.ts | 45 ++
.../web-backend/src/Folders/FoldersRpcs.ts | 22 +
packages/web-backend/src/Folders/index.ts | 82 +++
packages/web-backend/src/Rpcs.ts | 33 ++
.../src/S3Buckets/S3BucketAccess.ts | 210 ++++++++
.../src/S3Buckets/S3BucketClientProvider.ts | 9 +
.../src/S3Buckets/S3BucketsRepo.ts | 54 ++
packages/web-backend/src/S3Buckets/index.ts | 167 ++++++
.../web-backend/src/Videos/VideosPolicy.ts | 51 ++
packages/web-backend/src/Videos/VideosRepo.ts | 78 +++
packages/web-backend/src/Videos/VideosRpcs.ts | 27 +
packages/web-backend/src/Videos/index.ts | 110 ++++
packages/web-backend/src/index.ts | 9 +
packages/web-backend/tsconfig.json | 15 +
packages/web-domain/README.md | 10 +
packages/web-domain/package.json | 12 +
packages/web-domain/src/Authentication.ts | 33 ++
packages/web-domain/src/Errors.ts | 6 +
packages/web-domain/src/Folder.ts | 20 +
packages/web-domain/src/Organisation.ts | 6 +
packages/web-domain/src/Policy.ts | 76 +++
packages/web-domain/src/Rpcs.ts | 4 +
packages/web-domain/src/S3Bucket.ts | 16 +
packages/web-domain/src/Video.ts | 88 ++++
packages/web-domain/src/index.ts | 8 +
packages/web-domain/tsconfig.json | 13 +
pnpm-lock.yaml | 369 +++++++++++--
tsconfig.json | 5 +-
93 files changed, 3559 insertions(+), 1304 deletions(-)
delete mode 100644 apps/web/actions/folders/deleteFolder.ts
delete mode 100644 apps/web/actions/folders/duplicateFolder.ts
delete mode 100644 apps/web/actions/folders/getChildFolders.ts
delete mode 100644 apps/web/actions/folders/getFolderBreadcrumb.ts
delete mode 100644 apps/web/actions/folders/getFolderById.ts
rename apps/web/actions/organization/{get-organization.ts => get-organization-sso-data.ts} (79%)
delete mode 100644 apps/web/actions/videos/delete.ts
delete mode 100644 apps/web/actions/videos/duplicate.ts
create mode 100644 apps/web/app/api/erpc/route.ts
create mode 100644 apps/web/lib/EffectRuntime.ts
create mode 100644 apps/web/lib/Rpcs.ts
create mode 100644 apps/web/lib/effect-react-query.ts
rename apps/web/{actions/folders/getVideosByFolderId.ts => lib/folder.ts} (60%)
create mode 100644 apps/web/lib/server.ts
create mode 100644 apps/web/lib/tracing.ts
rename apps/web/{actions/videos => lib}/transcribe.ts (97%)
create mode 100644 packages/web-backend/README.md
create mode 100644 packages/web-backend/package.json
create mode 100644 packages/web-backend/src/Auth.ts
create mode 100644 packages/web-backend/src/Database.ts
create mode 100644 packages/web-backend/src/Folders/FoldersPolicy.ts
create mode 100644 packages/web-backend/src/Folders/FoldersRpcs.ts
create mode 100644 packages/web-backend/src/Folders/index.ts
create mode 100644 packages/web-backend/src/Rpcs.ts
create mode 100644 packages/web-backend/src/S3Buckets/S3BucketAccess.ts
create mode 100644 packages/web-backend/src/S3Buckets/S3BucketClientProvider.ts
create mode 100644 packages/web-backend/src/S3Buckets/S3BucketsRepo.ts
create mode 100644 packages/web-backend/src/S3Buckets/index.ts
create mode 100644 packages/web-backend/src/Videos/VideosPolicy.ts
create mode 100644 packages/web-backend/src/Videos/VideosRepo.ts
create mode 100644 packages/web-backend/src/Videos/VideosRpcs.ts
create mode 100644 packages/web-backend/src/Videos/index.ts
create mode 100644 packages/web-backend/src/index.ts
create mode 100644 packages/web-backend/tsconfig.json
create mode 100644 packages/web-domain/README.md
create mode 100644 packages/web-domain/package.json
create mode 100644 packages/web-domain/src/Authentication.ts
create mode 100644 packages/web-domain/src/Errors.ts
create mode 100644 packages/web-domain/src/Folder.ts
create mode 100644 packages/web-domain/src/Organisation.ts
create mode 100644 packages/web-domain/src/Policy.ts
create mode 100644 packages/web-domain/src/Rpcs.ts
create mode 100644 packages/web-domain/src/S3Bucket.ts
create mode 100644 packages/web-domain/src/Video.ts
create mode 100644 packages/web-domain/src/index.ts
create mode 100644 packages/web-domain/tsconfig.json
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5a2183488..16ce07063 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,6 +11,28 @@ concurrency:
cancel-in-progress: true
jobs:
+ changes:
+ name: Detect Changes
+ runs-on: ubuntu-latest
+ outputs:
+ rust: ${{ steps.filter.outputs.rust }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Check for changes
+ uses: dorny/paths-filter@v2
+ id: filter
+ with:
+ filters: |
+ rust:
+ - '.cargo/**'
+ - '.github/**'
+ - 'crates/**'
+ - 'desktop/src-tauri/**'
+ - 'Cargo.toml'
+ - 'Cargo.lock'
+
typecheck:
name: Typecheck
runs-on: ubuntu-latest
@@ -38,6 +60,8 @@ jobs:
clippy:
name: Clippy
runs-on: macos-latest
+ needs: changes
+ if: needs.changes.outputs.rust == 'true'
permissions:
contents: read
steps:
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index 11df6dd64..84857e803 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -54,7 +54,7 @@
"@ts-rest/core": "^3.52.1",
"@types/react-tooltip": "^4.2.4",
"cva": "npm:class-variance-authority@^0.7.0",
- "effect": "^3.7.2",
+ "effect": "^3.17.7",
"mp4box": "^0.5.2",
"posthog-js": "^1.215.3",
"solid-js": "^1.9.3",
diff --git a/apps/web/README.md b/apps/web/README.md
index 43049f3c9..869aace95 100644
--- a/apps/web/README.md
+++ b/apps/web/README.md
@@ -1,7 +1,4 @@
-# Cap Web App
+# `@cap/web`
-More details, as well as a contributor guide, will be posted soon.
-
-## Illustrations
-
-A big thank you to Popsy (https://popsy.co) for some of the illustrations used in Cap.
+Cap's NextJS web app for video sharing.
+Used for both self hosting and on [cap.so](https://cap.so).
diff --git a/apps/web/actions/folders/deleteFolder.ts b/apps/web/actions/folders/deleteFolder.ts
deleted file mode 100644
index aca5ec71b..000000000
--- a/apps/web/actions/folders/deleteFolder.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-"use server";
-
-import { db } from "@cap/database";
-import { folders, videos, spaceVideos } from "@cap/database/schema";
-import { eq, and } from "drizzle-orm";
-import { revalidatePath } from "next/cache";
-import { getFolderById } from "./getFolderById";
-
-export async function deleteFolder(folderId: string, spaceId?: string | null) {
- if (!folderId) throw new Error("Folder ID is required");
-
- // Get the folder to find its parent
- const folder = await getFolderById(folderId);
- const parentId = folder.parentId ?? null;
-
- // Recursively delete all child folders first
- const childFolders = await db()
- .select({ id: folders.id })
- .from(folders)
- .where(eq(folders.parentId, folderId));
- for (const child of childFolders) {
- await deleteFolder(child.id);
- }
-
- // Always update videos.folderId so videos move up to parent folder
- await db()
- .update(videos)
- .set({ folderId: parentId })
- .where(eq(videos.folderId, folderId));
-
- // If spaceId is provided, also update spaceVideos.folderId for consistency
- if (spaceId) {
- await db()
- .update(spaceVideos)
- .set({ folderId: parentId })
- .where(
- and(
- eq(spaceVideos.folderId, folderId),
- eq(spaceVideos.spaceId, spaceId)
- )
- );
- }
-
- // Delete the folder itself
- await db().delete(folders).where(eq(folders.id, folderId));
- if (spaceId) {
- revalidatePath(`/dashboard/spaces/${spaceId}`);
- } else {
- revalidatePath(`/dashboard/caps`);
- }
-}
diff --git a/apps/web/actions/folders/duplicateFolder.ts b/apps/web/actions/folders/duplicateFolder.ts
deleted file mode 100644
index faa43fd0a..000000000
--- a/apps/web/actions/folders/duplicateFolder.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-"use server";
-
-import { db } from "@cap/database";
-import { folders, videos, s3Buckets } from "@cap/database/schema";
-import { eq } from "drizzle-orm";
-import { nanoId } from "@cap/database/helpers";
-import { revalidatePath } from "next/cache";
-import { getFolderById } from "./getFolderById";
-
-export async function duplicateFolder(
- folderId: string,
- parentId?: string | null,
- spaceId?: string | null
-): Promise {
- if (!folderId) throw new Error("Folder ID is required");
-
- // Get the folder to duplicate
- const folder = await getFolderById(folderId);
- if (!folder) throw new Error("Folder not found");
-
- // Create the duplicated folder
- const newFolderId = nanoId();
- const now = new Date();
- const newFolder = {
- id: newFolderId,
- name: folder.name,
- color: folder.color,
- organizationId: folder.organizationId,
- createdById: folder.createdById,
- createdAt: now,
- updatedAt: now,
- parentId: parentId ?? null,
- spaceId: spaceId ?? null,
- };
- await db().insert(folders).values(newFolder);
-
- // Duplicate all videos in this folder
- const videosInFolder = await db()
- .select()
- .from(videos)
- .where(eq(videos.folderId, folderId));
- for (const video of videosInFolder) {
- const newVideoId = nanoId();
- await db()
- .insert(videos)
- .values({
- ...video,
- id: newVideoId,
- folderId: newFolderId,
- createdAt: now,
- updatedAt: now,
- });
-
- // --- S3 Asset Duplication ---
- // Copy all S3 objects from old video to new video
- try {
- const { createBucketProvider } = await import("@/utils/s3");
- let bucketProvider = null;
- let prefix: string | null = null;
- let newPrefix: string | null = null;
- if (video.bucket) {
- // Modern: use custom bucket
- const [bucketRow] = await db()
- .select()
- .from(s3Buckets)
- .where(eq(s3Buckets.id, video.bucket));
- if (bucketRow) {
- bucketProvider = await createBucketProvider(bucketRow);
- prefix = `${video.ownerId}/${video.id}/`;
- newPrefix = `${video.ownerId}/${newVideoId}/`;
- }
- } else if (video.awsBucket) {
- // Legacy: use global/default bucket
- bucketProvider = await createBucketProvider(); // No arg = default/global bucket
- prefix = `${video.ownerId}/${video.id}/`;
- newPrefix = `${video.ownerId}/${newVideoId}/`;
- }
- if (bucketProvider && prefix && newPrefix) {
- const objects = await bucketProvider.listObjects({ prefix });
- if (objects.Contents) {
- for (const obj of objects.Contents) {
- if (!obj.Key) continue;
- const newKey = obj.Key.replace(prefix, newPrefix);
- await bucketProvider.copyObject(
- `${bucketProvider.name}/${obj.Key}`,
- newKey
- );
- }
- }
- }
- } catch (err) {
- console.error("Failed to copy S3 assets for duplicated video", err);
- }
- }
-
- // Recursively duplicate all child folders
- const childFolders = await db()
- .select()
- .from(folders)
- .where(eq(folders.parentId, folderId));
- for (const child of childFolders) {
- await duplicateFolder(child.id, newFolderId);
- }
-
- revalidatePath(`/dashboard/caps`);
- return newFolderId;
-}
diff --git a/apps/web/actions/folders/getChildFolders.ts b/apps/web/actions/folders/getChildFolders.ts
deleted file mode 100644
index c82264346..000000000
--- a/apps/web/actions/folders/getChildFolders.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-"use server";
-
-import { db } from "@cap/database";
-import { folders } from "@cap/database/schema";
-import { eq, and } from "drizzle-orm";
-import { getCurrentUser } from "@cap/database/auth/session";
-import { sql } from "drizzle-orm/sql";
-import { revalidatePath } from "next/cache";
-
-export async function getChildFolders(folderId: string) {
- const user = await getCurrentUser();
- if (!user || !user.activeOrganizationId)
- throw new Error("Unauthorized or no active organization");
-
- const childFolders = await db()
- .select({
- id: folders.id,
- name: folders.name,
- color: folders.color,
- parentId: folders.parentId,
- organizationId: folders.organizationId,
- videoCount: sql`(
- SELECT COUNT(*) FROM videos WHERE videos.folderId = folders.id
- )`,
- })
- .from(folders)
- .where(
- and(
- eq(folders.parentId, folderId),
- eq(folders.organizationId, user.activeOrganizationId)
- )
- );
-
- revalidatePath(`/dashboard/folder/${folderId}`);
-
- return childFolders;
-}
diff --git a/apps/web/actions/folders/getFolderBreadcrumb.ts b/apps/web/actions/folders/getFolderBreadcrumb.ts
deleted file mode 100644
index 9f15646ca..000000000
--- a/apps/web/actions/folders/getFolderBreadcrumb.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-"use server";
-
-import { revalidatePath } from "next/cache";
-import { getFolderById } from "./getFolderById";
-
-export async function getFolderBreadcrumb(folderId: string) {
- const breadcrumb: Array<{
- id: string;
- name: string;
- color: "normal" | "blue" | "red" | "yellow";
- }> = [];
- let currentFolderId = folderId;
-
- while (currentFolderId) {
- const folder = await getFolderById(currentFolderId);
- if (!folder) break;
-
- breadcrumb.unshift({
- id: folder.id,
- name: folder.name,
- color: folder.color,
- });
-
- if (!folder.parentId) break;
- currentFolderId = folder.parentId;
- }
-
- revalidatePath(`/dashboard/folder/${folderId}`);
- return breadcrumb;
-}
diff --git a/apps/web/actions/folders/getFolderById.ts b/apps/web/actions/folders/getFolderById.ts
deleted file mode 100644
index e7827e31f..000000000
--- a/apps/web/actions/folders/getFolderById.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-"use server";
-
-import { db } from "@cap/database";
-import { folders } from "@cap/database/schema";
-import { eq } from "drizzle-orm";
-import { revalidatePath } from "next/cache";
-
-export async function getFolderById(folderId: string | undefined) {
- if (!folderId) throw new Error("Folder ID is required");
-
- const [folder] = await db()
- .select()
- .from(folders)
- .where(eq(folders.id, folderId));
-
- if (!folder) throw new Error("Folder not found");
-
- revalidatePath(`/dashboard/folder/${folderId}`);
- return folder;
-}
diff --git a/apps/web/actions/organization/get-organization.ts b/apps/web/actions/organization/get-organization-sso-data.ts
similarity index 79%
rename from apps/web/actions/organization/get-organization.ts
rename to apps/web/actions/organization/get-organization-sso-data.ts
index 69506fd45..1593d4529 100644
--- a/apps/web/actions/organization/get-organization.ts
+++ b/apps/web/actions/organization/get-organization-sso-data.ts
@@ -4,7 +4,7 @@ import { db } from "@cap/database";
import { organizations } from "@cap/database/schema";
import { eq } from "drizzle-orm";
-export async function getOrganization(organizationId: string) {
+export async function getOrganizationSSOData(organizationId: string) {
if (!organizationId) {
throw new Error("Organization ID is required");
}
@@ -18,7 +18,11 @@ export async function getOrganization(organizationId: string) {
.from(organizations)
.where(eq(organizations.id, organizationId));
- if (!organization || !organization.workosOrganizationId || !organization.workosConnectionId) {
+ if (
+ !organization ||
+ !organization.workosOrganizationId ||
+ !organization.workosConnectionId
+ ) {
throw new Error("Organization not found or SSO not configured");
}
@@ -27,4 +31,4 @@ export async function getOrganization(organizationId: string) {
connectionId: organization.workosConnectionId,
name: organization.name,
};
-}
\ No newline at end of file
+}
diff --git a/apps/web/actions/organization/update-space.ts b/apps/web/actions/organization/update-space.ts
index bd3692247..5139ffab7 100644
--- a/apps/web/actions/organization/update-space.ts
+++ b/apps/web/actions/organization/update-space.ts
@@ -3,7 +3,7 @@
import { db } from "@cap/database";
import { getCurrentUser } from "@cap/database/auth/session";
import { spaces, spaceMembers } from "@cap/database/schema";
-import { eq } from "drizzle-orm";
+import { eq, and } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { uploadSpaceIcon } from "./upload-space-icon";
import { v4 as uuidv4 } from "uuid";
@@ -19,6 +19,13 @@ export async function updateSpace(formData: FormData) {
const members = formData.getAll("members[]") as string[];
const iconFile = formData.get("icon") as File | null;
+ const [membership] = await db()
+ .select()
+ .from(spaceMembers)
+ .where(and(eq(spaceMembers.spaceId, id), eq(spaceMembers.userId, user.id)));
+
+ if (!membership) return { success: false, error: "Unauthorized" };
+
// Update space name
await db().update(spaces).set({ name }).where(eq(spaces.id, id));
diff --git a/apps/web/actions/videos/delete.ts b/apps/web/actions/videos/delete.ts
deleted file mode 100644
index 66f573af2..000000000
--- a/apps/web/actions/videos/delete.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-"use server";
-
-import { getCurrentUser } from "@cap/database/auth/session";
-import { s3Buckets, videos } from "@cap/database/schema";
-import { db } from "@cap/database";
-import { and, eq } from "drizzle-orm";
-import { createBucketProvider } from "@/utils/s3";
-
-export async function deleteVideo(videoId: string) {
- try {
- const user = await getCurrentUser();
- const userId = user?.id;
-
- if (!videoId || !userId) {
- return {
- success: false,
- message: "Missing required data",
- };
- }
-
- const query = await db()
- .select({ video: videos, bucket: s3Buckets })
- .from(videos)
- .leftJoin(s3Buckets, eq(videos.bucket, s3Buckets.id))
- .where(eq(videos.id, videoId));
-
- if (!query[0]) {
- return {
- success: false,
- message: "Video not found",
- };
- }
-
- await db()
- .delete(videos)
- .where(and(eq(videos.id, videoId), eq(videos.ownerId, userId)));
-
- const bucket = await createBucketProvider(query[0].bucket);
- const prefix = `${userId}/${videoId}/`;
-
- const listedObjects = await bucket.listObjects({
- prefix: prefix,
- });
-
- if (listedObjects.Contents?.length) {
- await bucket.deleteObjects(
- listedObjects.Contents.map((content) => ({
- Key: content.Key,
- }))
- );
- }
-
- return {
- success: true,
- message: "Video deleted successfully",
- };
- } catch (error) {
- console.error("Error deleting video:", error);
- return {
- success: false,
- message: "Failed to delete video",
- };
- }
-}
diff --git a/apps/web/actions/videos/duplicate.ts b/apps/web/actions/videos/duplicate.ts
deleted file mode 100644
index ba9670d36..000000000
--- a/apps/web/actions/videos/duplicate.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-"use server";
-
-import { db } from "@cap/database";
-import { videos, s3Buckets } from "@cap/database/schema";
-import { eq } from "drizzle-orm";
-import { revalidatePath } from "next/cache";
-
-import { nanoId } from "@cap/database/helpers";
-
-export async function duplicateVideo(videoId: string): Promise {
- if (!videoId) throw new Error("Video ID is required");
-
- // Get the video
- const [video] = await db()
- .select()
- .from(videos)
- .where(eq(videos.id, videoId));
- if (!video) throw new Error("Video not found");
-
- const newVideoId = nanoId();
- const now = new Date();
-
- // Insert the duplicated video
- await db()
- .insert(videos)
- .values({
- ...video,
- id: newVideoId,
- createdAt: now,
- updatedAt: now,
- });
-
- // Copy S3 assets
- try {
- const { createBucketProvider } = await import("@/utils/s3");
- let bucketProvider = null;
- let prefix: string | null = null;
- let newPrefix: string | null = null;
- if (video.bucket) {
- const [bucketRow] = await db()
- .select()
- .from(s3Buckets)
- .where(eq(s3Buckets.id, video.bucket));
- if (bucketRow) {
- bucketProvider = await createBucketProvider(bucketRow);
- prefix = `${video.ownerId}/${video.id}/`;
- newPrefix = `${video.ownerId}/${newVideoId}/`;
- }
- } else if (video.awsBucket) {
- bucketProvider = await createBucketProvider();
- prefix = `${video.ownerId}/${video.id}/`;
- newPrefix = `${video.ownerId}/${newVideoId}/`;
- }
- if (bucketProvider && prefix && newPrefix) {
- const objects = await bucketProvider.listObjects({ prefix });
- if (objects.Contents) {
- for (const obj of objects.Contents) {
- if (!obj.Key) continue;
- const newKey = obj.Key.replace(prefix, newPrefix);
- await bucketProvider.copyObject(
- `${bucketProvider.name}/${obj.Key}`,
- newKey
- );
- }
- }
- }
- } catch (err) {
- console.error("Failed to copy S3 assets for duplicated video", err);
- }
-
- revalidatePath("/dashboard/caps");
-
- return newVideoId;
-}
diff --git a/apps/web/actions/videos/get-status.ts b/apps/web/actions/videos/get-status.ts
index 5a036aa41..91ae12a46 100644
--- a/apps/web/actions/videos/get-status.ts
+++ b/apps/web/actions/videos/get-status.ts
@@ -6,7 +6,7 @@ import { videos, users } from "@cap/database/schema";
import { VideoMetadata } from "@cap/database/types";
import { eq } from "drizzle-orm";
import { generateAiMetadata } from "./generate-ai-metadata";
-import { transcribeVideo } from "./transcribe";
+import { transcribeVideo } from "../../lib/transcribe";
import { isAiGenerationEnabled } from "@/utils/flags";
const MAX_AI_PROCESSING_TIME = 10 * 60 * 1000;
@@ -21,7 +21,9 @@ export interface VideoStatusResult {
error?: string;
}
-export async function getVideoStatus(videoId: string): Promise {
+export async function getVideoStatus(
+ videoId: string
+): Promise {
const user = await getCurrentUser();
if (!user) {
@@ -41,12 +43,17 @@ export async function getVideoStatus(videoId: string): Promise {
- console.error(`[Get Status] Error starting transcription for video ${videoId}:`, error);
+ transcribeVideo(videoId, video.ownerId).catch((error) => {
+ console.error(
+ `[Get Status] Error starting transcription for video ${videoId}:`,
+ error
+ );
});
-
+
return {
transcriptionStatus: "PROCESSING",
aiProcessing: false,
@@ -56,7 +63,10 @@ export async function getVideoStatus(videoId: string): Promise MAX_AI_PROCESSING_TIME) {
- console.log(`[Get Status] AI processing appears stuck for video ${videoId} (${Math.round((currentTime - updatedAtTime) / 60000)} minutes), resetting flag`);
-
+ console.log(
+ `[Get Status] AI processing appears stuck for video ${videoId} (${Math.round(
+ (currentTime - updatedAtTime) / 60000
+ )} minutes), resetting flag`
+ );
+
await db()
.update(videos)
- .set({
+ .set({
metadata: {
...metadata,
aiProcessing: false,
- generationError: "AI processing timed out and was reset"
- }
+ generationError: "AI processing timed out and was reset",
+ },
})
.where(eq(videos.id, videoId));
-
- const updatedResult = await db().select().from(videos).where(eq(videos.id, videoId));
+
+ const updatedResult = await db()
+ .select()
+ .from(videos)
+ .where(eq(videos.id, videoId));
if (updatedResult.length > 0 && updatedResult[0]) {
const updatedVideo = updatedResult[0];
- const updatedMetadata = updatedVideo.metadata as VideoMetadata || {};
-
+ const updatedMetadata = (updatedVideo.metadata as VideoMetadata) || {};
+
return {
- transcriptionStatus: (updatedVideo.transcriptionStatus as "PROCESSING" | "COMPLETE" | "ERROR") || null,
+ transcriptionStatus:
+ (updatedVideo.transcriptionStatus as
+ | "PROCESSING"
+ | "COMPLETE"
+ | "ERROR") || null,
aiProcessing: false,
aiTitle: updatedMetadata.aiTitle || null,
summary: updatedMetadata.summary || null,
chapters: updatedMetadata.chapters || null,
generationError: updatedMetadata.generationError || null,
- error: "AI processing timed out and was reset"
+ error: "AI processing timed out and was reset",
};
}
}
}
if (
- video.transcriptionStatus === "COMPLETE" &&
- !metadata.aiProcessing &&
- !metadata.summary &&
+ video.transcriptionStatus === "COMPLETE" &&
+ !metadata.aiProcessing &&
+ !metadata.summary &&
!metadata.chapters &&
!metadata.generationError
) {
- console.log(`[Get Status] Transcription complete but no AI data, checking feature flag for video owner ${video.ownerId}`);
-
+ console.log(
+ `[Get Status] Transcription complete but no AI data, checking feature flag for video owner ${video.ownerId}`
+ );
+
const videoOwnerQuery = await db()
- .select({
- email: users.email,
- stripeSubscriptionStatus: users.stripeSubscriptionStatus
+ .select({
+ email: users.email,
+ stripeSubscriptionStatus: users.stripeSubscriptionStatus,
})
.from(users)
.where(eq(users.id, video.ownerId))
.limit(1);
- if (videoOwnerQuery.length > 0 && videoOwnerQuery[0] && (await isAiGenerationEnabled(videoOwnerQuery[0]))) {
- console.log(`[Get Status] Feature flag enabled, triggering AI generation for video ${videoId}`);
-
+ if (
+ videoOwnerQuery.length > 0 &&
+ videoOwnerQuery[0] &&
+ (await isAiGenerationEnabled(videoOwnerQuery[0]))
+ ) {
+ console.log(
+ `[Get Status] Feature flag enabled, triggering AI generation for video ${videoId}`
+ );
+
(async () => {
try {
- console.log(`[Get Status] Starting AI metadata generation for video ${videoId}`);
+ console.log(
+ `[Get Status] Starting AI metadata generation for video ${videoId}`
+ );
await generateAiMetadata(videoId, video.ownerId);
- console.log(`[Get Status] AI metadata generation completed for video ${videoId}`);
+ console.log(
+ `[Get Status] AI metadata generation completed for video ${videoId}`
+ );
} catch (error) {
- console.error(`[Get Status] Error generating AI metadata for video ${videoId}:`, error);
-
+ console.error(
+ `[Get Status] Error generating AI metadata for video ${videoId}:`,
+ error
+ );
+
try {
- const currentVideo = await db().select().from(videos).where(eq(videos.id, videoId));
+ const currentVideo = await db()
+ .select()
+ .from(videos)
+ .where(eq(videos.id, videoId));
if (currentVideo.length > 0 && currentVideo[0]) {
- const currentMetadata = (currentVideo[0].metadata as VideoMetadata) || {};
+ const currentMetadata =
+ (currentVideo[0].metadata as VideoMetadata) || {};
await db()
.update(videos)
- .set({
+ .set({
metadata: {
...currentMetadata,
aiProcessing: false,
- generationError: error instanceof Error ? error.message : String(error)
- }
+ generationError:
+ error instanceof Error ? error.message : String(error),
+ },
})
.where(eq(videos.id, videoId));
}
} catch (resetError) {
- console.error(`[Get Status] Failed to reset AI processing flag for video ${videoId}:`, resetError);
+ console.error(
+ `[Get Status] Failed to reset AI processing flag for video ${videoId}:`,
+ resetError
+ );
}
}
})();
-
+
return {
- transcriptionStatus: (video.transcriptionStatus as "PROCESSING" | "COMPLETE" | "ERROR") || null,
+ transcriptionStatus:
+ (video.transcriptionStatus as "PROCESSING" | "COMPLETE" | "ERROR") ||
+ null,
aiProcessing: true,
aiTitle: metadata.aiTitle || null,
summary: metadata.summary || null,
@@ -177,16 +223,20 @@ export async function getVideoStatus(videoId: string): Promise {
- const { refresh } = useRouter();
+ const router = useRouter();
const params = useSearchParams();
const page = Number(params.get("page")) || 1;
const { user } = useDashboardContext();
@@ -66,8 +70,7 @@ export const Caps = ({
const [openNewFolderDialog, setOpenNewFolderDialog] = useState(false);
const totalPages = Math.ceil(count / limit);
const previousCountRef = useRef(0);
- const [selectedCaps, setSelectedCaps] = useState([]);
- const [isDeleting, setIsDeleting] = useState(false);
+ const [selectedCaps, setSelectedCaps] = useState([]);
const [isDraggingCap, setIsDraggingCap] = useState(false);
const {
isUploading,
@@ -80,7 +83,7 @@ export const Caps = ({
const anyCapSelected = selectedCaps.length > 0;
const { data: analyticsData } = useQuery({
- queryKey: ['analytics', data.map(video => video.id)],
+ queryKey: ["analytics", data.map((video) => video.id)],
queryFn: async () => {
if (!dubApiKeyEnabled || data.length === 0) {
return {};
@@ -89,9 +92,9 @@ export const Caps = ({
const analyticsPromises = data.map(async (video) => {
try {
const response = await fetch(`/api/analytics?videoId=${video.id}`, {
- method: 'GET',
+ method: "GET",
headers: {
- 'Content-Type': 'application/json',
+ "Content-Type": "application/json",
},
});
@@ -101,7 +104,10 @@ export const Caps = ({
}
return { videoId: video.id, count: 0 };
} catch (error) {
- console.warn(`Failed to fetch analytics for video ${video.id}:`, error);
+ console.warn(
+ `Failed to fetch analytics for video ${video.id}:`,
+ error
+ );
return { videoId: video.id, count: 0 };
}
});
@@ -110,7 +116,7 @@ export const Caps = ({
const analyticsData: Record = {};
results.forEach((result) => {
- if (result.status === 'fulfilled' && result.value) {
+ if (result.status === "fulfilled" && result.value) {
analyticsData[result.value.videoId] = result.value.count;
}
});
@@ -142,7 +148,7 @@ export const Caps = ({
document.activeElement?.tagName || ""
)
) {
- deleteSelectedCaps();
+ deleteCaps.mutate(selectedCaps);
}
}
@@ -178,7 +184,7 @@ export const Caps = ({
};
}, []);
- const handleCapSelection = (capId: string) => {
+ const handleCapSelection = (capId: Video.VideoId) => {
setSelectedCaps((prev) => {
const newSelection = prev.includes(capId)
? prev.filter((id) => id !== capId)
@@ -190,85 +196,76 @@ export const Caps = ({
});
};
- const deleteSelectedCaps = async () => {
- if (selectedCaps.length === 0) return;
+ const deleteCaps = useEffectMutation({
+ mutationFn: Effect.fn(function* (ids: Video.VideoId[]) {
+ if (ids.length === 0) return;
- setIsDeleting(true);
+ const rpc = yield* Rpc;
- try {
- toast.promise(
- async () => {
- const results = await Promise.allSettled(
- selectedCaps.map((capId) => deleteVideo(capId))
- );
+ const fiber = yield* Effect.gen(function* () {
+ const results = yield* Effect.all(
+ ids.map((id) => rpc.VideoDelete(id).pipe(Effect.exit)),
+ { concurrency: 10 }
+ );
- const successCount = results.filter(
- (result) => result.status === "fulfilled" && result.value.success
- ).length;
+ const successCount = results.filter(Exit.isSuccess).length;
- const errorCount = selectedCaps.length - successCount;
+ const errorCount = ids.length - successCount;
- if (successCount > 0 && errorCount > 0) {
- return { success: successCount, error: errorCount };
- } else if (successCount > 0) {
- return { success: successCount };
- } else {
- throw new Error(
+ if (successCount > 0 && errorCount > 0) {
+ return { success: successCount, error: errorCount };
+ } else if (successCount > 0) {
+ return { success: successCount };
+ } else {
+ return yield* Effect.fail(
+ new Error(
`Failed to delete ${errorCount} cap${errorCount === 1 ? "" : "s"}`
- );
+ )
+ );
+ }
+ }).pipe(Effect.fork);
+
+ toast.promise(Effect.runPromise(fiber.await.pipe(Effect.flatten)), {
+ loading: `Deleting ${ids.length} cap${ids.length === 1 ? "" : "s"}...`,
+ success: (data) => {
+ if (data.error) {
+ return `Successfully deleted ${data.success} cap${
+ data.success === 1 ? "" : "s"
+ }, but failed to delete ${data.error} cap${
+ data.error === 1 ? "" : "s"
+ }`;
}
+ return `Successfully deleted ${data.success} cap${
+ data.success === 1 ? "" : "s"
+ }`;
},
- {
- loading: `Deleting ${selectedCaps.length} cap${selectedCaps.length === 1 ? "" : "s"
- }...`,
- success: (data) => {
- if (data.error) {
- return `Successfully deleted ${data.success} cap${data.success === 1 ? "" : "s"
- }, but failed to delete ${data.error} cap${data.error === 1 ? "" : "s"
- }`;
- }
- return `Successfully deleted ${data.success} cap${data.success === 1 ? "" : "s"
- }`;
- },
- error: (error) =>
- error.message || "An error occurred while deleting caps",
- }
- );
+ error: (error) =>
+ error.message || "An error occurred while deleting caps",
+ });
+ return yield* fiber.await.pipe(Effect.flatten);
+ }),
+ onSuccess: Effect.fn(function* () {
setSelectedCaps([]);
- refresh();
- } catch (error) {
- } finally {
- setIsDeleting(false);
- }
- };
+ router.refresh();
+ }),
+ });
- const deleteCap = async (capId: string) => {
- try {
- await deleteVideo(capId);
+ const deleteCap = useEffectMutation({
+ mutationFn: (id: Video.VideoId) => withRpc((r) => r.VideoDelete(id)),
+ onSuccess: Effect.fn(function* () {
toast.success("Cap deleted successfully");
- refresh();
- } catch (error) {
+ router.refresh();
+ }),
+ onError: Effect.fn(function* () {
toast.error("Failed to delete cap");
- }
- };
+ }),
+ });
- if (count === 0) {
- return ;
- }
+ if (count === 0) return ;
return (
-
- {isDraggingCap && (
-
-
-
-
-
Drag to a space to share or folder to move
-
-
-
- )}
+
{isUploading && (
-
+
)}
{data.map((cap) => (
{
if (selectedCaps.length > 0) {
- await deleteSelectedCaps();
+ await deleteCaps.mutateAsync(selectedCaps);
} else {
- await deleteCap(cap.id);
+ deleteCap.mutateAsync(cap.id);
}
}}
userId={user?.id}
@@ -351,13 +346,27 @@ export const Caps = ({
)}
-
deleteCaps.mutate(selectedCaps)}
+ isDeleting={deleteCaps.isPending}
/>
+ {isDraggingCap && (
+
+
+
+
+
+ Drag to a space to share or folder to move
+
+
+
+
+ )}
);
};
diff --git a/apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx b/apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx
index d7a146ed3..476ce315d 100644
--- a/apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx
+++ b/apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx
@@ -4,14 +4,21 @@ import { useDashboardContext } from "@/app/(org)/dashboard/Contexts";
import { Tooltip } from "@/components/Tooltip";
import { VideoThumbnail } from "@/components/VideoThumbnail";
import { VideoMetadata } from "@cap/database/types";
-import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@cap/ui";
+import {
+ Button,
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@cap/ui";
import {
faCheck,
faCopy,
- faEllipsis, faLock,
+ faEllipsis,
+ faLock,
faTrash,
faUnlock,
- faVideo
+ faVideo,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import clsx from "clsx";
@@ -24,13 +31,13 @@ import { SharingDialog } from "../SharingDialog";
import { CapCardAnalytics } from "./CapCardAnalytics";
import { CapCardButtons } from "./CapCardButtons";
import { CapCardContent } from "./CapCardContent";
-import { duplicateVideo } from "@/actions/videos/duplicate";
-
-
+import { EffectRuntime } from "@/lib/EffectRuntime";
+import { withRpc } from "@/lib/Rpcs";
+import { Video } from "@cap/web-domain";
export interface CapCardProps extends PropsWithChildren {
cap: {
- id: string;
+ id: Video.VideoId;
ownerId: string;
name: string;
createdAt: Date;
@@ -53,7 +60,7 @@ export interface CapCardProps extends PropsWithChildren {
hasPassword?: boolean;
};
analytics: number;
- onDelete?: () => Promise;
+ onDelete?: () => Promise;
userId?: string;
sharedCapCard?: boolean;
isSelected?: boolean;
@@ -91,12 +98,13 @@ export const CapCard = ({
const [copyPressed, setCopyPressed] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const [isDownloading, setIsDownloading] = useState(false);
- const router = useRouter();
const { isSubscribed, setUpgradeModalOpen } = useDashboardContext();
const [confirmOpen, setConfirmOpen] = useState(false);
const [removing, setRemoving] = useState(false);
+ const router = useRouter();
+
const handleDeleteClick = (e: React.MouseEvent) => {
e.stopPropagation();
setConfirmOpen(true);
@@ -129,18 +137,19 @@ export const CapCard = ({
// Helper function to create a drag preview element
const createDragPreview = (text: string): HTMLElement => {
// Create the element
- const element = document.createElement('div');
+ const element = document.createElement("div");
// Add text content
element.textContent = text;
// Apply Tailwind-like styles directly
- element.className = 'px-2 py-1.5 text-sm font-medium rounded-lg shadow-md text-gray-1 bg-gray-12';
+ element.className =
+ "px-2 py-1.5 text-sm font-medium rounded-lg shadow-md text-gray-1 bg-gray-12";
// Position off-screen
- element.style.position = 'absolute';
- element.style.top = '-9999px';
- element.style.left = '-9999px';
+ element.style.position = "absolute";
+ element.style.top = "-9999px";
+ element.style.left = "-9999px";
return element;
};
@@ -158,7 +167,7 @@ export const CapCard = ({
);
// Set drag effect to 'move' to avoid showing the + icon
- e.dataTransfer.effectAllowed = 'move';
+ e.dataTransfer.effectAllowed = "move";
// Set the drag image using the helper function
try {
@@ -169,7 +178,7 @@ export const CapCard = ({
// Clean up after a short delay
setTimeout(() => document.body.removeChild(dragPreview), 100);
} catch (error) {
- console.error('Error setting drag image:', error);
+ console.error("Error setting drag image:", error);
}
setIsDragging(true);
@@ -229,7 +238,6 @@ export const CapCard = ({
}
};
-
const handleCardClick = (e: React.MouseEvent) => {
if (anyCapSelected) {
e.preventDefault();
@@ -276,17 +284,14 @@ export const CapCard = ({
isSelected
? "!border-blue-10 border-px"
: anyCapSelected
- ? "border-blue-10 border-px hover:border-blue-10"
- : "hover:border-blue-10",
+ ? "border-blue-10 border-px hover:border-blue-10"
+ : "hover:border-blue-10",
isDragging && "opacity-50",
isOwner && !anyCapSelected && "cursor-grab active:cursor-grabbing"
)}
>
{anyCapSelected && !sharedCapCard && (
-
+
)}
{!sharedCapCard && (
@@ -317,37 +322,38 @@ export const CapCard = ({
onClick={(e) => {
e.stopPropagation();
}}
- className={clsx("!size-8 hover:bg-gray-5 hover:border-gray-7 rounded-full min-w-fit !p-0 delay-75",
+ className={clsx(
+ "!size-8 hover:bg-gray-5 hover:border-gray-7 rounded-full min-w-fit !p-0 delay-75",
isDropdownOpen ? "bg-gray-5 border-gray-7" : ""
)}
variant="white"
size="sm"
aria-label="More options"
>
-
+
-
+
{
try {
- await duplicateVideo(cap.id)
+ await EffectRuntime.runPromise(
+ withRpc((r) => r.VideoDuplicate(cap.id))
+ );
toast.success("Cap duplicated successfully");
+ router.refresh();
} catch (error) {
toast.error("Failed to duplicate cap");
}
}}
className="flex gap-2 items-center rounded-lg"
>
-
+
Duplicate
- {passwordProtected ? "Edit password" : "Add password"}
+
+ {passwordProtected ? "Edit password" : "Add password"}
+
{
@@ -431,7 +439,11 @@ export const CapCard = ({
>
{
+const FolderCard = ({
+ name,
+ color,
+ id,
+ parentId,
+ videoCount,
+ spaceId,
+}: FolderDataType) => {
+ const router = useRouter();
const { theme } = useTheme();
const [confirmDeleteFolderOpen, setConfirmDeleteFolderOpen] = useState(false);
- const [deleteFolderLoading, setDeleteFolderLoading] = useState(false);
const [isRenaming, setIsRenaming] = useState(false);
const [updateName, setUpdateName] = useState(name);
const nameRef = useRef(null);
@@ -38,7 +49,7 @@ const Folder = ({ name, color, id, parentId, videoCount, spaceId }: FolderDataTy
// Use a ref to track drag state to avoid re-renders during animation
const dragStateRef = useRef({
isDragging: false,
- isAnimating: false
+ isAnimating: false,
});
// Add a debounce timer ref to prevent animation stuttering
@@ -48,8 +59,8 @@ const Folder = ({ name, color, id, parentId, videoCount, spaceId }: FolderDataTy
theme === "dark" && color === "normal"
? "folder"
: color === "normal"
- ? "folder-dark"
- : `folder-${color}`;
+ ? "folder-dark"
+ : `folder-${color}`;
const { rive, RiveComponent: FolderRive } = useRive({
src: "/rive/dashboard.riv",
@@ -61,18 +72,19 @@ const Folder = ({ name, color, id, parentId, videoCount, spaceId }: FolderDataTy
}),
});
- const deleteFolderHandler = async () => {
- try {
- setDeleteFolderLoading(true);
- await deleteFolder(id, spaceId);
+ const deleteFolder = useEffectMutation({
+ mutationFn: (id: Folder.FolderId) => withRpc((r) => r.FolderDelete(id)),
+ onSuccess: Effect.fn(function* () {
+ router.refresh();
toast.success("Folder deleted successfully");
- } catch (error) {
+ }),
+ onError: Effect.fn(function* () {
toast.error("Failed to delete folder");
- } finally {
- setDeleteFolderLoading(false);
+ }),
+ onSettled: Effect.fn(function* () {
setConfirmDeleteFolderOpen(false);
- }
- };
+ }),
+ });
useEffect(() => {
if (isRenaming && nameRef.current) {
@@ -93,7 +105,11 @@ const Folder = ({ name, color, id, parentId, videoCount, spaceId }: FolderDataTy
try {
setIsMovingVideo(true);
- await moveVideoToFolder({ videoId: data.id, folderId: id, spaceId: spaceId ?? activeOrganization?.organization.id });
+ await moveVideoToFolder({
+ videoId: data.id,
+ folderId: id,
+ spaceId: spaceId ?? activeOrganization?.organization.id,
+ });
toast.success(`"${data.name}" moved to "${name}" folder`);
} catch (error) {
console.error("Error moving video to folder:", error);
@@ -155,16 +171,14 @@ const Folder = ({ name, color, id, parentId, videoCount, spaceId }: FolderDataTy
}
};
- document.addEventListener('dragend', handleDragEnd);
+ document.addEventListener("dragend", handleDragEnd);
return () => {
unregister();
- document.removeEventListener('dragend', handleDragEnd);
+ document.removeEventListener("dragend", handleDragEnd);
};
}, [id, name, rive, isDragOver]);
-
-
const updateFolderNameHandler = async () => {
try {
await updateFolder({ folderId: id, name: updateName });
@@ -197,7 +211,6 @@ const Folder = ({ name, color, id, parentId, videoCount, spaceId }: FolderDataTy
rive.stop();
rive.play("folder-open");
}
-
}
}
};
@@ -221,7 +234,6 @@ const Folder = ({ name, color, id, parentId, videoCount, spaceId }: FolderDataTy
rive.stop();
rive.play("folder-close");
}
-
}
};
@@ -261,11 +273,15 @@ const Folder = ({ name, color, id, parentId, videoCount, spaceId }: FolderDataTy
}
};
-
return (
-
+
{
@@ -311,15 +327,17 @@ const Folder = ({ name, color, id, parentId, videoCount, spaceId }: FolderDataTy
isMovingVideo && "opacity-70"
)}
>
-
+
-
{
- e.stopPropagation();
- }} className="flex flex-col justify-center h-10">
+
{
+ e.stopPropagation();
+ }}
+ className="flex flex-col justify-center h-10"
+ >
{isRenaming ? (
) : (
-
{
- e.stopPropagation()
- setIsRenaming(true)
- }} className="text-[15px] truncate text-gray-12 w-full max-w-[116px] m-0 p-0 h-[22px] leading-[22px] font-normal tracking-normal">{updateName}
+
{
+ e.stopPropagation();
+ setIsRenaming(true);
+ }}
+ className="text-[15px] truncate text-gray-12 w-full max-w-[116px] m-0 p-0 h-[22px] leading-[22px] font-normal tracking-normal"
+ >
+ {updateName}
+
)}
-
{`${videoCount} ${videoCount === 1 ? "video" : "videos"
- }`}
+
{`${videoCount} ${
+ videoCount === 1 ? "video" : "videos"
+ }`}
}
- onConfirm={deleteFolderHandler}
+ onConfirm={() => deleteFolder.mutate(id)}
onCancel={() => setConfirmDeleteFolderOpen(false)}
title="Delete Folder"
description={`Are you sure you want to delete the folder "${name}"? This action cannot be undone.`}
@@ -370,8 +394,8 @@ const Folder = ({ name, color, id, parentId, videoCount, spaceId }: FolderDataTy
nameRef={nameRef}
/>
-
+
);
};
-export default Folder;
+export default FolderCard;
diff --git a/apps/web/app/(org)/dashboard/caps/components/FoldersDropdown.tsx b/apps/web/app/(org)/dashboard/caps/components/FoldersDropdown.tsx
index 0fe123c2f..6c94da7ae 100644
--- a/apps/web/app/(org)/dashboard/caps/components/FoldersDropdown.tsx
+++ b/apps/web/app/(org)/dashboard/caps/components/FoldersDropdown.tsx
@@ -1,12 +1,16 @@
-"use client";
-
-import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@cap/ui";
-import { faCopy, faEllipsis, faPencil, faTrash } from "@fortawesome/free-solid-svg-icons";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@cap/ui";
+import {
+ faEllipsis,
+ faPencil,
+ faTrash,
+} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { RefObject } from "react";
-import { toast } from "sonner";
-import { duplicateFolder } from "@/actions/folders/duplicateFolder";
-import { useDashboardContext } from "../../Contexts";
interface FoldersDropdownProps {
id: string;
@@ -17,13 +21,10 @@ interface FoldersDropdownProps {
}
export const FoldersDropdown = ({
- id,
- parentId,
setIsRenaming,
setConfirmDeleteFolderOpen,
nameRef,
}: FoldersDropdownProps) => {
- const { activeSpace } = useDashboardContext()
return (
e.stopPropagation()}
@@ -33,10 +34,13 @@ export const FoldersDropdown = ({
-
+
@@ -51,31 +55,35 @@ export const FoldersDropdown = ({
label: "Rename",
icon: faPencil,
onClick: () => {
- setIsRenaming(true)
+ setIsRenaming(true);
setTimeout(() => {
- nameRef.current?.focus()
- nameRef.current?.select()
- }, 0)
- }
+ nameRef.current?.focus();
+ nameRef.current?.select();
+ }, 0);
+ },
},
// Only show Duplicate if there is NO active space
- ...(!activeSpace ? [{
- label: "Duplicate",
- icon: faCopy,
- onClick: async () => {
- try {
- await duplicateFolder(id, parentId);
- toast.success("Folder duplicated successfully");
- } catch (error) {
- toast.error("Failed to duplicate folder");
- }
- }
- }] : []),
+ // ...(!activeSpace
+ // ? [
+ // {
+ // label: "Duplicate",
+ // icon: faCopy,
+ // onClick: async () => {
+ // try {
+ // await duplicateFolder(id, parentId);
+ // toast.success("Folder duplicated successfully");
+ // } catch (error) {
+ // toast.error("Failed to duplicate folder");
+ // }
+ // },
+ // },
+ // ]
+ // : []),
{
label: "Delete",
icon: faTrash,
- onClick: () => setConfirmDeleteFolderOpen(true)
- }
+ onClick: () => setConfirmDeleteFolderOpen(true),
+ },
];
return items.map((item) => (
-
+
{item.label}
));
diff --git a/apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx b/apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx
index 1e86b4e9d..89aace060 100644
--- a/apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx
+++ b/apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx
@@ -9,12 +9,13 @@ import { AnimatePresence, motion } from "framer-motion";
interface SelectedCapsBarProps {
selectedCaps: string[];
- setSelectedCaps: (caps: string[]) => void;
+ setSelectedCaps: (caps: Video.VideoId[]) => void;
deleteSelectedCaps: () => void;
isDeleting: boolean;
}
import { useState } from "react";
+import { Video } from "@cap/web-domain";
export const SelectedCapsBar = ({
selectedCaps,
@@ -82,7 +83,11 @@ export const SelectedCapsBar = ({
open={confirmOpen}
icon={ }
title="Delete selected Caps"
- description={`Are you sure you want to delete ${selectedCaps.length} cap${selectedCaps.length === 1 ? '' : 's'}? This action cannot be undone.`}
+ description={`Are you sure you want to delete ${
+ selectedCaps.length
+ } cap${
+ selectedCaps.length === 1 ? "" : "s"
+ }? This action cannot be undone.`}
confirmLabel="Delete"
cancelLabel="Cancel"
confirmVariant="dark"
diff --git a/apps/web/app/(org)/dashboard/caps/page.tsx b/apps/web/app/(org)/dashboard/caps/page.tsx
index 4c15eef8f..a353292c9 100644
--- a/apps/web/app/(org)/dashboard/caps/page.tsx
+++ b/apps/web/app/(org)/dashboard/caps/page.tsx
@@ -2,15 +2,20 @@ import { db } from "@cap/database";
import { getCurrentUser } from "@cap/database/auth/session";
import {
comments,
- folders, organizations,
- sharedVideos, spaceVideos, spaces, users,
- videos
+ folders,
+ organizations,
+ sharedVideos,
+ spaceVideos,
+ spaces,
+ users,
+ videos,
} from "@cap/database/schema";
-import { and, count, desc, eq, isNull, sql } from "drizzle-orm";
+import { and, count, desc, eq, inArray, isNull, sql } from "drizzle-orm";
import { Metadata } from "next";
import { redirect } from "next/navigation";
import { Caps } from "./Caps";
import { serverEnv } from "@cap/env";
+import { Video } from "@cap/web-domain";
export const metadata: Metadata = {
title: "My Caps — Cap",
@@ -32,7 +37,7 @@ async function getSharedSpacesForVideos(videoIds: string[]) {
.from(spaceVideos)
.innerJoin(spaces, eq(spaceVideos.spaceId, spaces.id))
.innerJoin(organizations, eq(spaces.organizationId, organizations.id))
- .where(sql`${spaceVideos.videoId} IN (${sql.join(videoIds.map(id => sql`${id}`), sql`, `)})`);
+ .where(inArray(spaceVideos.videoId, videoIds));
// Fetch organization-level sharing
const orgSharing = await db()
@@ -45,19 +50,22 @@ async function getSharedSpacesForVideos(videoIds: string[]) {
})
.from(sharedVideos)
.innerJoin(organizations, eq(sharedVideos.organizationId, organizations.id))
- .where(sql`${sharedVideos.videoId} IN (${sql.join(videoIds.map(id => sql`${id}`), sql`, `)})`);
+ .where(inArray(sharedVideos.videoId, videoIds));
// Combine and group by videoId
- const sharedSpacesMap: Record> = {};
+ const sharedSpacesMap: Record<
+ string,
+ Array<{
+ id: string;
+ name: string;
+ organizationId: string;
+ iconUrl: string;
+ isOrg: boolean;
+ }>
+ > = {};
// Add space-level sharing
- spaceSharing.forEach(space => {
+ spaceSharing.forEach((space) => {
if (!sharedSpacesMap[space.videoId]) {
sharedSpacesMap[space.videoId] = [];
}
@@ -65,13 +73,13 @@ async function getSharedSpacesForVideos(videoIds: string[]) {
id: space.id,
name: space.name,
organizationId: space.organizationId,
- iconUrl: space.iconUrl || '',
+ iconUrl: space.iconUrl || "",
isOrg: false,
});
});
// Add organization-level sharing
- orgSharing.forEach(org => {
+ orgSharing.forEach((org) => {
if (!sharedSpacesMap[org.videoId]) {
sharedSpacesMap[org.videoId] = [];
}
@@ -79,7 +87,7 @@ async function getSharedSpacesForVideos(videoIds: string[]) {
id: org.id,
name: org.name,
organizationId: org.organizationId,
- iconUrl: org.iconUrl || '',
+ iconUrl: org.iconUrl || "",
isOrg: true,
});
});
@@ -160,7 +168,6 @@ export default async function CapsPage({
JSON_ARRAY()
)
`,
-
ownerName: users.name,
effectiveDate: sql`
COALESCE(
@@ -213,7 +220,7 @@ export default async function CapsPage({
);
// Fetch shared spaces data for all videos
- const videoIds = videoData.map(video => video.id);
+ const videoIds = videoData.map((video) => video.id);
const sharedSpacesMap = await getSharedSpacesForVideos(videoIds);
const processedVideoData = videoData.map((video) => {
@@ -221,9 +228,12 @@ export default async function CapsPage({
return {
...videoWithoutEffectiveDate,
+ id: Video.VideoId.make(video.id),
foldersData,
sharedOrganizations: Array.isArray(video.sharedOrganizations)
- ? video.sharedOrganizations.filter((organization) => organization.id !== null)
+ ? video.sharedOrganizations.filter(
+ (organization) => organization.id !== null
+ )
: [],
sharedSpaces: Array.isArray(sharedSpacesMap[video.id])
? sharedSpacesMap[video.id]
@@ -231,9 +241,9 @@ export default async function CapsPage({
ownerName: video.ownerName ?? "",
metadata: video.metadata as
| {
- customCreatedAt?: string;
- [key: string]: any;
- }
+ customCreatedAt?: string;
+ [key: string]: any;
+ }
| undefined,
hasPassword: video.hasPassword === 1,
};
diff --git a/apps/web/app/(org)/dashboard/folder/[id]/components/FolderVideosSection.tsx b/apps/web/app/(org)/dashboard/folder/[id]/components/FolderVideosSection.tsx
index e4ab8d2d5..c1139cb8c 100644
--- a/apps/web/app/(org)/dashboard/folder/[id]/components/FolderVideosSection.tsx
+++ b/apps/web/app/(org)/dashboard/folder/[id]/components/FolderVideosSection.tsx
@@ -5,13 +5,16 @@ import { ClientCapCard } from "./index";
import { useRouter } from "next/navigation";
import { useState, useRef } from "react";
import { toast } from "sonner";
-import { deleteVideo } from "@/actions/videos/delete";
import { SelectedCapsBar } from "../../../caps/components/SelectedCapsBar";
import { useUploadingContext } from "../../../caps/UploadingContext";
import { type VideoData } from "../../../caps/Caps";
import { useDashboardContext } from "@/app/(org)/dashboard/Contexts";
import { SharedCapCard } from "../../../spaces/[spaceId]/components/SharedCapCard";
import { useSuspenseQuery } from "@tanstack/react-query";
+import { useEffectMutation } from "@/lib/EffectRuntime";
+import { Effect, Exit } from "effect";
+import { Video } from "@cap/web-domain";
+import { Rpc } from "@/lib/Rpcs";
interface FolderVideosSectionProps {
initialVideos: VideoData;
@@ -30,64 +33,67 @@ export default function FolderVideosSection({
const { isUploading } = useUploadingContext();
const { activeOrganization } = useDashboardContext();
- const [selectedCaps, setSelectedCaps] = useState([]);
+ const [selectedCaps, setSelectedCaps] = useState([]);
const previousCountRef = useRef(0);
- const [isDeleting, setIsDeleting] = useState(false);
- const deleteSelectedCaps = async () => {
- if (selectedCaps.length === 0) return;
+ const deleteCaps = useEffectMutation({
+ mutationFn: Effect.fn(function* (ids: Video.VideoId[]) {
+ if (ids.length === 0) return;
- setIsDeleting(true);
+ const rpc = yield* Rpc;
- try {
- toast.promise(
- async () => {
- const results = await Promise.allSettled(
- selectedCaps.map((capId) => deleteVideo(capId))
- );
+ const fiber = yield* Effect.gen(function* () {
+ const results = yield* Effect.all(
+ ids.map((id) => rpc.VideoDelete(id).pipe(Effect.exit)),
+ { concurrency: 10 }
+ );
- const successCount = results.filter(
- (result) => result.status === "fulfilled" && result.value.success
- ).length;
+ const successCount = results.filter(Exit.isSuccess).length;
- const errorCount = selectedCaps.length - successCount;
+ const errorCount = ids.length - successCount;
- if (successCount > 0 && errorCount > 0) {
- return { success: successCount, error: errorCount };
- } else if (successCount > 0) {
- return { success: successCount };
- } else {
- throw new Error(
+ if (successCount > 0 && errorCount > 0) {
+ return { success: successCount, error: errorCount };
+ } else if (successCount > 0) {
+ return { success: successCount };
+ } else {
+ return yield* Effect.fail(
+ new Error(
`Failed to delete ${errorCount} cap${errorCount === 1 ? "" : "s"}`
- );
+ )
+ );
+ }
+ }).pipe(Effect.fork);
+
+ toast.promise(Effect.runPromise(fiber.await.pipe(Effect.flatten)), {
+ loading: `Deleting ${selectedCaps.length} cap${
+ selectedCaps.length === 1 ? "" : "s"
+ }...`,
+ success: (data) => {
+ if (data.error) {
+ return `Successfully deleted ${data.success} cap${
+ data.success === 1 ? "" : "s"
+ }, but failed to delete ${data.error} cap${
+ data.error === 1 ? "" : "s"
+ }`;
}
+ return `Successfully deleted ${data.success} cap${
+ data.success === 1 ? "" : "s"
+ }`;
},
- {
- loading: `Deleting ${selectedCaps.length} cap${selectedCaps.length === 1 ? "" : "s"
- }...`,
- success: (data) => {
- if (data.error) {
- return `Successfully deleted ${data.success} cap${data.success === 1 ? "" : "s"
- }, but failed to delete ${data.error} cap${data.error === 1 ? "" : "s"
- }`;
- }
- return `Successfully deleted ${data.success} cap${data.success === 1 ? "" : "s"
- }`;
- },
- error: (error) =>
- error.message || "An error occurred while deleting caps",
- }
- );
+ error: (error) =>
+ error.message || "An error occurred while deleting caps",
+ });
+ return yield* fiber.await.pipe(Effect.flatten);
+ }),
+ onSuccess: Effect.fn(function* () {
setSelectedCaps([]);
router.refresh();
- } catch (error) {
- } finally {
- setIsDeleting(false);
- }
- };
+ }),
+ });
- const handleCapSelection = (capId: string) => {
+ const handleCapSelection = (capId: Video.VideoId) => {
setSelectedCaps((prev) => {
const newSelection = prev.includes(capId)
? prev.filter((id) => id !== capId)
@@ -99,47 +105,50 @@ export default function FolderVideosSection({
});
};
- const { data: analyticsData, isLoading: isLoadingAnalytics } = useSuspenseQuery({
- queryKey: ['analytics', initialVideos.map(video => video.id)],
- queryFn: async () => {
- if (!dubApiKeyEnabled || initialVideos.length === 0) {
- return {};
- }
-
- const analyticsPromises = initialVideos.map(async (video) => {
- try {
- const response = await fetch(`/api/analytics?videoId=${video.id}`, {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- },
- });
-
- if (response.ok) {
- const responseData = await response.json();
- return { videoId: video.id, count: responseData.count || 0 };
- }
- return { videoId: video.id, count: 0 };
- } catch (error) {
- console.warn(`Failed to fetch analytics for video ${video.id}:`, error);
- return { videoId: video.id, count: 0 };
+ const { data: analyticsData, isLoading: isLoadingAnalytics } =
+ useSuspenseQuery({
+ queryKey: ["analytics", initialVideos.map((video) => video.id)],
+ queryFn: async () => {
+ if (!dubApiKeyEnabled || initialVideos.length === 0) {
+ return {};
}
- });
- const results = await Promise.allSettled(analyticsPromises);
- const analyticsData: Record = {};
+ const analyticsPromises = initialVideos.map(async (video) => {
+ try {
+ const response = await fetch(`/api/analytics?videoId=${video.id}`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+
+ if (response.ok) {
+ const responseData = await response.json();
+ return { videoId: video.id, count: responseData.count || 0 };
+ }
+ return { videoId: video.id, count: 0 };
+ } catch (error) {
+ console.warn(
+ `Failed to fetch analytics for video ${video.id}:`,
+ error
+ );
+ return { videoId: video.id, count: 0 };
+ }
+ });
+ const results = await Promise.allSettled(analyticsPromises);
+ const analyticsData: Record = {};
- results.forEach((result) => {
- if (result.status === 'fulfilled' && result.value) {
- analyticsData[result.value.videoId] = result.value.count;
- }
- });
- return analyticsData;
- },
- staleTime: 30000, // 30 seconds
- refetchOnWindowFocus: false,
- });
+ results.forEach((result) => {
+ if (result.status === "fulfilled" && result.value) {
+ analyticsData[result.value.videoId] = result.value.count;
+ }
+ });
+ return analyticsData;
+ },
+ staleTime: 30000, // 30 seconds
+ refetchOnWindowFocus: false,
+ });
const analytics = analyticsData || {};
@@ -160,41 +169,41 @@ export default function FolderVideosSection({
)}
- {cardType === "shared" ? (
- initialVideos.map((video) => (
-
- ))
- ) : (
- initialVideos.map((video) => (
- 0}
- isDeleting={isDeleting}
- onSelectToggle={() => handleCapSelection(video.id)}
- onDelete={deleteSelectedCaps}
- />
- ))
- )}
+ {cardType === "shared"
+ ? initialVideos.map((video) => (
+
+ ))
+ : initialVideos.map((video) => (
+ 0}
+ isDeleting={deleteCaps.isPending}
+ onSelectToggle={() => handleCapSelection(video.id)}
+ onDelete={() => deleteCaps.mutateAsync(selectedCaps)}
+ />
+ ))}
>
)}
deleteCaps.mutateAsync(selectedCaps)}
+ isDeleting={deleteCaps.isPending}
/>
>
);
diff --git a/apps/web/app/(org)/dashboard/folder/[id]/page.tsx b/apps/web/app/(org)/dashboard/folder/[id]/page.tsx
index adebf916c..2eec5740f 100644
--- a/apps/web/app/(org)/dashboard/folder/[id]/page.tsx
+++ b/apps/web/app/(org)/dashboard/folder/[id]/page.tsx
@@ -1,14 +1,20 @@
-import { ClientMyCapsLink, NewSubfolderButton, BreadcrumbItem } from "./components";
-import Folder from "../../caps/components/Folder";
-import { getFolderBreadcrumb } from "@/actions/folders/getFolderBreadcrumb";
-import { getChildFolders } from "@/actions/folders/getChildFolders";
-import { getVideosByFolderId } from "@/actions/folders/getVideosByFolderId";
+import {
+ ClientMyCapsLink,
+ NewSubfolderButton,
+ BreadcrumbItem,
+} from "./components";
+import FolderCard from "../../caps/components/Folder";
import { serverEnv } from "@cap/env";
import { UploadCapButtonWithFolder } from "./components/UploadCapButtonWithFolder";
import FolderVideosSection from "./components/FolderVideosSection";
+import {
+ getChildFolders,
+ getFolderBreadcrumb,
+ getVideosByFolderId,
+} from "@/lib/folder";
+import { Folder } from "@cap/web-domain";
-
-const FolderPage = async ({ params }: { params: { id: string } }) => {
+const FolderPage = async ({ params }: { params: { id: Folder.FolderId } }) => {
const [childFolders, breadcrumb, videosData] = await Promise.all([
getChildFolders(params.id),
getFolderBreadcrumb(params.id),
@@ -16,7 +22,6 @@ const FolderPage = async ({ params }: { params: { id: string } }) => {
]);
return (
-
@@ -46,7 +51,7 @@ const FolderPage = async ({ params }: { params: { id: string } }) => {
Subfolders
{childFolders.map((folder) => (
- {
dubApiKeyEnabled={!!serverEnv().DUB_API_KEY}
/>
-
);
};
diff --git a/apps/web/app/(org)/dashboard/spaces/[spaceId]/SharedCaps.tsx b/apps/web/app/(org)/dashboard/spaces/[spaceId]/SharedCaps.tsx
index 47c364bd1..2087e4052 100644
--- a/apps/web/app/(org)/dashboard/spaces/[spaceId]/SharedCaps.tsx
+++ b/apps/web/app/(org)/dashboard/spaces/[spaceId]/SharedCaps.tsx
@@ -22,9 +22,10 @@ import { Button } from "@cap/ui";
import { faFolderPlus, faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import Folder from "../../caps/components/Folder";
import { useSuspenseQuery } from "@tanstack/react-query";
+import { Video } from "@cap/web-domain";
type SharedVideoData = {
- id: string;
+ id: Video.VideoId;
ownerId: string;
name: string;
createdAt: Date;
@@ -92,7 +93,7 @@ export const SharedCaps = ({
const organizationMemberCount = organizationMembers?.length || 0;
const { data: analyticsData } = useSuspenseQuery({
- queryKey: ['analytics', data.map(video => video.id)],
+ queryKey: ["analytics", data.map((video) => video.id)],
queryFn: async () => {
if (!dubApiKeyEnabled || data.length === 0) {
return {};
@@ -101,9 +102,9 @@ export const SharedCaps = ({
const analyticsPromises = data.map(async (video) => {
try {
const response = await fetch(`/api/analytics?videoId=${video.id}`, {
- method: 'GET',
+ method: "GET",
headers: {
- 'Content-Type': 'application/json',
+ "Content-Type": "application/json",
},
});
@@ -113,7 +114,10 @@ export const SharedCaps = ({
}
return { videoId: video.id, count: 0 };
} catch (error) {
- console.warn(`Failed to fetch analytics for video ${video.id}:`, error);
+ console.warn(
+ `Failed to fetch analytics for video ${video.id}:`,
+ error
+ );
return { videoId: video.id, count: 0 };
}
});
@@ -122,7 +126,7 @@ export const SharedCaps = ({
const analyticsData: Record
= {};
results.forEach((result) => {
- if (result.status === 'fulfilled' && result.value) {
+ if (result.status === "fulfilled" && result.value) {
analyticsData[result.value.videoId] = result.value.count;
}
});
@@ -205,7 +209,9 @@ export const SharedCaps = ({
icon={faInfoCircle}
/>
- {isDraggingCap.isOwner ? " Drag to a space to share or folder to move" : "Only the video owner can drag and move the video"}
+ {isDraggingCap.isOwner
+ ? " Drag to a space to share or folder to move"
+ : "Only the video owner can drag and move the video"}
@@ -288,7 +294,9 @@ export const SharedCaps = ({
organizationName={activeOrganization?.organization.name || ""}
spaceName={spaceData?.name || ""}
userId={currentUserId}
- onDragStart={() => setIsDraggingCap({ isOwner, isDragging: true })}
+ onDragStart={() =>
+ setIsDraggingCap({ isOwner, isDragging: true })
+ }
onDragEnd={() => setIsDraggingCap({ isOwner, isDragging: false })}
/>
);
diff --git a/apps/web/app/(org)/dashboard/spaces/[spaceId]/components/SharedCapCard.tsx b/apps/web/app/(org)/dashboard/spaces/[spaceId]/components/SharedCapCard.tsx
index 6d6c3379d..41d567804 100644
--- a/apps/web/app/(org)/dashboard/spaces/[spaceId]/components/SharedCapCard.tsx
+++ b/apps/web/app/(org)/dashboard/spaces/[spaceId]/components/SharedCapCard.tsx
@@ -2,10 +2,11 @@ import { VideoMetadata } from "@cap/database/types";
import { faBuilding, faUser } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CapCard } from "../../../caps/components/CapCard/CapCard";
+import { Video } from "@cap/web-domain";
interface SharedCapCardProps {
cap: {
- id: string;
+ id: Video.VideoId;
ownerId: string;
name: string;
createdAt: Date;
@@ -40,10 +41,7 @@ export const SharedCapCard: React.FC = ({
const isOwner = userId === cap.ownerId;
return (
-
+
{
+ const user = await getCurrentUser();
+ if (!user) return;
-const FolderPage = async ({ params }: { params: { spaceId: string; folderId: string } }) => {
const [childFolders, breadcrumb, videosData] = await Promise.all([
getChildFolders(params.folderId),
getFolderBreadcrumb(params.folderId),
getVideosByFolderId(params.folderId),
]);
- const user = await getCurrentUser();
const userId = user?.id as string;
return (
@@ -43,7 +56,7 @@ const FolderPage = async ({ params }: { params: { spaceId: string; folderId: str
Subfolders
{childFolders.map((folder) => (
-
{
const { effectiveDate, ...videoWithoutEffectiveDate } = video;
return {
...videoWithoutEffectiveDate,
+ id: Video.VideoId.make(video.id),
ownerName: video.ownerName ?? null,
metadata: video.metadata as
- | { customCreatedAt?: string;[key: string]: any }
+ | { customCreatedAt?: string; [key: string]: any }
| undefined,
};
});
diff --git a/apps/web/app/(org)/login/form.tsx b/apps/web/app/(org)/login/form.tsx
index 6fb6b2f8e..2d172e820 100644
--- a/apps/web/app/(org)/login/form.tsx
+++ b/apps/web/app/(org)/login/form.tsx
@@ -1,6 +1,6 @@
"use client";
-import { getOrganization } from "@/actions/organization/get-organization";
+import { getOrganizationSSOData } from "@/actions/organization/get-organization-sso-data";
import { trackEvent } from "@/app/utils/analytics";
import { NODE_ENV } from "@cap/env";
import { Button, Input, LogoBadge } from "@cap/ui";
@@ -116,7 +116,7 @@ export function LoginForm() {
}
try {
- const data = await getOrganization(organizationId);
+ const data = await getOrganizationSSOData(organizationId);
setOrganizationName(data.name);
signIn("workos", undefined, {
@@ -134,7 +134,7 @@ export function LoginForm() {
layout
transition={{
layout: { duration: 0.3, ease: "easeInOut" },
- height: { duration: 0.3, ease: "easeInOut" }
+ height: { duration: 0.3, ease: "easeInOut" },
}}
className="overflow-hidden relative w-[calc(100%-5%)] p-[28px] max-w-[432px] bg-gray-3 border border-gray-5 rounded-2xl"
>
@@ -151,17 +151,29 @@ export function LoginForm() {
className="absolute overflow-hidden top-5 rounded-full left-5 z-20 hover:bg-gray-1 gap-2 items-center py-1.5 px-3 text-gray-12 bg-transparent border border-gray-4 transition-colors duration-300 cursor-pointer"
>
- Back
+
+ Back
+
-
+
-
- Sign in to Cap
-
+
+
+ Sign in to Cap
+
+
Beautiful screen recordings, owned by you.
@@ -175,10 +187,7 @@ export function LoginForm() {
>
}
>
-
+
{showOrgInput ? (
@@ -211,9 +224,21 @@ export function LoginForm() {
key="email"
layout
initial={{ opacity: 0, y: 10 }}
- animate={{ opacity: 1, y: 0, transition: { duration: 0.1 } }}
- exit={{ opacity: 0, y: -10, transition: { duration: 0.15 } }}
- transition={{ duration: 0.2, ease: "easeInOut", opacity: { delay: 0.05 } }}
+ animate={{
+ opacity: 1,
+ y: 0,
+ transition: { duration: 0.1 },
+ }}
+ exit={{
+ opacity: 0,
+ y: -10,
+ transition: { duration: 0.15 },
+ }}
+ transition={{
+ duration: 0.2,
+ ease: "easeInOut",
+ opacity: { delay: 0.05 },
+ }}
onSubmit={async (e) => {
e.preventDefault();
if (!email) return;
@@ -226,7 +251,9 @@ export function LoginForm() {
signIn("email", {
email,
redirect: false,
- ...(next && next.length > 0 ? { callbackUrl: next } : {}),
+ ...(next && next.length > 0
+ ? { callbackUrl: next }
+ : {}),
})
.then((res) => {
setLoading(false);
@@ -262,7 +289,9 @@ export function LoginForm() {
+ layout="position"
+ className="pt-3 text-xs text-center text-gray-9"
+ >
By typing your email and clicking continue, you acknowledge that
you have both read and agree to Cap's{" "}
{
return (
-
+
{organizationName && (
-
- Signing in to: {organizationName}
-
+ Signing in to: {organizationName}
)}
@@ -398,7 +429,10 @@ const NormalLogin = ({
OR
-
+
{!oauthError && (
<>
{
- const { videoId, videoType, thumbnail, fileType } = c.req.valid("query");
- const user = c.get("user");
-
- const query = await db()
- .select({ video: videos, bucket: s3Buckets })
- .from(videos)
- .leftJoin(s3Buckets, eq(videos.bucket, s3Buckets.id))
- .where(eq(videos.id, videoId));
-
- if (!query[0])
- return c.json(
- JSON.stringify({ error: true, message: "Video does not exist" }),
- 404
+const GetPlaylistParams = Schema.Struct({
+ videoId: Video.VideoId,
+ videoType: Schema.Literal("video", "audio", "master", "mp4"),
+ thumbnail: Schema.OptionFromUndefinedOr(Schema.String),
+ fileType: Schema.OptionFromUndefinedOr(Schema.String),
+});
+
+class Api extends HttpApi.make("CapWebApi").add(
+ HttpApiGroup.make("root").add(
+ HttpApiEndpoint.get("getVideoSrc")`/api/playlist`
+ .setUrlParams(GetPlaylistParams)
+ .addError(HttpApiError.Forbidden)
+ .addError(HttpApiError.BadRequest)
+ .addError(HttpApiError.Unauthorized)
+ .addError(HttpApiError.InternalServerError)
+ .addError(HttpApiError.NotFound)
+ )
+) {}
+
+const ApiLive = HttpApiBuilder.api(Api).pipe(
+ Layer.provide(
+ HttpApiBuilder.group(Api, "root", (handlers) =>
+ Effect.gen(function* () {
+ const s3Buckets = yield* S3Buckets;
+ const videos = yield* Videos;
+
+ return handlers.handle("getVideoSrc", ({ urlParams }) =>
+ Effect.gen(function* () {
+ const [video] = yield* videos.getById(urlParams.videoId).pipe(
+ Effect.flatten,
+ Effect.catchTag(
+ "NoSuchElementException",
+ () => new HttpApiError.NotFound()
+ )
+ );
+
+ const [S3ProviderLayer, customBucket] =
+ yield* s3Buckets.getProviderLayer(video.bucketId);
+
+ return yield* getPlaylistResponse(
+ video,
+ Option.isSome(customBucket),
+ urlParams
+ ).pipe(Effect.provide(S3ProviderLayer));
+ }).pipe(
+ provideOptionalAuth,
+ Effect.catchTags({
+ VerifyVideoPasswordError: () => new HttpApiError.Forbidden(),
+ PolicyDenied: () => new HttpApiError.Unauthorized(),
+ DatabaseError: (e) =>
+ Effect.logError(e).pipe(
+ Effect.andThen(() => new HttpApiError.InternalServerError())
+ ),
+ S3Error: (e) =>
+ Effect.logError(e).pipe(
+ Effect.andThen(() => new HttpApiError.InternalServerError())
+ ),
+ })
+ )
);
+ })
+ )
+ )
+);
+
+const getPlaylistResponse = (
+ video: Video.Video,
+ isCustomBucket: boolean,
+ urlParams: (typeof GetPlaylistParams)["Type"]
+) =>
+ Effect.gen(function* () {
+ const s3 = yield* S3BucketAccess;
+
+ if (!isCustomBucket) {
+ let redirect = `${video.ownerId}/${video.id}/combined-source/stream.m3u8`;
+
+ if (video.source.type === "desktopMP4" || urlParams.videoType === "mp4")
+ redirect = `${video.ownerId}/${video.id}/result.mp4`;
+ else if (video.source.type === "MediaConvert")
+ redirect = `${video.ownerId}/${video.id}/output/video_recording_000.m3u8`;
+
+ return HttpServerResponse.redirect(
+ yield* s3.getSignedObjectUrl(redirect)
+ );
+ }
- const { video, bucket: customBucket } = query[0];
+ if (
+ Option.isSome(urlParams.fileType) &&
+ urlParams.fileType.value === "transcription"
+ ) {
+ return yield* s3
+ .getObject(`${video.ownerId}/${video.id}/transcription.vtt`)
+ .pipe(
+ Effect.andThen(
+ Option.match({
+ onNone: () => new HttpApiError.NotFound(),
+ onSome: (c) =>
+ HttpServerResponse.text(c).pipe(
+ HttpServerResponse.setHeaders({
+ ...CACHE_CONTROL_HEADERS,
+ "Content-Type": "text/vtt",
+ })
+ ),
+ })
+ ),
+ Effect.withSpan("fetchTranscription")
+ );
+ }
- const hasAccess = await userHasAccessToVideo(user, video);
+ const videoPrefix = `${video.ownerId}/${video.id}/video/`;
+ const audioPrefix = `${video.ownerId}/${video.id}/audio/`;
- if (hasAccess === "private")
- return c.json(
- JSON.stringify({ error: true, message: "Video is not public" }),
- 401
- );
- else if (hasAccess === "needs-password")
- return c.json(
- JSON.stringify({ error: true, message: "Video requires password" }),
- 403
- );
+ return yield* Effect.gen(function* () {
+ if (video.source.type === "local") {
+ const playlistText =
+ (yield* s3.getObject(
+ `${video.ownerId}/${video.id}/combined-source/stream.m3u8`
+ )).pipe(Option.getOrNull) ?? "";
- const bucket = await createBucketProvider(customBucket);
+ const lines = playlistText.split("\n");
- if (!customBucket || video.awsBucket === serverEnv().CAP_AWS_BUCKET) {
- if (video.source.type === "desktopMP4") {
- return c.redirect(
- await bucket.getSignedObjectUrl(
- `${video.ownerId}/${videoId}/result.mp4`
- )
- );
+ for (const [index, line] of lines.entries()) {
+ if (line.endsWith(".ts")) {
+ const url = yield* s3.getSignedObjectUrl(
+ `${video.ownerId}/${video.id}/combined-source/${line}`
+ );
+ lines[index] = url;
+ }
}
- if (video.source.type === "MediaConvert") {
- return c.redirect(
- await bucket.getSignedObjectUrl(
- `${video.ownerId}/${videoId}/output/video_recording_000.m3u8`
- )
- );
- }
+ const playlist = lines.join("\n");
- return c.redirect(
- await bucket.getSignedObjectUrl(
- `${video.ownerId}/${videoId}/combined-source/stream.m3u8`
- )
- );
+ return HttpServerResponse.text(playlist, {
+ headers: CACHE_CONTROL_HEADERS,
+ });
+ } else if (video.source.type === "desktopMP4") {
+ return yield* s3
+ .getSignedObjectUrl(`${video.ownerId}/${video.id}/result.mp4`)
+ .pipe(Effect.map(HttpServerResponse.redirect));
}
- if (fileType === "transcription") {
- try {
- const transcriptionContent = await bucket.getObject(
- `${video.ownerId}/${videoId}/transcription.vtt`
- );
-
- return c.body(transcriptionContent ?? "", {
- headers: {
- ...CACHE_CONTROL_HEADERS,
- "Content-Type": "text/vtt",
- },
- });
- } catch (error) {
- console.error("Error fetching transcription file:", error);
- return c.json(
- {
- error: true,
- message: "Transcription file not found",
- },
- 404
- );
- }
+ let prefix;
+ switch (urlParams.videoType) {
+ case "video":
+ prefix = videoPrefix;
+ break;
+ case "audio":
+ prefix = audioPrefix;
+ break;
+ case "master":
+ prefix = null;
+ break;
}
- const videoPrefix = `${video.ownerId}/${videoId}/video/`;
- const audioPrefix = `${video.ownerId}/${videoId}/audio/`;
-
- try {
- if (video.source.type === "local") {
- const playlistText =
- (await bucket.getObject(
- `${video.ownerId}/${videoId}/combined-source/stream.m3u8`
- )) ?? "";
-
- const lines = playlistText.split("\n");
-
- for (const [index, line] of lines.entries()) {
- if (line.endsWith(".ts")) {
- const url = await bucket.getObject(
- `${video.ownerId}/${videoId}/combined-source/${line}`
- );
- if (!url) continue;
- lines[index] = url;
- }
- }
+ if (prefix === null) {
+ const [videoSegment, audioSegment] = yield* Effect.all([
+ s3.listObjects({ prefix: videoPrefix, maxKeys: 1 }),
+ s3.listObjects({ prefix: audioPrefix, maxKeys: 1 }),
+ ]);
- const playlist = lines.join("\n");
-
- return c.text(playlist, {
- headers: CACHE_CONTROL_HEADERS,
- });
- }
-
- if (video.source.type === "desktopMP4") {
- const playlistUrl = await bucket.getSignedObjectUrl(
- `${video.ownerId}/${videoId}/result.mp4`
+ let audioMetadata;
+ const videoMetadata = yield* s3.headObject(
+ videoSegment.Contents?.[0]?.Key ?? ""
+ );
+ if (audioSegment?.KeyCount && audioSegment?.KeyCount > 0) {
+ audioMetadata = yield* s3.headObject(
+ audioSegment.Contents?.[0]?.Key ?? ""
);
- if (!playlistUrl) return new Response(null, { status: 404 });
-
- return c.redirect(playlistUrl);
- }
-
- let prefix;
- switch (videoType) {
- case "video":
- prefix = videoPrefix;
- break;
- case "audio":
- prefix = audioPrefix;
- break;
- case "master":
- prefix = null;
- break;
- default:
- return c.json({ error: true, message: "Invalid video type" }, 401);
}
- if (prefix === null) {
- const [videoSegment, audioSegment] = await Promise.all([
- bucket.listObjects({ prefix: videoPrefix, maxKeys: 1 }),
- bucket.listObjects({ prefix: audioPrefix, maxKeys: 1 }),
- ]);
-
- let audioMetadata;
- const videoMetadata = await bucket.headObject(
- videoSegment.Contents?.[0]?.Key ?? ""
- );
- if (audioSegment?.KeyCount && audioSegment?.KeyCount > 0) {
- audioMetadata = await bucket.headObject(
- audioSegment.Contents?.[0]?.Key ?? ""
- );
- }
-
- const generatedPlaylist = await generateMasterPlaylist(
- videoMetadata?.Metadata?.resolution ?? "",
- videoMetadata?.Metadata?.bandwidth ?? "",
- `${serverEnv().WEB_URL}/api/playlist?userId=${
- video.ownerId
- }&videoId=${videoId}&videoType=video`,
- audioMetadata
- ? `${serverEnv().WEB_URL}/api/playlist?userId=${
- video.ownerId
- }&videoId=${videoId}&videoType=audio`
- : null,
- video.xStreamInfo ?? ""
- );
-
- return c.text(generatedPlaylist, {
- headers: CACHE_CONTROL_HEADERS,
- });
- }
+ const generatedPlaylist = generateMasterPlaylist(
+ videoMetadata?.Metadata?.resolution ?? "",
+ videoMetadata?.Metadata?.bandwidth ?? "",
+ `${serverEnv().WEB_URL}/api/playlist?userId=${
+ video.ownerId
+ }&videoId=${video.id}&videoType=video`,
+ audioMetadata
+ ? `${serverEnv().WEB_URL}/api/playlist?userId=${
+ video.ownerId
+ }&videoId=${video.id}&videoType=audio`
+ : null
+ );
- const objects = await bucket.listObjects({
- prefix,
- maxKeys: thumbnail ? 1 : undefined,
+ return HttpServerResponse.text(generatedPlaylist, {
+ headers: CACHE_CONTROL_HEADERS,
});
+ }
+
+ const objects = yield* s3.listObjects({
+ prefix,
+ maxKeys: urlParams.thumbnail ? 1 : undefined,
+ });
- const chunksUrls = await Promise.all(
- (objects.Contents || []).map(async (object) => {
- const url = await bucket.getSignedObjectUrl(object.Key ?? "");
- const metadata = await bucket.headObject(object.Key ?? "");
+ const chunksUrls = yield* Effect.all(
+ (objects.Contents || []).map((object) =>
+ Effect.gen(function* () {
+ const url = yield* s3.getSignedObjectUrl(object.Key ?? "");
+ const metadata = yield* s3.headObject(object.Key ?? "");
return {
url: url,
@@ -228,26 +232,18 @@ const app = new Hono()
audioCodec: metadata?.Metadata?.audiocodec ?? "",
};
})
- );
+ )
+ );
- const generatedPlaylist = generateM3U8Playlist(chunksUrls);
+ const generatedPlaylist = generateM3U8Playlist(chunksUrls);
- return c.text(generatedPlaylist, {
- headers: CACHE_CONTROL_HEADERS,
- });
- } catch (error) {
- console.error("Error generating video segment URLs", error);
- return c.json(
- {
- error: error,
- message: "Error generating video URLs",
- },
- 500
- );
- }
- }
- );
+ return HttpServerResponse.text(generatedPlaylist, {
+ headers: CACHE_CONTROL_HEADERS,
+ });
+ }).pipe(Effect.withSpan("generateUrls"));
+ });
+
+const { handler } = apiToHandler(ApiLive);
-export const GET = handle(app);
-export const OPTIONS = handle(app);
-export const HEAD = handle(app);
+export const GET = handler;
+export const HEAD = handler;
diff --git a/apps/web/app/api/upload/[...route]/multipart.ts b/apps/web/app/api/upload/[...route]/multipart.ts
index 98e17ac44..e118d7888 100644
--- a/apps/web/app/api/upload/[...route]/multipart.ts
+++ b/apps/web/app/api/upload/[...route]/multipart.ts
@@ -302,7 +302,9 @@ app.post(
},
body: JSON.stringify({ videoId: videoIdFromFileKey }),
});
- console.log(`Revalidation triggered for videoId: ${videoId}`);
+ console.log(
+ `Revalidation triggered for videoId: ${videoIdFromFileKey}`
+ );
} catch (revalidateError) {
console.error("Failed to revalidate page:", revalidateError);
}
diff --git a/apps/web/app/api/video/delete/route.ts b/apps/web/app/api/video/delete/route.ts
index 108574f3d..d5648daf4 100644
--- a/apps/web/app/api/video/delete/route.ts
+++ b/apps/web/app/api/video/delete/route.ts
@@ -1,71 +1,53 @@
-import { type NextRequest } from "next/server";
-import { getCurrentUser } from "@cap/database/auth/session";
-import { s3Buckets, videos } from "@cap/database/schema";
-import { db } from "@cap/database";
-import { and, eq } from "drizzle-orm";
-import { getHeaders } from "@/utils/helpers";
-import { createBucketProvider } from "@/utils/s3";
-
-export async function DELETE(request: NextRequest) {
- const user = await getCurrentUser();
- const { searchParams } = request.nextUrl;
- const videoId = searchParams.get("videoId") || "";
- const userId = user?.id as string;
- const origin = request.headers.get("origin") as string;
-
- if (!videoId || !userId) {
- console.error("Missing required data in /api/video/delete/route.ts");
-
- return Response.json({ error: true }, { status: 401 });
- }
-
- const query = await db()
- .select({ video: videos, bucket: s3Buckets })
- .from(videos)
- .leftJoin(s3Buckets, eq(videos.bucket, s3Buckets.id))
- .where(eq(videos.id, videoId));
-
- if (query.length === 0) {
- return new Response(
- JSON.stringify({ error: true, message: "Video does not exist" }),
- {
- status: 401,
- headers: getHeaders(origin),
- }
- );
- }
-
- const result = query[0];
- if (!result) {
- return new Response(
- JSON.stringify({ error: true, message: "Video not found" }),
- {
- status: 404,
- headers: getHeaders(origin),
- }
- );
- }
-
- await db()
- .delete(videos)
- .where(and(eq(videos.id, videoId), eq(videos.ownerId, userId)));
-
- const bucketProvider = await createBucketProvider(result.bucket);
- const prefix = `${userId}/${videoId}/`;
-
- const listedObjects = await bucketProvider.listObjects({
- prefix: prefix,
- });
-
- if (listedObjects.Contents?.length) {
- await bucketProvider.deleteObjects(
- listedObjects.Contents.map((content) => ({
- Key: content.Key,
- }))
- );
- }
-
- return Response.json(true, {
- status: 200,
- });
-}
+import {
+ HttpApi,
+ HttpApiBuilder,
+ HttpApiEndpoint,
+ HttpApiError,
+ HttpApiGroup,
+ HttpServerResponse,
+} from "@effect/platform";
+import { Effect, Layer, Schema } from "effect";
+import { Videos } from "@cap/web-backend";
+import { HttpAuthMiddleware, Video } from "@cap/web-domain";
+import { apiToHandler } from "@/lib/server";
+
+class Api extends HttpApi.make("Api").add(
+ HttpApiGroup.make("root").add(
+ HttpApiEndpoint.del("deleteVideo")`/api/video/delete`
+ .setUrlParams(Schema.Struct({ videoId: Video.VideoId }))
+ .middleware(HttpAuthMiddleware)
+ .addError(HttpApiError.Forbidden)
+ .addError(HttpApiError.NotFound)
+ )
+) {}
+
+const ApiLive = HttpApiBuilder.api(Api).pipe(
+ Layer.provide(
+ HttpApiBuilder.group(Api, "root", (handlers) =>
+ Effect.gen(function* () {
+ const videos = yield* Videos;
+
+ return handlers.handle("deleteVideo", ({ urlParams }) =>
+ videos.delete(urlParams.videoId).pipe(
+ Effect.catchTags({
+ VideoNotFoundError: () => new HttpApiError.NotFound(),
+ PolicyDenied: () => new HttpApiError.Unauthorized(),
+ DatabaseError: (e) =>
+ Effect.logError(e).pipe(
+ Effect.andThen(() => new HttpApiError.InternalServerError())
+ ),
+ S3Error: (e) =>
+ Effect.logError(e).pipe(
+ Effect.andThen(() => new HttpApiError.InternalServerError())
+ ),
+ })
+ )
+ );
+ })
+ )
+ )
+);
+
+const { handler } = apiToHandler(ApiLive);
+
+export const DELETE = handler;
diff --git a/apps/web/app/embed/[videoId]/page.tsx b/apps/web/app/embed/[videoId]/page.tsx
index 358810d91..6d3ee87aa 100644
--- a/apps/web/app/embed/[videoId]/page.tsx
+++ b/apps/web/app/embed/[videoId]/page.tsx
@@ -13,7 +13,7 @@ import type { Metadata } from "next";
import { notFound } from "next/navigation";
import Link from "next/link";
import { buildEnv } from "@cap/env";
-import { transcribeVideo } from "@/actions/videos/transcribe";
+import { transcribeVideo } from "@/lib/transcribe";
import { isAiGenerationEnabled } from "@/utils/flags";
import { userHasAccessToVideo } from "@/utils/auth";
import { EmbedVideo } from "./_components/EmbedVideo";
@@ -166,7 +166,8 @@ export default async function EmbedVideoPage(props: Props) {
This video is private
- If you own this video, please sign in to manage sharing.
+ If you own this video, please sign in to
+ manage sharing.
);
@@ -243,7 +244,7 @@ async function EmbedContent({
video.transcriptionStatus !== "COMPLETE" &&
video.transcriptionStatus !== "PROCESSING"
) {
- await transcribeVideo(video.id, video.ownerId, aiGenerationEnabled);
+ transcribeVideo(video.id, video.ownerId, aiGenerationEnabled);
}
const currentMetadata = (video.metadata as VideoMetadata) || {};
diff --git a/apps/web/app/s/[videoId]/page.tsx b/apps/web/app/s/[videoId]/page.tsx
index 723360037..934829a9e 100644
--- a/apps/web/app/s/[videoId]/page.tsx
+++ b/apps/web/app/s/[videoId]/page.tsx
@@ -19,7 +19,7 @@ import { notFound } from "next/navigation";
import Link from "next/link";
import { buildEnv } from "@cap/env";
import { getVideoAnalytics } from "@/actions/videos/get-analytics";
-import { transcribeVideo } from "@/actions/videos/transcribe";
+import { transcribeVideo } from "@/lib/transcribe";
import { headers } from "next/headers";
import { generateAiMetadata } from "@/actions/videos/generate-ai-metadata";
import { isAiGenerationEnabled } from "@/utils/flags";
diff --git a/apps/web/lib/EffectRuntime.ts b/apps/web/lib/EffectRuntime.ts
new file mode 100644
index 000000000..873240dff
--- /dev/null
+++ b/apps/web/lib/EffectRuntime.ts
@@ -0,0 +1,22 @@
+import { Layer, ManagedRuntime } from "effect";
+import * as WebSdk from "@effect/opentelemetry/WebSdk";
+
+import { Rpc } from "./Rpcs";
+import {
+ makeUseEffectMutation,
+ makeUseEffectQuery,
+} from "./effect-react-query";
+import { getTracingConfig } from "./tracing";
+
+const TracingLayer = WebSdk.layer(getTracingConfig);
+
+const RuntimeLayer = Layer.mergeAll(Rpc.Default, TracingLayer);
+
+export type RuntimeLayer = typeof RuntimeLayer;
+
+export const EffectRuntime = ManagedRuntime.make(RuntimeLayer);
+
+export const useEffectQuery = makeUseEffectQuery(() => EffectRuntime);
+export const useEffectMutation = makeUseEffectMutation(() => EffectRuntime);
+
+export const useRpcClient = () => EffectRuntime.runSync(Rpc);
diff --git a/apps/web/lib/Rpcs.ts b/apps/web/lib/Rpcs.ts
new file mode 100644
index 000000000..4f513e4ce
--- /dev/null
+++ b/apps/web/lib/Rpcs.ts
@@ -0,0 +1,17 @@
+import { Rpcs } from "@cap/web-domain";
+import { FetchHttpClient } from "@effect/platform";
+import { RpcClient, RpcSerialization } from "@effect/rpc";
+import { Effect, Layer } from "effect";
+
+const RpcProtocol = RpcClient.layerProtocolHttp({ url: "/api/erpc" }).pipe(
+ Layer.provideMerge(FetchHttpClient.layer),
+ Layer.provideMerge(RpcSerialization.layerJson)
+);
+
+export class Rpc extends Effect.Service()("Rpc", {
+ scoped: RpcClient.make(Rpcs),
+ dependencies: [RpcProtocol],
+}) {}
+
+export const withRpc = (cb: (rpc: Rpc) => Effect.Effect ) =>
+ Effect.flatMap(Rpc, cb);
diff --git a/apps/web/lib/effect-react-query.ts b/apps/web/lib/effect-react-query.ts
new file mode 100644
index 000000000..4399e43ee
--- /dev/null
+++ b/apps/web/lib/effect-react-query.ts
@@ -0,0 +1,483 @@
+// all credit to https://github.com/ethanniser/terpc/blob/main/packages/effect-react-query/src/hooks.ts
+import type {
+ MutateOptions,
+ QueryFunctionContext,
+ QueryKey,
+ SkipToken,
+ UseMutationOptions,
+ UseMutationResult,
+ UseQueryOptions,
+ UseQueryResult,
+} from "@tanstack/react-query";
+import { useMutation, useQuery } from "@tanstack/react-query";
+import type { ManagedRuntime } from "effect";
+import * as Cause from "effect/Cause";
+import * as Effect from "effect/Effect";
+import * as Either from "effect/Either";
+import * as Exit from "effect/Exit";
+
+// Todo: useSuspenseQuery and queryOptions
+
+type Override = {
+ [AKey in keyof TTargetA]: AKey extends keyof TTargetB
+ ? TTargetB[AKey]
+ : TTargetA[AKey];
+};
+
+export function makeUseEffectQuery(
+ useEffectRuntime: () => ManagedRuntime.ManagedRuntime
+) {
+ return function useEffectQuery<
+ TData,
+ TError,
+ ThrowOnDefect extends boolean = false,
+ TExposedError = ThrowOnDefect extends true ? TError : Cause.Cause,
+ TQueryKey extends QueryKey = QueryKey
+ >(
+ options: {
+ throwOnDefect?: ThrowOnDefect;
+ } & Override<
+ UseQueryOptions,
+ {
+ queryFn?:
+ | ((
+ context: QueryFunctionContext
+ ) => Effect.Effect)
+ | SkipToken;
+ }
+ >
+ ): UseQueryResult {
+ const throwOnDefect = options.throwOnDefect ?? false;
+
+ const runtime = useEffectRuntime();
+ const queryFn = options.queryFn;
+ const throwOnError = options.throwOnError;
+
+ const baseResults = useQuery({
+ ...(options as any),
+ ...(typeof queryFn === "function"
+ ? {
+ queryFn: async (args) => {
+ let queryEffect: Effect.Effect;
+ try {
+ queryEffect = queryFn(args);
+ } catch (e) {
+ throw new Cause.UnknownException(e, "queryFn threw");
+ }
+ const effectToRun = queryEffect;
+ // .pipe(
+ // Effect.withSpan("useEffectQuery", {
+ // attributes: {
+ // queryKey: args.queryKey,
+ // queryFn: queryFn.toString(),
+ // },
+ // })
+ // );
+ const result = await runtime.runPromiseExit(effectToRun, {
+ signal: args.signal,
+ });
+ if (Exit.isFailure(result)) {
+ // we always throw the cause
+ throw result.cause;
+ } else {
+ return result.value;
+ }
+ },
+ }
+ : { queryFn }),
+ ...(typeof throwOnError === "function"
+ ? {
+ throwOnError: (error, query) => {
+ // this is safe because internally when we call useQuery we always throw the full cause or UnknownException
+ const cause = error as
+ | Cause.Cause
+ | Cause.UnknownException;
+ // if the cause is UnknownException, we always return true and throw it
+ if (Cause.isUnknownException(cause)) {
+ return true;
+ }
+ const failureOrCause = Cause.failureOrCause(cause);
+ if (throwOnDefect) {
+ // in this case options.throwOnError expects a TError
+ // the cause was a fail, so we have TError
+ if (Either.isLeft(failureOrCause)) {
+ // this is safe because if throwOnDefect is true then TExposedError is TError
+ const exposedError =
+ failureOrCause.left as unknown as TExposedError;
+ return throwOnError(exposedError, query);
+ } else {
+ // the cause was a die or interrupt, so we return true
+ return true;
+ }
+ } else {
+ // in this case options.throwOnError expects a Cause
+ // this is safe because if throwOnDefect is false then TExposedError is Cause
+ const exposedError = cause as unknown as TExposedError;
+ return throwOnError(exposedError, query);
+ }
+ },
+ }
+ : {}),
+ });
+
+ // the results from react query all have getters which trigger fine grained tracking, we need to replicate this when we wrap the results
+ const resultsProxy = new Proxy(baseResults, {
+ get: (target, prop, receiver) => {
+ if (prop === "error") {
+ return target.error
+ ? throwOnDefect
+ ? Either.match(
+ Cause.failureOrCause(
+ target.error as unknown as Cause.Cause // this is safe because we always throw the full cause and we know that error is not null
+ ),
+ {
+ onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError
+ onRight: (_cause) => {
+ throw new Error(
+ "non fail cause with throwOnDefect: true should have thrown already"
+ );
+ },
+ }
+ )
+ : target.error // if throwOnDefect is false then TExposedError is Cause, and base error is always Cause
+ : null;
+ } else if (prop === "failureReason") {
+ return target.failureReason
+ ? throwOnDefect
+ ? Either.match(
+ Cause.failureOrCause(
+ target.failureReason as unknown as Cause.Cause // this is safe because we always throw the full cause and we know that error is not null
+ ),
+ {
+ onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError
+ onRight: (_cause) => {
+ throw new Error(
+ "non fail cause with throwOnDefect: true should have thrown already"
+ );
+ },
+ }
+ )
+ : target.failureReason // if throwOnDefect is false then TExposedError is Cause, and base error is always Cause
+ : null;
+ }
+
+ return Reflect.get(target, prop, receiver);
+ },
+ });
+
+ return resultsProxy as UseQueryResult;
+ // this is safe because we are only doing very light remapping
+ // it gets mad when you touch error because it is either TError or null depending on other properities, but we honor those cases
+ };
+}
+
+export function makeUseEffectMutation(
+ useEffectRuntime: () => ManagedRuntime.ManagedRuntime
+) {
+ return function useEffectMutation<
+ TData,
+ TError,
+ ThrowOnDefect extends boolean = false,
+ TExposedError = ThrowOnDefect extends true ? TError : Cause.Cause,
+ TVariables = void,
+ TContext = unknown
+ >(
+ options: {
+ throwOnDefect?: ThrowOnDefect;
+ } & Override<
+ Omit<
+ UseMutationOptions,
+ "retry" | "retryDelay"
+ >,
+ {
+ mutationFn?: (variables: TVariables) => Effect.Effect;
+ onMutate?: (
+ variables: TVariables
+ ) => Effect.Effect;
+ onSuccess?: (
+ data: TData,
+ variables: TVariables,
+ context: TContext
+ ) => Effect.Effect;
+ onError?: (
+ error: TExposedError,
+ variables: TVariables,
+ context: TContext | undefined
+ ) => Effect.Effect;
+ onSettled?: (
+ data: TData | undefined,
+ error: TExposedError | null,
+ variables: TVariables,
+ context: TContext | undefined
+ ) => Effect.Effect;
+ }
+ >
+ ): Override<
+ UseMutationResult,
+ {
+ mutateAsync: (
+ variables: TVariables,
+ options?: MutateOptions<
+ TData,
+ Cause.Cause,
+ TVariables,
+ TContext
+ >
+ ) => Promise>;
+ mutate: (
+ variables: TVariables,
+ options?: MutateOptions<
+ TData,
+ Cause.Cause,
+ TVariables,
+ TContext
+ >
+ ) => void;
+ }
+ > {
+ const mutationFn = options.mutationFn;
+ const throwOnDefect = options.throwOnDefect ?? false;
+ const throwOnError = options.throwOnError;
+ const onMutate = options.onMutate;
+ const onSuccess = options.onSuccess;
+ const onError = options.onError;
+ const onSettled = options.onSettled;
+
+ const runtime = useEffectRuntime();
+
+ const baseResults = useMutation({
+ ...options,
+ mutationFn:
+ typeof mutationFn === "function"
+ ? async (variables: TVariables) => {
+ let mutationEffect: Effect.Effect;
+ try {
+ mutationEffect = mutationFn(variables);
+ } catch (e) {
+ throw new Cause.UnknownException(e, "mutationFn threw");
+ }
+ const effectToRun = mutationEffect;
+ // .pipe(
+ // Effect.withSpan("useEffectMutation", {
+ // attributes: {
+ // mutationFn: mutationFn.toString(),
+ // },
+ // })
+ // );
+ const result = await runtime.runPromiseExit(effectToRun);
+ console.log({ result });
+ if (Exit.isFailure(result)) {
+ // we always throw the cause
+ throw result.cause;
+ } else {
+ return result.value;
+ }
+ }
+ : mutationFn,
+ throwOnError:
+ typeof throwOnError === "function"
+ ? (error: Cause.Cause) => {
+ // this is safe because internally when we call useQuery we always throw the full cause or UnknownException
+ const cause = error as
+ | Cause.Cause
+ | Cause.UnknownException;
+
+ // if the cause is UnknownException, we always return true and throw it
+ if (Cause.isUnknownException(cause)) {
+ return true;
+ }
+
+ const failureOrCause = Cause.failureOrCause(cause);
+
+ if (throwOnDefect) {
+ // in this case options.throwOnError expects a TError
+
+ // the cause was a fail, so we have TError
+ if (Either.isLeft(failureOrCause)) {
+ // this is safe because if throwOnDefect is true then TExposedError is TError
+ const exposedError =
+ failureOrCause.left as unknown as TExposedError;
+ return throwOnError(exposedError);
+ } else {
+ // the cause was a die or interrupt, so we return true
+ return true;
+ }
+ } else {
+ // in this case options.throwOnError expects a Cause
+ // this is safe because if throwOnDefect is false then TExposedError is Cause
+ const exposedError = cause as unknown as TExposedError;
+ return throwOnError(exposedError);
+ }
+ }
+ : throwOnError,
+ onMutate:
+ typeof onMutate === "function"
+ ? async (...args) => {
+ return await runtime.runPromise(
+ onMutate(...args)
+ // .pipe(
+ // Effect.withSpan("useEffectMutation.onMutate", {
+ // attributes: {
+ // mutationFn: mutationFn?.toString(),
+ // },
+ // })
+ // )
+ );
+ }
+ : undefined,
+ onSuccess:
+ typeof onSuccess === "function"
+ ? async (...args) => {
+ return await runtime.runPromise(
+ onSuccess(...args)
+ // .pipe(
+ // Effect.withSpan("useEffectMutation.onSuccess", {
+ // attributes: {
+ // mutationFn: mutationFn?.toString(),
+ // },
+ // })
+ // )
+ );
+ }
+ : undefined,
+ onError:
+ typeof onError === "function"
+ ? async (baseError, ...args) => {
+ const error = throwOnDefect
+ ? Either.match(Cause.failureOrCause(baseError), {
+ onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError
+ onRight: (_cause) => {
+ throw new Error(
+ "non fail cause with throwOnDefect: true should have thrown already"
+ );
+ },
+ })
+ : (baseError as unknown as TExposedError);
+
+ return await runtime.runPromise(
+ onError(error, ...args)
+ // .pipe(
+ // Effect.withSpan("useEffectMutation.onError", {
+ // attributes: {
+ // mutationFn: mutationFn?.toString(),
+ // },
+ // })
+ // )
+ );
+ }
+ : undefined,
+ onSettled:
+ typeof onSettled === "function"
+ ? async (data, baseError, ...args) => {
+ const error = baseError
+ ? throwOnDefect
+ ? Either.match(Cause.failureOrCause(baseError), {
+ onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError
+ onRight: (_cause) => {
+ throw new Error(
+ "non fail cause with throwOnDefect: true should have thrown already"
+ );
+ },
+ })
+ : (baseError as unknown as TExposedError)
+ : null;
+
+ return await runtime.runPromise(
+ onSettled(data, error, ...args)
+ // .pipe(
+ // Effect.withSpan("useEffectMutation.onSettled", {
+ // attributes: {
+ // mutationFn: mutationFn?.toString(),
+ // },
+ // })
+ // )
+ );
+ }
+ : undefined,
+ });
+
+ // the results from react query all have getters which trigger fine grained tracking, we need to replicate this when we wrap the results
+ const resultsProxy = new Proxy(baseResults, {
+ get: (target, prop, receiver) => {
+ if (prop === "error") {
+ return target.error
+ ? throwOnDefect
+ ? Either.match(
+ Cause.failureOrCause(
+ target.error as unknown as Cause.Cause // this is safe because we always throw the full cause and we know that error is not null
+ ),
+ {
+ onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError
+ onRight: (_cause) => {
+ throw new Error(
+ "non fail cause with throwOnDefect: true should have thrown already"
+ );
+ },
+ }
+ )
+ : target.error // if throwOnDefect is false then TExposedError is Cause, and base error is always Cause
+ : null;
+ } else if (prop === "failureReason") {
+ return target.failureReason
+ ? throwOnDefect
+ ? Either.match(
+ Cause.failureOrCause(
+ target.failureReason as unknown as Cause.Cause // this is safe because we always throw the full cause and we know that error is not null
+ ),
+ {
+ onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError
+ onRight: (_cause) => {
+ throw new Error(
+ "non fail cause with throwOnDefect: true should have thrown already"
+ );
+ },
+ }
+ )
+ : target.failureReason // if throwOnDefect is false then TExposedError is Cause, and base error is always Cause
+ : null;
+ } else if (prop === "mutate") {
+ return (variables: any, options: any) => {
+ return target.mutate(variables, options);
+ };
+ } else if (prop === "mutateAsync") {
+ return (variables: any, options: any) =>
+ target
+ .mutateAsync(variables, options)
+ .then((res) => Exit.succeed(res))
+ // we always throw the cause, so we can always catch it
+ .catch((cause: Cause.Cause) =>
+ Exit.fail(cause)
+ ) as Promise>;
+ }
+
+ return Reflect.get(target, prop, receiver);
+ },
+ });
+
+ return resultsProxy as Override<
+ UseMutationResult,
+ {
+ mutateAsync: (
+ variables: TVariables,
+ options?: MutateOptions<
+ TData,
+ Cause.Cause,
+ TVariables,
+ TContext
+ >
+ ) => Promise>;
+ mutate: (
+ variables: TVariables,
+ options?: MutateOptions<
+ TData,
+ Cause.Cause,
+ TVariables,
+ TContext
+ >
+ ) => void;
+ }
+ >;
+ // this is safe because we are only doing very light remapping
+ // it gets mad when you touch error because it is either TError or null depending on other properities, but we honor those cases
+ };
+}
diff --git a/apps/web/actions/folders/getVideosByFolderId.ts b/apps/web/lib/folder.ts
similarity index 60%
rename from apps/web/actions/folders/getVideosByFolderId.ts
rename to apps/web/lib/folder.ts
index 64997e617..629a68d6a 100644
--- a/apps/web/actions/folders/getVideosByFolderId.ts
+++ b/apps/web/lib/folder.ts
@@ -1,6 +1,8 @@
-"use server";
+import "server-only";
import { db } from "@cap/database";
+import { getCurrentUser } from "@cap/database/auth/session";
+import { folders } from "@cap/database/schema";
import {
videos,
comments,
@@ -10,10 +12,51 @@ import {
spaceVideos,
spaces,
} from "@cap/database/schema";
-import { eq, desc } from "drizzle-orm";
+import { Folder } from "@cap/web-domain";
+import { eq, and, desc } from "drizzle-orm";
import { sql } from "drizzle-orm/sql";
import { revalidatePath } from "next/cache";
+export async function getFolderById(folderId: string | undefined) {
+ if (!folderId) throw new Error("Folder ID is required");
+
+ const [folder] = await db()
+ .select()
+ .from(folders)
+ .where(eq(folders.id, Folder.FolderId.make(folderId)));
+
+ if (!folder) throw new Error("Folder not found");
+
+ revalidatePath(`/dashboard/folder/${folderId}`);
+ return folder;
+}
+
+export async function getFolderBreadcrumb(folderId: string) {
+ const breadcrumb: Array<{
+ id: string;
+ name: string;
+ color: "normal" | "blue" | "red" | "yellow";
+ }> = [];
+ let currentFolderId = folderId;
+
+ while (currentFolderId) {
+ const folder = await getFolderById(currentFolderId);
+ if (!folder) break;
+
+ breadcrumb.unshift({
+ id: folder.id,
+ name: folder.name,
+ color: folder.color,
+ });
+
+ if (!folder.parentId) break;
+ currentFolderId = folder.parentId;
+ }
+
+ revalidatePath(`/dashboard/folder/${folderId}`);
+ return breadcrumb;
+}
+
// Helper function to fetch shared spaces data for videos
async function getSharedSpacesForVideos(videoIds: string[]) {
if (videoIds.length === 0) return {};
@@ -30,7 +73,12 @@ async function getSharedSpacesForVideos(videoIds: string[]) {
.from(spaceVideos)
.innerJoin(spaces, eq(spaceVideos.spaceId, spaces.id))
.innerJoin(organizations, eq(spaces.organizationId, organizations.id))
- .where(sql`${spaceVideos.videoId} IN (${sql.join(videoIds.map(id => sql`${id}`), sql`, `)})`);
+ .where(
+ sql`${spaceVideos.videoId} IN (${sql.join(
+ videoIds.map((id) => sql`${id}`),
+ sql`, `
+ )})`
+ );
// Fetch organization-level sharing
const orgSharing = await db()
@@ -43,41 +91,43 @@ async function getSharedSpacesForVideos(videoIds: string[]) {
})
.from(sharedVideos)
.innerJoin(organizations, eq(sharedVideos.organizationId, organizations.id))
- .where(sql`${sharedVideos.videoId} IN (${sql.join(videoIds.map(id => sql`${id}`), sql`, `)})`);
+ .where(
+ sql`${sharedVideos.videoId} IN (${sql.join(
+ videoIds.map((id) => sql`${id}`),
+ sql`, `
+ )})`
+ );
// Combine and group by videoId
- const sharedSpacesMap: Record> = {};
+ const sharedSpacesMap: Record<
+ string,
+ Array<{
+ id: string;
+ name: string;
+ organizationId: string;
+ iconUrl: string;
+ isOrg: boolean;
+ }>
+ > = {};
// Add space-level sharing
- spaceSharing.forEach(space => {
- if (!sharedSpacesMap[space.videoId]) {
- sharedSpacesMap[space.videoId] = [];
- }
- sharedSpacesMap[space.videoId].push({
+ spaceSharing.forEach((space) => {
+ (sharedSpacesMap[space.videoId] ??= []).push({
id: space.id,
name: space.name,
organizationId: space.organizationId,
- iconUrl: space.iconUrl || '',
+ iconUrl: space.iconUrl || "",
isOrg: false,
});
});
// Add organization-level sharing
- orgSharing.forEach(org => {
- if (!sharedSpacesMap[org.videoId]) {
- sharedSpacesMap[org.videoId] = [];
- }
- sharedSpacesMap[org.videoId].push({
+ orgSharing.forEach((org) => {
+ (sharedSpacesMap[org.videoId] ??= []).push({
id: org.id,
name: org.name,
organizationId: org.organizationId,
- iconUrl: org.iconUrl || '',
+ iconUrl: org.iconUrl || "",
isOrg: true,
});
});
@@ -141,7 +191,7 @@ export async function getVideosByFolderId(folderId: string) {
);
// Fetch shared spaces data for all videos
- const videoIds = videoData.map(video => video.id);
+ const videoIds = videoData.map((video) => video.id);
const sharedSpacesMap = await getSharedSpacesForVideos(videoIds);
// Process the video data to match the expected format
@@ -151,7 +201,9 @@ export async function getVideosByFolderId(folderId: string) {
return {
...videoWithoutEffectiveDate,
sharedOrganizations: Array.isArray(video.sharedOrganizations)
- ? video.sharedOrganizations.filter((organization) => organization.id !== null)
+ ? video.sharedOrganizations.filter(
+ (organization) => organization.id !== null
+ )
: [],
sharedSpaces: Array.isArray(sharedSpacesMap[video.id])
? sharedSpacesMap[video.id]
@@ -171,3 +223,32 @@ export async function getVideosByFolderId(folderId: string) {
return processedVideoData;
}
+
+export async function getChildFolders(folderId: Folder.FolderId) {
+ const user = await getCurrentUser();
+ if (!user || !user.activeOrganizationId)
+ throw new Error("Unauthorized or no active organization");
+
+ const childFolders = await db()
+ .select({
+ id: folders.id,
+ name: folders.name,
+ color: folders.color,
+ parentId: folders.parentId,
+ organizationId: folders.organizationId,
+ videoCount: sql`(
+ SELECT COUNT(*) FROM videos WHERE videos.folderId = folders.id
+ )`,
+ })
+ .from(folders)
+ .where(
+ and(
+ eq(folders.parentId, folderId),
+ eq(folders.organizationId, user.activeOrganizationId)
+ )
+ );
+
+ revalidatePath(`/dashboard/folder/${folderId}`);
+
+ return childFolders;
+}
diff --git a/apps/web/lib/server.ts b/apps/web/lib/server.ts
new file mode 100644
index 000000000..6d567d760
--- /dev/null
+++ b/apps/web/lib/server.ts
@@ -0,0 +1,62 @@
+import "server-only";
+
+import * as NodeSdk from "@effect/opentelemetry/NodeSdk";
+import { db } from "@cap/database";
+import { Effect, Layer } from "effect";
+import { HttpAuthMiddleware } from "@cap/web-domain";
+import {
+ Database,
+ DatabaseError,
+ Folders,
+ HttpAuthMiddlewareLive,
+ S3Buckets,
+ Videos,
+} from "@cap/web-backend";
+import {
+ HttpApi,
+ HttpApiBuilder,
+ HttpMiddleware,
+ HttpServer,
+} from "@effect/platform";
+import { allowedOrigins } from "@/utils/cors";
+import { getTracingConfig } from "./tracing";
+
+const DatabaseLive = Layer.sync(Database, () => ({
+ execute: (cb) =>
+ Effect.tryPromise({
+ try: () => cb(db()),
+ catch: (error) => new DatabaseError({ message: String(error) }),
+ }),
+}));
+
+const TracingLayer = NodeSdk.layer(getTracingConfig);
+
+export const Dependencies = Layer.mergeAll(
+ S3Buckets.Default,
+ Videos.Default,
+ Folders.Default,
+ TracingLayer
+).pipe(Layer.provideMerge(DatabaseLive));
+
+const cors = HttpApiBuilder.middlewareCors({
+ allowedOrigins,
+ credentials: true,
+ allowedMethods: ["GET", "HEAD", "POST", "OPTIONS"],
+ allowedHeaders: ["Content-Type", "Authorization", "sentry-trace", "baggage"],
+});
+
+export const apiToHandler = (
+ api: Layer.Layer<
+ HttpApi.Api,
+ never,
+ Layer.Layer.Success | HttpAuthMiddleware
+ >
+) =>
+ api.pipe(
+ HttpMiddleware.withSpanNameGenerator((req) => `${req.method} ${req.url}`),
+ Layer.provideMerge(HttpAuthMiddlewareLive),
+ Layer.provideMerge(Dependencies),
+ Layer.merge(HttpServer.layerContext),
+ Layer.provide(cors),
+ HttpApiBuilder.toWebHandler
+ );
diff --git a/apps/web/lib/tracing.ts b/apps/web/lib/tracing.ts
new file mode 100644
index 000000000..0d0b8b3f6
--- /dev/null
+++ b/apps/web/lib/tracing.ts
@@ -0,0 +1,29 @@
+import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
+import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
+import { Effect, Option } from "effect";
+
+export const getTracingConfig = Effect.gen(function* () {
+ const axiomToken = Option.fromNullable(process.env.NEXT_PUBLIC_AXIOM_TOKEN);
+
+ const axiomProcessor = Option.map(
+ axiomToken,
+ (token) =>
+ new BatchSpanProcessor(
+ new OTLPTraceExporter({
+ url: "https://api.axiom.co/v1/traces",
+ headers: {
+ Authorization: `Bearer ${token}`,
+ "X-Axiom-Dataset": "cap-web-test",
+ },
+ })
+ )
+ );
+
+ return {
+ resource: { serviceName: "cap-web" },
+ spanProcessor: Option.match(axiomProcessor, {
+ onNone: () => [],
+ onSome: (processor) => [processor],
+ }),
+ };
+});
diff --git a/apps/web/actions/videos/transcribe.ts b/apps/web/lib/transcribe.ts
similarity index 97%
rename from apps/web/actions/videos/transcribe.ts
rename to apps/web/lib/transcribe.ts
index 850d93b8b..e6e981422 100644
--- a/apps/web/actions/videos/transcribe.ts
+++ b/apps/web/lib/transcribe.ts
@@ -1,5 +1,3 @@
-import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
-import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { createClient } from "@deepgram/sdk";
import { db } from "@cap/database";
import { s3Buckets, videos } from "@cap/database/schema";
diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs
index 27dc6ffe4..07341cf9e 100644
--- a/apps/web/next.config.mjs
+++ b/apps/web/next.config.mjs
@@ -17,6 +17,7 @@ const nextConfig = {
"@cap/ui",
"@cap/utils",
"@cap/web-api-contract",
+ "@cap/web-domain",
"next-mdx-remote",
],
eslint: {
@@ -27,7 +28,12 @@ const nextConfig = {
},
experimental: {
instrumentationHook: process.env.NEXT_PUBLIC_DOCKER_BUILD === "true",
- optimizePackageImports: ["@cap/ui", "@cap/utils", "@cap/web-api-contract"],
+ optimizePackageImports: [
+ "@cap/ui",
+ "@cap/utils",
+ "@cap/web-api-contract",
+ "@cap/web-domain",
+ ],
},
images: {
remotePatterns: [
diff --git a/apps/web/package.json b/apps/web/package.json
index 59c3cc41c..3ba06a258 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -23,8 +23,14 @@
"@cap/ui": "workspace:*",
"@cap/utils": "workspace:*",
"@cap/web-api-contract": "workspace:*",
+ "@cap/web-api-contract-effect": "workspace:*",
+ "@cap/web-backend": "workspace:*",
+ "@cap/web-domain": "workspace:*",
"@deepgram/sdk": "^3.3.4",
"@dub/analytics": "^0.0.27",
+ "@effect/opentelemetry": "^0.56.1",
+ "@effect/platform": "^0.90.1",
+ "@effect/rpc": "^0.68.3",
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
@@ -36,6 +42,10 @@
"@mux/mux-player-react": "^3.3.0",
"@number-flow/react": "^0.5.9",
"@octokit/rest": "^20.0.2",
+ "@opentelemetry/exporter-trace-otlp-http": "^0.203.0",
+ "@opentelemetry/sdk-trace-base": "^2.0.1",
+ "@opentelemetry/sdk-trace-node": "^2.0.1",
+ "@opentelemetry/sdk-trace-web": "^2.0.1",
"@radix-ui/colors": "^3.0.0",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-select": "^2.2.5",
@@ -66,6 +76,7 @@
"dotenv": "^16.3.1",
"drizzle-orm": "0.43.1",
"dub": "^0.64.0",
+ "effect": "^3.17.7",
"file-saver": "^2.0.5",
"framer-motion": "^11.13.1",
"geist": "^1.3.1",
diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json
index 95571bbc7..ed4566c74 100644
--- a/apps/web/tsconfig.json
+++ b/apps/web/tsconfig.json
@@ -2,11 +2,7 @@
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
- "lib": [
- "DOM",
- "DOM.Iterable",
- "ESNext"
- ],
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": true,
@@ -21,40 +17,27 @@
"noEmit": true,
"jsx": "preserve",
"incremental": true,
- "plugins": [
- {
- "name": "next"
- }
- ],
+ "plugins": [{ "name": "next" }, { "name": "@effect/language-service" }],
"baseUrl": ".",
"paths": {
- "@/app/*": [
- "app/*"
- ],
- "@/components/*": [
- "components/*"
- ],
- "@/pages/*": [
- "components/pages/*"
- ],
- "@/utils/*": [
- "utils/*"
- ],
- "@/lib/*": [
- "lib/*"
- ],
- "@/actions/*": [
- "actions/*"
- ]
- }
+ "@/app/*": ["app/*"],
+ "@/components/*": ["components/*"],
+ "@/pages/*": ["components/pages/*"],
+ "@/utils/*": ["utils/*"],
+ "@/lib/*": ["lib/*"],
+ "@/actions/*": ["actions/*"],
+ "@/services": ["services"]
+ },
+ "types": ["node"]
},
- "include": [
- "next-env.d.ts",
- "**/*.ts",
- "**/*.tsx",
- ".next/types/**/*.ts"
- ],
- "exclude": [
- "node_modules"
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"],
+ "references": [
+ { "path": "../../packages/database" },
+ { "path": "../../packages/env" },
+ { "path": "../../packages/ui" },
+ { "path": "../../packages/utils" },
+ { "path": "../../packages/web-backend" },
+ { "path": "../../packages/web-domain" }
]
}
diff --git a/apps/web/utils/auth.ts b/apps/web/utils/auth.ts
index 25bef1b7b..9a438a10a 100644
--- a/apps/web/utils/auth.ts
+++ b/apps/web/utils/auth.ts
@@ -13,7 +13,10 @@ async function verifyPasswordCookie(videoPassword: string) {
export async function userHasAccessToVideo(
user: MaybePromise<{ id: string } | undefined | null>,
- video: Omit, "folderId">
+ video: Pick<
+ InferSelectModel,
+ "public" | "password" | "ownerId"
+ >
): Promise<"has-access" | "private" | "needs-password" | "not-org-email"> {
if (video.public && video.password === null) return "has-access";
diff --git a/apps/web/utils/video/ffmpeg/helpers.ts b/apps/web/utils/video/ffmpeg/helpers.ts
index c62d7e282..d62f88b96 100644
--- a/apps/web/utils/video/ffmpeg/helpers.ts
+++ b/apps/web/utils/video/ffmpeg/helpers.ts
@@ -27,12 +27,12 @@ export function generateM3U8Playlist(
return m3u8Content;
}
-export async function generateMasterPlaylist(
+export function generateMasterPlaylist(
resolution: string,
bandwidth: string,
videoPlaylistUrl: string,
audioPlaylistUrl: string | null,
- xStreamInfo: string
+ xStreamInfo?: string
) {
const streamInfo = xStreamInfo
? xStreamInfo + ',AUDIO="audio"'
diff --git a/apps/web/utils/web-api.ts b/apps/web/utils/web-api.ts
index aca399bc7..9ba8c5135 100644
--- a/apps/web/utils/web-api.ts
+++ b/apps/web/utils/web-api.ts
@@ -1,6 +1,6 @@
import { initClient } from "@ts-rest/core";
import { contract } from "@cap/web-api-contract";
-import { useContext, useState } from "react";
+import { useState } from "react";
import { usePublicEnv } from "./public-env";
@@ -8,9 +8,10 @@ export function useApiClient() {
const { webUrl } = usePublicEnv();
const [client] = useState(() =>
initClient(contract, {
- baseUrl: typeof window === "undefined"
- ? `${webUrl.replace(/\/+$/, "")}/api`
- : "/api",
+ baseUrl:
+ typeof window === "undefined"
+ ? `${webUrl.replace(/\/+$/, "")}/api`
+ : "/api",
})
);
diff --git a/package.json b/package.json
index 14d80e9db..1ef4947ff 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,8 @@
"dotenv-cli": "latest",
"prettier": "^2.5.1",
"turbo": "^2.3.4",
- "typescript": "^5.8.3"
+ "typescript": "^5.8.3",
+ "@effect/language-service": "^0.34.0"
},
"packageManager": "pnpm@10.5.2",
"name": "cap",
diff --git a/packages/config/base.tsconfig.json b/packages/config/base.tsconfig.json
index 1b1236d7f..dfd250e21 100644
--- a/packages/config/base.tsconfig.json
+++ b/packages/config/base.tsconfig.json
@@ -5,19 +5,16 @@
"module": "ESNext",
"skipLibCheck": true,
"esModuleInterop": true,
-
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
-
"strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
- "declaration": true
+ "declaration": true,
+ "plugins": [{ "name": "@effect/language-service" }]
},
"references": [{ "path": "./tsconfig.node.json" }]
}
diff --git a/packages/database/auth/auth-options.tsx b/packages/database/auth/auth-options.tsx
index f9ddabb1b..7b854d458 100644
--- a/packages/database/auth/auth-options.tsx
+++ b/packages/database/auth/auth-options.tsx
@@ -8,6 +8,7 @@ import { serverEnv } from "@cap/env";
import type { Adapter } from "next-auth/adapters";
import type { Provider } from "next-auth/providers/index";
import { cookies } from "next/headers";
+import { getServerSession as _getServerSession } from "next-auth";
import { dub } from "../dub";
import { db } from "../";
@@ -110,9 +111,12 @@ export const authOptions = (): NextAuthOptions => {
.from(users)
.where(eq(users.id, user.id))
.limit(1);
-
- const needsOrganizationSetup = isNewUser || (!dbUser?.activeOrganizationId || dbUser.activeOrganizationId === "");
-
+
+ const needsOrganizationSetup =
+ isNewUser ||
+ !dbUser?.activeOrganizationId ||
+ dbUser.activeOrganizationId === "";
+
if (needsOrganizationSetup) {
const dubId = cookies().get("dub_id")?.value;
const dubPartnerData = cookies().get("dub_partner_data")?.value;
@@ -143,7 +147,9 @@ export const authOptions = (): NextAuthOptions => {
console.error("Error details:", JSON.stringify(error, null, 2));
}
} else if (!isNewUser) {
- console.log("Guest checkout user signing in for the first time - setting up organization");
+ console.log(
+ "Guest checkout user signing in for the first time - setting up organization"
+ );
}
const organizationId = nanoId();
@@ -206,3 +212,5 @@ export const authOptions = (): NextAuthOptions => {
},
};
};
+
+export const getServerSession = () => _getServerSession(authOptions());
diff --git a/packages/database/dub.ts b/packages/database/dub.ts
index a18682e77..fdefc646a 100644
--- a/packages/database/dub.ts
+++ b/packages/database/dub.ts
@@ -4,4 +4,4 @@ import { serverEnv } from "@cap/env";
export const dub = () =>
new Dub({
token: serverEnv().DUB_API_KEY,
- });
\ No newline at end of file
+ });
diff --git a/packages/database/emails/config.ts b/packages/database/emails/config.ts
index 2bac32806..632d95f2e 100644
--- a/packages/database/emails/config.ts
+++ b/packages/database/emails/config.ts
@@ -1,20 +1,10 @@
import { buildEnv, serverEnv } from "@cap/env";
-import { render, renderAsync } from "@react-email/render";
import { JSXElementConstructor, ReactElement } from "react";
import { Resend } from "resend";
export const resend = () =>
serverEnv().RESEND_API_KEY ? new Resend(serverEnv().RESEND_API_KEY) : null;
-// Augment the CreateEmailOptions type to include scheduledAt
-type EmailOptions = {
- from: string;
- to: string | string[];
- subject: string;
- react: ReactElement>;
- scheduledAt?: string;
-};
-
export const sendEmail = async ({
email,
subject,
diff --git a/packages/database/package.json b/packages/database/package.json
index c0497f226..4b3f32bbf 100644
--- a/packages/database/package.json
+++ b/packages/database/package.json
@@ -15,6 +15,7 @@
},
"dependencies": {
"@cap/env": "workspace:*",
+ "@cap/web-domain": "workspace:*",
"@mattrax/mysql-planetscale": "^0.0.3",
"@paralleldrive/cuid2": "^2.2.2",
"@planetscale/database": "^1.13.0",
diff --git a/packages/database/schema.ts b/packages/database/schema.ts
index 9eecead15..688034510 100644
--- a/packages/database/schema.ts
+++ b/packages/database/schema.ts
@@ -13,6 +13,7 @@ import {
float,
} from "drizzle-orm/mysql-core";
import { relations } from "drizzle-orm/relations";
+import { Folder } from "@cap/web-domain";
import { nanoIdLength } from "./helpers";
import { VideoMetadata } from "./types";
@@ -204,7 +205,7 @@ export const organizationInvites = mysqlTable(
export const folders = mysqlTable(
"folders",
{
- id: nanoId("id").notNull().primaryKey().unique(),
+ id: nanoId("id").notNull().primaryKey().unique().$type(),
name: varchar("name", { length: 255 }).notNull(),
color: varchar("color", {
length: 16,
@@ -214,7 +215,7 @@ export const folders = mysqlTable(
.default("normal"),
organizationId: nanoId("organizationId").notNull(),
createdById: nanoId("createdById").notNull(),
- parentId: nanoIdNullable("parentId"),
+ parentId: nanoIdNullable("parentId").$type(),
spaceId: nanoIdNullable("spaceId"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow().onUpdateNow(),
@@ -233,23 +234,12 @@ export const videos = mysqlTable(
id: nanoId("id").notNull().primaryKey().unique(),
ownerId: nanoId("ownerId").notNull(),
name: varchar("name", { length: 255 }).notNull().default("My Video"),
- // DEPRECATED
- awsRegion: varchar("awsRegion", { length: 255 }),
- awsBucket: varchar("awsBucket", { length: 255 }),
bucket: nanoIdNullable("bucket"),
metadata: json("metadata").$type(),
public: boolean("public").notNull().default(true),
- password: encryptedTextNullable("password"),
- videoStartTime: varchar("videoStartTime", { length: 255 }),
- audioStartTime: varchar("audioStartTime", { length: 255 }),
- xStreamInfo: text("xStreamInfo"),
- jobId: varchar("jobId", { length: 255 }),
- jobStatus: varchar("jobStatus", { length: 255 }),
- isScreenshot: boolean("isScreenshot").notNull().default(false),
- skipProcessing: boolean("skipProcessing").notNull().default(false),
- transcriptionStatus: varchar("transcriptionStatus", { length: 255 }),
- createdAt: timestamp("createdAt").notNull().defaultNow(),
- updatedAt: timestamp("updatedAt").notNull().defaultNow().onUpdateNow(),
+ transcriptionStatus: varchar("transcriptionStatus", { length: 255 }).$type<
+ "PROCESSING" | "COMPLETE" | "ERROR"
+ >(),
source: json("source")
.$type<
{ type: "MediaConvert" } | { type: "local" } | { type: "desktopMP4" }
@@ -257,6 +247,21 @@ export const videos = mysqlTable(
.notNull()
.default({ type: "MediaConvert" }),
folderId: nanoIdNullable("folderId"),
+ createdAt: timestamp("createdAt").notNull().defaultNow(),
+ updatedAt: timestamp("updatedAt").notNull().defaultNow().onUpdateNow(),
+ // PRIVATE
+ password: encryptedTextNullable("password"),
+ // LEGACY
+ xStreamInfo: text("xStreamInfo"),
+ isScreenshot: boolean("isScreenshot").notNull().default(false),
+ // DEPRECATED
+ awsRegion: varchar("awsRegion", { length: 255 }),
+ awsBucket: varchar("awsBucket", { length: 255 }),
+ videoStartTime: varchar("videoStartTime", { length: 255 }),
+ audioStartTime: varchar("audioStartTime", { length: 255 }),
+ jobId: varchar("jobId", { length: 255 }),
+ jobStatus: varchar("jobStatus", { length: 255 }),
+ skipProcessing: boolean("skipProcessing").notNull().default(false),
},
(table) => ({
idIndex: index("id_idx").on(table.id),
diff --git a/packages/database/tsconfig.json b/packages/database/tsconfig.json
index 0c2367d79..303d6c6da 100644
--- a/packages/database/tsconfig.json
+++ b/packages/database/tsconfig.json
@@ -1,10 +1,12 @@
{
"extends": "../config/base.tsconfig.json",
- "exclude": ["node_modules"],
+ "exclude": ["node_modules", "dist"],
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
"compilerOptions": {
- "baseUrl": ".",
- "moduleResolution": "bundler",
+ "composite": true,
+ "outDir": "dist",
+ "noEmit": false,
+ "emitDeclarationOnly": true,
"paths": {
"@/emails/*": ["./emails/*"]
}
diff --git a/packages/env/package.json b/packages/env/package.json
index 92d6e358a..d50c7c83d 100644
--- a/packages/env/package.json
+++ b/packages/env/package.json
@@ -8,6 +8,6 @@
"zod": "^3.25.76"
},
"devDependencies": {
- "@types/node": "^22.12.0"
+ "@types/node": "^22.15.14"
}
}
diff --git a/packages/env/tsconfig.json b/packages/env/tsconfig.json
index 980872899..683dd71d3 100644
--- a/packages/env/tsconfig.json
+++ b/packages/env/tsconfig.json
@@ -1,9 +1,14 @@
{
"extends": "../config/base.tsconfig.json",
- "exclude": ["node_modules"],
+ "exclude": ["node_modules", "dist"],
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
"compilerOptions": {
"baseUrl": ".",
- "moduleResolution": "bundler"
+ "moduleResolution": "bundler",
+ "composite": true,
+ "outDir": "dist",
+ "noEmit": false,
+ "emitDeclarationOnly": true,
+ "types": ["node"]
}
}
diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json
index 6e9912992..b9941fdec 100644
--- a/packages/ui/tsconfig.json
+++ b/packages/ui/tsconfig.json
@@ -5,6 +5,10 @@
"compilerOptions": {
"rootDir": "src",
"baseUrl": ".",
+ "composite": true,
+ "outDir": "dist",
+ "noEmit": false,
+ "emitDeclarationOnly": true,
"paths": {
"@/*": ["./src/*"],
"@/utils/*": ["src/utils/*"]
diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json
index a74c980bc..9fe21b063 100644
--- a/packages/utils/tsconfig.json
+++ b/packages/utils/tsconfig.json
@@ -6,6 +6,10 @@
"rootDir": "src",
"baseUrl": ".",
"moduleResolution": "bundler",
+ "composite": true,
+ "outDir": "dist",
+ "noEmit": false,
+ "emitDeclarationOnly": true,
"paths": {
"@/*": ["./src/*"]
}
diff --git a/packages/web-api-contract-effect/package.json b/packages/web-api-contract-effect/package.json
index a78434b3b..f653b9b14 100644
--- a/packages/web-api-contract-effect/package.json
+++ b/packages/web-api-contract-effect/package.json
@@ -5,8 +5,7 @@
"types": "./src/index.ts",
"type": "module",
"dependencies": {
- "@effect/platform": "^0.87.1",
- "effect": "^3.16.10",
- "zod": "^3"
+ "@effect/platform": "^0.90.1",
+ "effect": "^3.17.7"
}
}
diff --git a/packages/web-api-contract-effect/src/index.ts b/packages/web-api-contract-effect/src/index.ts
index fc1f42e40..aa185720d 100644
--- a/packages/web-api-contract-effect/src/index.ts
+++ b/packages/web-api-contract-effect/src/index.ts
@@ -1,5 +1,13 @@
-import { HttpApi, HttpApiEndpoint, HttpApiGroup } from "@effect/platform";
+import {
+ HttpApi,
+ HttpApiEndpoint,
+ HttpApiError,
+ HttpApiGroup,
+ HttpApiMiddleware,
+ HttpServerError,
+} from "@effect/platform";
import * as Schema from "effect/Schema";
+import { Context, Data } from "effect";
const TranscriptionStatus = Schema.Literal("PROCESSING", "COMPLETE", "ERROR");
const OSType = Schema.Literal("macos", "windows");
@@ -40,6 +48,25 @@ const AuthHeaders = Schema.Struct({
authorization: Schema.String,
});
+export class User extends Data.Class<{
+ id: string;
+ email: string;
+ stripeSubscriptionStatus: string;
+ thirdPartyStripeSubscriptionId: string | null;
+ stripeSubscriptionId: string | null;
+ stripeCustomerId: string | null;
+}> {}
+
+export class Authentication extends Context.Tag("Authentication")<
+ Authentication,
+ { user: User }
+>() {}
+
+export class AuthMiddleware extends HttpApiMiddleware.Tag()(
+ "Authentication",
+ { provides: Authentication }
+) {}
+
export class ApiContract extends HttpApi.make("cap-web-api")
.add(
HttpApiGroup.make("video")
@@ -160,6 +187,7 @@ export class ApiContract extends HttpApi.make("cap-web-api")
.addError(Schema.Struct({ message: Schema.String }), { status: 500 })
)
)
+ .addError(HttpApiError.InternalServerError)
.prefix("/api") {}
export class LicenseApiContract extends HttpApi.make("cap-license-api").add(
diff --git a/packages/web-backend/README.md b/packages/web-backend/README.md
new file mode 100644
index 000000000..b0209223e
--- /dev/null
+++ b/packages/web-backend/README.md
@@ -0,0 +1,36 @@
+# `@cap/web-backend`
+
+Implementations for the backend services of `@cap/web`.
+
+## Services
+
+Code is organized horizontally by domain/entity (Video, Folder, stc),
+and then in the following way where applicable:
+
+- Repository Service (VideosRepo)
+- Policy Service (VideosPolicy)
+- Domain Service (Videos)
+
+#### Repository Services
+
+Wrap database queries for easier mocking and auditing.
+These services should not be exported from the package,
+as they do not handle access control.
+
+#### Policy Services
+
+Provide functions to determine if the CurrentUser has a given level
+of access to a resource.
+
+#### Domain Services
+
+Provide high-level functions to perform resource operations,
+consuming Repository Services for database access, Policy Services for access control,
+and other Domain Services for other business logic ie. S3 operations.
+Domain Services are safe to export and consume inside HTTP/RPC handlers.
+
+## RPC
+
+Some resources have RPC endpoint implementations as defined in `@cap/web-domain`.
+In a lot of cases, endpoint implementations are thin wrappers around Domain Services.
+Endpoint implementations should never directly use Repository Services or they may introduce security vulnerabilities.
diff --git a/packages/web-backend/package.json b/packages/web-backend/package.json
new file mode 100644
index 000000000..f1e31b53e
--- /dev/null
+++ b/packages/web-backend/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "@cap/web-backend",
+ "private": true,
+ "main": "./src/index.ts",
+ "types": "./src/index.ts",
+ "type": "module",
+ "dependencies": {
+ "@aws-sdk/client-s3": "^3.485.0",
+ "@aws-sdk/cloudfront-signer": "^3.821.0",
+ "@aws-sdk/s3-presigned-post": "^3.485.0",
+ "@aws-sdk/s3-request-presigner": "^3.485.0",
+ "@cap/database": "workspace:*",
+ "@cap/utils": "workspace:*",
+ "@cap/web-domain": "workspace:*",
+ "@effect/platform": "^0.90.1",
+ "@effect/rpc": "^0.68.3",
+ "@smithy/types": "^4.3.1",
+ "drizzle-orm": "0.43.1",
+ "effect": "^3.17.7",
+ "server-only": "^0.0.1",
+ "next": "14.2.3"
+ }
+}
diff --git a/packages/web-backend/src/Auth.ts b/packages/web-backend/src/Auth.ts
new file mode 100644
index 000000000..06085bdfd
--- /dev/null
+++ b/packages/web-backend/src/Auth.ts
@@ -0,0 +1,89 @@
+import * as Db from "@cap/database/schema";
+import * as Dz from "drizzle-orm";
+import { Effect, Layer, Option } from "effect";
+import { HttpApiError, HttpApp } from "@effect/platform";
+import { HttpAuthMiddleware, CurrentUser } from "@cap/web-domain";
+import { getServerSession } from "@cap/database/auth/auth-options";
+
+import { Database } from "./Database";
+
+export const getCurrentUser = Effect.gen(function* () {
+ const db = yield* Database;
+
+ return yield* Option.fromNullable(
+ yield* Effect.tryPromise(() => getServerSession())
+ ).pipe(
+ Option.map((session) =>
+ Effect.gen(function* () {
+ const [currentUser] = yield* db.execute((db) =>
+ db
+ .select()
+ .from(Db.users)
+ .where(Dz.eq(Db.users.id, (session.user as any).id))
+ );
+
+ return Option.fromNullable(currentUser);
+ })
+ ),
+ Effect.transposeOption,
+ Effect.map(Option.flatten)
+ );
+}).pipe(Effect.withSpan("getCurrentUser"));
+
+export const HttpAuthMiddlewareLive = Layer.effect(
+ HttpAuthMiddleware,
+ Effect.gen(function* () {
+ const database = yield* Database;
+
+ return HttpAuthMiddleware.of(
+ Effect.gen(function* () {
+ const user = yield* getCurrentUser.pipe(
+ Effect.flatten,
+ Effect.catchTag(
+ "NoSuchElementException",
+ () => new HttpApiError.Unauthorized()
+ )
+ );
+
+ return { id: user.id, email: user.email };
+ }).pipe(
+ Effect.provideService(Database, database),
+ Effect.catchTags({
+ UnknownException: () => new HttpApiError.InternalServerError(),
+ DatabaseError: () => new HttpApiError.InternalServerError(),
+ })
+ )
+ );
+ })
+);
+
+export const provideOptionalAuth = (
+ app: HttpApp.Default
+): HttpApp.Default<
+ E | HttpApiError.Unauthorized | HttpApiError.InternalServerError,
+ R | Database
+> =>
+ Effect.gen(function* () {
+ const user = yield* getCurrentUser;
+ return yield* user.pipe(
+ Option.map((user) =>
+ CurrentUser.context({
+ id: user.id,
+ email: user.email,
+ })
+ ),
+ Option.match({
+ onNone: () => app,
+ onSome: (ctx) => app.pipe(Effect.provide(ctx)),
+ })
+ );
+ }).pipe(
+ Effect.catchTag(
+ "DatabaseError",
+ () => new HttpApiError.InternalServerError()
+ ),
+ Effect.catchTag(
+ "UnknownException",
+ () => new HttpApiError.InternalServerError()
+ )
+ );
diff --git a/packages/web-backend/src/Database.ts b/packages/web-backend/src/Database.ts
new file mode 100644
index 000000000..ec4aa62b9
--- /dev/null
+++ b/packages/web-backend/src/Database.ts
@@ -0,0 +1,15 @@
+import { Context, Data, Effect } from "effect";
+import { db } from "@cap/database";
+
+export class Database extends Context.Tag("Database")<
+ Database,
+ {
+ execute(
+ callback: (_: ReturnType) => Promise
+ ): Effect.Effect;
+ }
+>() {}
+
+export class DatabaseError extends Data.TaggedError("DatabaseError")<{
+ message: string;
+}> {}
diff --git a/packages/web-backend/src/Folders/FoldersPolicy.ts b/packages/web-backend/src/Folders/FoldersPolicy.ts
new file mode 100644
index 000000000..efe73ae31
--- /dev/null
+++ b/packages/web-backend/src/Folders/FoldersPolicy.ts
@@ -0,0 +1,45 @@
+import { Folder, Policy } from "@cap/web-domain";
+import * as Db from "@cap/database/schema";
+import * as Dz from "drizzle-orm";
+import { Effect } from "effect";
+import { Database } from "../Database";
+
+export class FoldersPolicy extends Effect.Service()(
+ "FoldersPolicy",
+ {
+ effect: Effect.gen(function* () {
+ const db = yield* Database;
+
+ const canEdit = (id: Folder.FolderId) =>
+ Policy.policy((user) =>
+ Effect.gen(function* () {
+ const [folder] = yield* db.execute((db) =>
+ db.select().from(Db.folders).where(Dz.eq(Db.folders.id, id))
+ );
+
+ // All space members can edit space properties
+ if (!folder?.spaceId) {
+ return folder?.createdById === user.id;
+ }
+
+ const { spaceId } = folder;
+ const [spaceMember] = yield* db.execute((db) =>
+ db
+ .select()
+ .from(Db.spaceMembers)
+ .where(
+ Dz.and(
+ Dz.eq(Db.spaceMembers.userId, user.id),
+ Dz.eq(Db.spaceMembers.spaceId, spaceId)
+ )
+ )
+ );
+
+ return spaceMember !== undefined;
+ })
+ );
+
+ return { canEdit };
+ }),
+ }
+) {}
diff --git a/packages/web-backend/src/Folders/FoldersRpcs.ts b/packages/web-backend/src/Folders/FoldersRpcs.ts
new file mode 100644
index 000000000..9ec50684e
--- /dev/null
+++ b/packages/web-backend/src/Folders/FoldersRpcs.ts
@@ -0,0 +1,22 @@
+import { InternalError, Folder } from "@cap/web-domain";
+import { Effect } from "effect";
+
+import { Folders } from ".";
+
+export const FolderRpcsLive = Folder.FolderRpcs.toLayer(
+ Effect.gen(function* () {
+ const folders = yield* Folders;
+
+ return {
+ FolderDelete: (folderId) =>
+ folders
+ .delete(folderId)
+ .pipe(
+ Effect.catchTag(
+ "DatabaseError",
+ () => new InternalError({ type: "database" })
+ )
+ ),
+ };
+ })
+);
diff --git a/packages/web-backend/src/Folders/index.ts b/packages/web-backend/src/Folders/index.ts
new file mode 100644
index 000000000..2238fdd4a
--- /dev/null
+++ b/packages/web-backend/src/Folders/index.ts
@@ -0,0 +1,82 @@
+import { Effect } from "effect";
+import { Folder, Policy } from "@cap/web-domain";
+import * as Db from "@cap/database/schema";
+import * as Dz from "drizzle-orm";
+
+import { Database, DatabaseError } from "../Database";
+import { FoldersPolicy } from "./FoldersPolicy";
+
+export class Folders extends Effect.Service()("Folders", {
+ effect: Effect.gen(function* () {
+ const db = yield* Database;
+ const policy = yield* FoldersPolicy;
+
+ const deleteFolder = (folder: {
+ id: Folder.FolderId;
+ parentId: Folder.FolderId | null;
+ spaceId: string | null;
+ }): Effect.Effect =>
+ Effect.gen(function* () {
+ const children = yield* db.execute((db) =>
+ db
+ .select({
+ id: Db.folders.id,
+ parentId: Db.folders.parentId,
+ spaceId: Db.folders.spaceId,
+ })
+ .from(Db.folders)
+ .where(Dz.eq(Db.folders.parentId, folder.id))
+ );
+
+ for (const child of children) {
+ yield* deleteFolder(child);
+ }
+
+ // Folders can't be both in the root and in a space
+ if (folder.spaceId) {
+ const { spaceId } = folder;
+ yield* db.execute((db) =>
+ db
+ .update(Db.spaceVideos)
+ .set({ folderId: folder.parentId })
+ .where(
+ Dz.and(
+ Dz.eq(Db.spaceVideos.folderId, folder.id),
+ Dz.eq(Db.spaceVideos.spaceId, spaceId)
+ )
+ )
+ );
+ } else {
+ yield* db.execute((db) =>
+ db
+ .update(Db.videos)
+ .set({ folderId: folder.parentId })
+ .where(Dz.eq(Db.videos.folderId, folder.id))
+ );
+ }
+
+ yield* db.execute((db) =>
+ db.delete(Db.folders).where(Dz.eq(Db.folders.id, folder.id))
+ );
+ });
+
+ return {
+ /**
+ * Deletes a folder and all its subfolders. Videos inside the folders will be
+ * relocated to the root of the collection (space or My Caps) they're in
+ */
+ delete: Effect.fn("Folders.delete")(function* (id: Folder.FolderId) {
+ const [folder] = yield* db
+ .execute((db) =>
+ db.select().from(Db.folders).where(Dz.eq(Db.folders.id, id))
+ )
+ .pipe(Policy.withPolicy(policy.canEdit(id)));
+
+ if (!folder) return yield* new Folder.NotFoundError();
+
+ yield* deleteFolder(folder);
+ }),
+ };
+ }),
+ dependencies: [FoldersPolicy.Default],
+}) {}
diff --git a/packages/web-backend/src/Rpcs.ts b/packages/web-backend/src/Rpcs.ts
new file mode 100644
index 000000000..d08abde37
--- /dev/null
+++ b/packages/web-backend/src/Rpcs.ts
@@ -0,0 +1,33 @@
+import { Effect, Layer, Option } from "effect";
+import {
+ InternalError,
+ RpcAuthMiddleware,
+ UnauthenticatedError,
+} from "@cap/web-domain";
+
+import { getCurrentUser } from "./Auth";
+import { Database } from "./Database";
+import { VideosRpcsLive } from "./Videos/VideosRpcs";
+import { FolderRpcsLive } from "./Folders/FoldersRpcs";
+
+export const RpcsLive = Layer.mergeAll(VideosRpcsLive, FolderRpcsLive);
+
+export const RpcAuthMiddlewareLive = Layer.effect(
+ RpcAuthMiddleware,
+ Effect.gen(function* () {
+ const database = yield* Database;
+
+ return RpcAuthMiddleware.of(() =>
+ getCurrentUser.pipe(
+ Effect.provideService(Database, database),
+ Effect.catchAll(() => new InternalError({ type: "database" })),
+ Effect.flatMap(
+ Option.match({
+ onNone: () => new UnauthenticatedError(),
+ onSome: Effect.succeed,
+ })
+ )
+ )
+ );
+ })
+);
diff --git a/packages/web-backend/src/S3Buckets/S3BucketAccess.ts b/packages/web-backend/src/S3Buckets/S3BucketAccess.ts
new file mode 100644
index 000000000..2d7d87a12
--- /dev/null
+++ b/packages/web-backend/src/S3Buckets/S3BucketAccess.ts
@@ -0,0 +1,210 @@
+import * as S3 from "@aws-sdk/client-s3";
+import {
+ RequestPresigningArguments,
+ StreamingBlobPayloadInputTypes,
+} from "@smithy/types";
+import {
+ createPresignedPost,
+ PresignedPostOptions,
+} from "@aws-sdk/s3-presigned-post";
+import { Data, Effect, Option } from "effect";
+import * as S3Presigner from "@aws-sdk/s3-request-presigner";
+import { S3BucketClientProvider } from "./S3BucketClientProvider";
+
+export class S3Error extends Data.TaggedError("S3Error")<{ message: string }> {}
+
+const wrapS3Promise = (
+ callback: (provider: S3BucketClientProvider["Type"]) => Promise
+) =>
+ Effect.flatMap(S3BucketClientProvider, (provider) =>
+ Effect.tryPromise({
+ try: () => callback(provider),
+ catch: (e) => new S3Error({ message: String(e) }),
+ })
+ );
+
+// @effect-diagnostics-next-line leakingRequirements:off
+export class S3BucketAccess extends Effect.Service()(
+ "S3BucketAccess",
+ {
+ sync: () => ({
+ bucketName: Effect.map(S3BucketClientProvider, (p) => p.bucket),
+ getSignedObjectUrl: (key: string) =>
+ wrapS3Promise((provider) =>
+ S3Presigner.getSignedUrl(
+ provider.getPublic(),
+ new S3.GetObjectCommand({ Bucket: provider.bucket, Key: key }),
+ { expiresIn: 3600 }
+ )
+ ),
+ getObject: (key: string) =>
+ wrapS3Promise(async (provider) => {
+ const a = await provider
+ .getInternal()
+ .send(
+ new S3.GetObjectCommand({ Bucket: provider.bucket, Key: key })
+ )
+ .then((resp) => resp.Body?.transformToString())
+ .catch((e) => {
+ if (e instanceof S3.NoSuchKey) {
+ return null;
+ } else {
+ throw e;
+ }
+ });
+
+ return Option.fromNullable(a);
+ }),
+ listObjects: (config: { prefix?: string; maxKeys?: number }) =>
+ wrapS3Promise((provider) =>
+ provider.getInternal().send(
+ new S3.ListObjectsV2Command({
+ Bucket: provider.bucket,
+ Prefix: config?.prefix,
+ MaxKeys: config?.maxKeys,
+ })
+ )
+ ),
+ headObject: (key: string) =>
+ wrapS3Promise((provider) =>
+ provider
+ .getInternal()
+ .send(
+ new S3.HeadObjectCommand({ Bucket: provider.bucket, Key: key })
+ )
+ ),
+ putObject: (
+ key: string,
+ body: StreamingBlobPayloadInputTypes,
+ fields?: { contentType?: string }
+ ) =>
+ wrapS3Promise((provider) =>
+ provider.getInternal().send(
+ new S3.PutObjectCommand({
+ Bucket: provider.bucket,
+ Key: key,
+ Body: body,
+ ContentType: fields?.contentType,
+ })
+ )
+ ),
+ /** Copy an object within the same bucket */
+ copyObject: (
+ source: string,
+ key: string,
+ args?: Omit
+ ) =>
+ wrapS3Promise((provider) =>
+ provider.getInternal().send(
+ new S3.CopyObjectCommand({
+ Bucket: provider.bucket,
+ CopySource: source,
+ Key: key,
+ ...args,
+ })
+ )
+ ),
+ deleteObject: (key: string) =>
+ wrapS3Promise((provider) =>
+ provider.getInternal().send(
+ new S3.DeleteObjectCommand({
+ Bucket: provider.bucket,
+ Key: key,
+ })
+ )
+ ),
+ deleteObjects: (objects: S3.ObjectIdentifier[]) =>
+ wrapS3Promise((provider) =>
+ provider.getInternal().send(
+ new S3.DeleteObjectsCommand({
+ Bucket: provider.bucket,
+ Delete: {
+ Objects: objects,
+ },
+ })
+ )
+ ),
+ getPresignedPutUrl: (
+ key: string,
+ args?: Omit,
+ signingArgs?: RequestPresigningArguments
+ ) =>
+ wrapS3Promise((provider) =>
+ S3Presigner.getSignedUrl(
+ provider.getPublic(),
+ new S3.PutObjectCommand({
+ Bucket: provider.bucket,
+ Key: key,
+ ...args,
+ }),
+ signingArgs
+ )
+ ),
+ getPresignedPostUrl: (
+ key: string,
+ args: Omit
+ ) =>
+ wrapS3Promise((provider) =>
+ createPresignedPost(provider.getPublic(), {
+ ...args,
+ Bucket: provider.bucket,
+ Key: key,
+ })
+ ),
+ multipart: {
+ create: (
+ key: string,
+ args?: Omit
+ ) =>
+ wrapS3Promise((provider) =>
+ provider.getInternal().send(
+ new S3.CreateMultipartUploadCommand({
+ ...args,
+ Bucket: provider.bucket,
+ Key: key,
+ })
+ )
+ ),
+ getPresignedUploadPartUrl: (
+ key: string,
+ uploadId: string,
+ partNumber: number,
+ args?: Omit<
+ S3.UploadPartCommandInput,
+ "Key" | "Bucket" | "PartNumber" | "UploadId"
+ >
+ ) =>
+ wrapS3Promise((provider) =>
+ S3Presigner.getSignedUrl(
+ provider.getPublic(),
+ new S3.UploadPartCommand({
+ ...args,
+ Bucket: provider.bucket,
+ Key: key,
+ UploadId: uploadId,
+ PartNumber: partNumber,
+ })
+ )
+ ),
+ complete: (
+ key: string,
+ uploadId: string,
+ args?: Omit<
+ S3.CompleteMultipartUploadCommandInput,
+ "Key" | "Bucket" | "UploadId"
+ >
+ ) =>
+ wrapS3Promise((provider) =>
+ provider.getInternal().send(
+ new S3.CompleteMultipartUploadCommand({
+ Bucket: provider.bucket,
+ Key: key,
+ UploadId: uploadId,
+ ...args,
+ })
+ )
+ ),
+ },
+ }),
+ }
+) {}
diff --git a/packages/web-backend/src/S3Buckets/S3BucketClientProvider.ts b/packages/web-backend/src/S3Buckets/S3BucketClientProvider.ts
new file mode 100644
index 000000000..ff7959c43
--- /dev/null
+++ b/packages/web-backend/src/S3Buckets/S3BucketClientProvider.ts
@@ -0,0 +1,9 @@
+import { S3Client } from "@aws-sdk/client-s3";
+import { Context } from "effect";
+
+export class S3BucketClientProvider extends Context.Tag(
+ "S3BucketClientProvider"
+)<
+ S3BucketClientProvider,
+ { getInternal: () => S3Client; getPublic: () => S3Client; bucket: string }
+>() {}
diff --git a/packages/web-backend/src/S3Buckets/S3BucketsRepo.ts b/packages/web-backend/src/S3Buckets/S3BucketsRepo.ts
new file mode 100644
index 000000000..e0fd846a8
--- /dev/null
+++ b/packages/web-backend/src/S3Buckets/S3BucketsRepo.ts
@@ -0,0 +1,54 @@
+import * as Db from "@cap/database/schema";
+import * as Dz from "drizzle-orm";
+import { S3Bucket, Video } from "@cap/web-domain";
+import { Effect, Option } from "effect";
+
+import { Database } from "../Database";
+
+export class S3BucketsRepo extends Effect.Service()(
+ "S3BucketsRepo",
+ {
+ effect: Effect.gen(function* () {
+ const db = yield* Database;
+
+ const getForVideo = Effect.fn("S3BucketsRepo.getForVideo")(
+ (videoId: Video.VideoId) =>
+ Effect.gen(function* () {
+ const [res] = yield* db.execute((db) =>
+ db
+ .select({ bucket: Db.s3Buckets })
+ .from(Db.s3Buckets)
+ .leftJoin(Db.videos, Dz.eq(Db.videos.bucket, Db.s3Buckets.id))
+ .where(Dz.and(Dz.eq(Db.videos.id, videoId)))
+ );
+
+ return Option.fromNullable(res).pipe(
+ Option.map((v) =>
+ S3Bucket.decodeSync({ ...v.bucket, name: v.bucket.bucketName })
+ )
+ );
+ })
+ );
+
+ const getById = Effect.fn("S3BucketsRepo.getById")(
+ (id: S3Bucket.S3BucketId) =>
+ Effect.gen(function* () {
+ const [res] = yield* db.execute((db) =>
+ db
+ .select({ bucket: Db.s3Buckets })
+ .from(Db.s3Buckets)
+ .where(Dz.eq(Db.s3Buckets.id, id))
+ );
+
+ return Option.fromNullable(res).pipe(
+ Option.map((v) =>
+ S3Bucket.decodeSync({ ...v.bucket, name: v.bucket.bucketName })
+ )
+ );
+ })
+ );
+
+ return { getForVideo, getById };
+ }),
+ }
+) {}
diff --git a/packages/web-backend/src/S3Buckets/index.ts b/packages/web-backend/src/S3Buckets/index.ts
new file mode 100644
index 000000000..4339b9ea0
--- /dev/null
+++ b/packages/web-backend/src/S3Buckets/index.ts
@@ -0,0 +1,167 @@
+import { S3Bucket } from "@cap/web-domain";
+import { Config, Context, Effect, Layer, Option } from "effect";
+import * as S3 from "@aws-sdk/client-s3";
+import * as CloudFrontPresigner from "@aws-sdk/cloudfront-signer";
+import { S3_BUCKET_URL } from "@cap/utils";
+
+import { S3BucketsRepo } from "./S3BucketsRepo";
+import { S3BucketAccess } from "./S3BucketAccess";
+import { S3BucketClientProvider } from "./S3BucketClientProvider";
+
+export class S3Buckets extends Effect.Service()("S3Buckets", {
+ effect: Effect.gen(function* () {
+ const repo = yield* S3BucketsRepo;
+
+ const defaultConfigs = {
+ publicEndpoint: yield* Config.string("S3_PUBLIC_ENDPOINT").pipe(
+ Config.orElse(() => Config.string("CAP_AWS_ENDPOINT")),
+ Config.option,
+ Effect.flatten,
+ Effect.catchTag("NoSuchElementException", () =>
+ Effect.dieMessage(
+ "Neither S3_PUBLIC_ENDPOINT nor CAP_AWS_ENDPOINT provided"
+ )
+ )
+ ),
+ internalEndpoint: yield* Config.string("S3_INTERNAL_ENDPOINT").pipe(
+ Config.orElse(() => Config.string("CAP_AWS_ENDPOINT")),
+ Config.option,
+ Effect.flatten,
+ Effect.catchTag("NoSuchElementException", () =>
+ Effect.dieMessage(
+ "Neither S3_INTERNAL_ENDPOINT nor CAP_AWS_ENDPOINT provided"
+ )
+ )
+ ),
+ region: yield* Config.string("CAP_AWS_REGION"),
+ accessKey: yield* Config.string("CAP_AWS_ACCESS_KEY"),
+ secretKey: yield* Config.string("CAP_AWS_SECRET_KEY"),
+ forcePathStyle:
+ Option.getOrNull(
+ yield* Config.boolean("S3_PATH_STYLE").pipe(Config.option)
+ ) ?? true,
+ bucket: yield* Config.string("CAP_AWS_BUCKET"),
+ };
+
+ const createDefaultClient = (internal: boolean) =>
+ new S3.S3Client({
+ endpoint: internal
+ ? defaultConfigs.internalEndpoint
+ : defaultConfigs.publicEndpoint,
+ region: defaultConfigs.region,
+ credentials: {
+ accessKeyId: defaultConfigs.accessKey,
+ secretAccessKey: defaultConfigs.secretKey,
+ },
+ forcePathStyle: defaultConfigs.forcePathStyle,
+ });
+
+ const createBucketClient = (bucket: S3Bucket.S3Bucket) =>
+ new S3.S3Client({
+ endpoint: bucket.endpoint.pipe(Option.getOrUndefined),
+ region: bucket.region,
+ credentials: {
+ accessKeyId: bucket.accessKeyId,
+ secretAccessKey: bucket.secretAccessKey,
+ },
+ forcePathStyle:
+ bucket.endpoint.pipe(
+ Option.map((e) => e.endsWith("s3.amazonaws.com")),
+ Option.getOrNull
+ ) ?? true,
+ useArnRegion: false,
+ });
+
+ const defaultBucketAccess = S3BucketAccess.Default;
+
+ const cloudfrontEnvs = yield* Config.all({
+ distributionId: Config.string("CAP_CLOUDFRONT_DISTRIBUTION_ID"),
+ keypairId: Config.string("CLOUDFRONT_KEYPAIR_ID"),
+ privateKey: Config.string("CLOUDFRONT_KEYPAIR_PRIVATE_KEY"),
+ }).pipe(
+ Effect.match({
+ onSuccess: (v) => v,
+ onFailure: () => null,
+ }),
+ Effect.map(Option.fromNullable)
+ );
+
+ const cloudfrontBucketAccess = cloudfrontEnvs.pipe(
+ Option.map((cloudfrontEnvs) =>
+ Layer.map(defaultBucketAccess, (context) => {
+ const s3 = Context.get(context, S3BucketAccess);
+
+ return Context.make(S3BucketAccess, {
+ ...s3,
+ getSignedObjectUrl: (key) => {
+ const url = `${S3_BUCKET_URL}/${key}`;
+ const expires = Math.floor((Date.now() + 3600 * 1000) / 1000);
+
+ const policy = {
+ Statement: [
+ {
+ Resource: url,
+ Condition: {
+ DateLessThan: {
+ "AWS:EpochTime": Math.floor(expires),
+ },
+ },
+ },
+ ],
+ };
+
+ return Effect.succeed(
+ CloudFrontPresigner.getSignedUrl({
+ url,
+ keyPairId: cloudfrontEnvs.keypairId,
+ privateKey: cloudfrontEnvs.privateKey,
+ policy: JSON.stringify(policy),
+ })
+ );
+ },
+ });
+ })
+ )
+ );
+
+ return {
+ getProviderLayer: Effect.fn("S3Buckets.getProviderLayer")(function* (
+ bucketId: Option.Option
+ ) {
+ const customBucket = yield* bucketId.pipe(
+ Option.map(repo.getById),
+ Effect.transposeOption,
+ Effect.map(Option.flatten)
+ );
+
+ let layer;
+
+ if (Option.isNone(customBucket)) {
+ const provider = Layer.succeed(S3BucketClientProvider, {
+ getInternal: () => createDefaultClient(true),
+ getPublic: () => createDefaultClient(false),
+ bucket: defaultConfigs.bucket,
+ });
+
+ layer = Option.match(cloudfrontBucketAccess, {
+ onSome: (access) => access,
+ onNone: () => defaultBucketAccess,
+ }).pipe(Layer.merge(provider));
+ } else {
+ layer = defaultBucketAccess.pipe(
+ Layer.merge(
+ Layer.succeed(S3BucketClientProvider, {
+ getInternal: () => createBucketClient(customBucket.value),
+ getPublic: () => createBucketClient(customBucket.value),
+ bucket: customBucket.value.name,
+ })
+ )
+ );
+ }
+
+ return [layer, customBucket] as const;
+ }),
+ };
+ }),
+ dependencies: [S3BucketsRepo.Default],
+}) {}
diff --git a/packages/web-backend/src/Videos/VideosPolicy.ts b/packages/web-backend/src/Videos/VideosPolicy.ts
new file mode 100644
index 000000000..2e2c9d8e7
--- /dev/null
+++ b/packages/web-backend/src/Videos/VideosPolicy.ts
@@ -0,0 +1,51 @@
+import { Effect, Option } from "effect";
+import { Policy, Video } from "@cap/web-domain";
+
+import { VideosRepo } from "./VideosRepo";
+
+export class VideosPolicy extends Effect.Service()(
+ "VideosPolicy",
+ {
+ effect: Effect.gen(function* () {
+ const repo = yield* VideosRepo;
+
+ const canView = (videoId: Video.VideoId) =>
+ Policy.publicPolicy(
+ Effect.fn(function* (user) {
+ const res = yield* repo.getById(videoId);
+
+ if (Option.isNone(res)) return true;
+
+ const [video, password] = res.value;
+
+ if (
+ user.pipe(
+ Option.filter((user) => user.id === video.ownerId),
+ Option.isSome
+ )
+ )
+ return true;
+
+ yield* Video.verifyPassword(video, password);
+
+ return true;
+ })
+ );
+
+ const isOwner = (videoId: Video.VideoId) =>
+ Policy.policy((user) =>
+ repo.getById(videoId).pipe(
+ Effect.map(
+ Option.match({
+ onNone: () => true,
+ onSome: ([video]) => video.ownerId === user.id,
+ })
+ )
+ )
+ );
+
+ return { canView, isOwner };
+ }),
+ dependencies: [VideosRepo.Default],
+ }
+) {}
diff --git a/packages/web-backend/src/Videos/VideosRepo.ts b/packages/web-backend/src/Videos/VideosRepo.ts
new file mode 100644
index 000000000..a7cd5caa4
--- /dev/null
+++ b/packages/web-backend/src/Videos/VideosRepo.ts
@@ -0,0 +1,78 @@
+import { Effect, Option } from "effect";
+import * as Db from "@cap/database/schema";
+import * as Dz from "drizzle-orm";
+import { Video } from "@cap/web-domain";
+
+import { Database } from "../Database";
+import { nanoId } from "@cap/database/helpers";
+
+export class VideosRepo extends Effect.Service()("VideosRepo", {
+ effect: Effect.gen(function* () {
+ const db = yield* Database;
+
+ return {
+ /**
+ * Gets a `Video` and its accompanying password if available.
+ *
+ * The password is returned separately as the `Video` class is client-safe
+ */
+ getById: (id: Video.VideoId) =>
+ Effect.gen(function* () {
+ const [video] = yield* db.execute((db) =>
+ db.select().from(Db.videos).where(Dz.eq(Db.videos.id, id))
+ );
+
+ return Option.fromNullable(video).pipe(
+ Option.map(
+ (v) =>
+ [
+ Video.Video.decodeSync({
+ ...v,
+ bucketId: v.bucket,
+ createdAt: v.createdAt.toISOString(),
+ updatedAt: v.updatedAt.toISOString(),
+ metadata: v.metadata as any,
+ }),
+ Option.fromNullable(video?.password),
+ ] as const
+ )
+ );
+ }),
+ delete: (id: Video.VideoId) =>
+ db.execute((db) => db.delete(Db.videos).where(Dz.eq(Db.videos.id, id))),
+ create: (
+ data: Pick<
+ (typeof Video.Video)["Encoded"],
+ | "ownerId"
+ | "name"
+ | "bucketId"
+ | "metadata"
+ | "public"
+ | "transcriptionStatus"
+ | "source"
+ | "folderId"
+ > & { password?: string }
+ ) => {
+ const id = nanoId();
+
+ return db.execute((db) =>
+ db
+ .insert(Db.videos)
+ .values({
+ id,
+ ownerId: data.ownerId,
+ name: data.name,
+ bucket: data.bucketId,
+ metadata: data.metadata,
+ public: data.public,
+ transcriptionStatus: data.transcriptionStatus,
+ source: data.source,
+ folderId: data.folderId,
+ password: data.password,
+ })
+ .then(() => Video.VideoId.make(id))
+ );
+ },
+ };
+ }),
+}) {}
diff --git a/packages/web-backend/src/Videos/VideosRpcs.ts b/packages/web-backend/src/Videos/VideosRpcs.ts
new file mode 100644
index 000000000..cdcf2cd2d
--- /dev/null
+++ b/packages/web-backend/src/Videos/VideosRpcs.ts
@@ -0,0 +1,27 @@
+import { InternalError, Video } from "@cap/web-domain";
+import { Effect } from "effect";
+
+import { Videos } from ".";
+
+export const VideosRpcsLive = Video.VideoRpcs.toLayer(
+ Effect.gen(function* () {
+ const videos = yield* Videos;
+
+ return {
+ VideoDelete: (videoId) =>
+ videos.delete(videoId).pipe(
+ Effect.catchTags({
+ DatabaseError: () => new InternalError({ type: "database" }),
+ S3Error: () => new InternalError({ type: "s3" }),
+ })
+ ),
+ VideoDuplicate: (videoId) =>
+ videos.duplicate(videoId).pipe(
+ Effect.catchTags({
+ DatabaseError: () => new InternalError({ type: "database" }),
+ S3Error: () => new InternalError({ type: "s3" }),
+ })
+ ),
+ };
+ })
+);
diff --git a/packages/web-backend/src/Videos/index.ts b/packages/web-backend/src/Videos/index.ts
new file mode 100644
index 000000000..a0f7e8e69
--- /dev/null
+++ b/packages/web-backend/src/Videos/index.ts
@@ -0,0 +1,110 @@
+import { Array, Effect, Option } from "effect";
+import { CurrentUser, Policy, Video } from "@cap/web-domain";
+
+import { VideosPolicy } from "./VideosPolicy";
+import { VideosRepo } from "./VideosRepo";
+import { S3Buckets } from "../S3Buckets";
+import { S3BucketAccess } from "../S3Buckets/S3BucketAccess";
+
+export class Videos extends Effect.Service()("Videos", {
+ effect: Effect.gen(function* () {
+ const repo = yield* VideosRepo;
+ const policy = yield* VideosPolicy;
+ const s3Buckets = yield* S3Buckets;
+
+ return {
+ /*
+ * Get a video by ID. Will fail if the user does not have access.
+ */
+ // This is only for external use since it does an access check,
+ // internal use should prefer the repo directly
+ getById: (id: Video.VideoId) =>
+ repo
+ .getById(id)
+ .pipe(
+ Policy.withPublicPolicy(policy.canView(id)),
+ Effect.withSpan("Videos.getById")
+ ),
+
+ /*
+ * Delete a video. Will fail if the user does not have access.
+ */
+ delete: Effect.fn("Videos.delete")(function* (videoId: Video.VideoId) {
+ const [video] = yield* repo
+ .getById(videoId)
+ .pipe(
+ Effect.flatMap(Effect.catchAll(() => new Video.NotFoundError()))
+ );
+
+ const [S3ProviderLayer] = yield* s3Buckets.getProviderLayer(
+ video.bucketId
+ );
+
+ yield* repo
+ .delete(video.id)
+ .pipe(Policy.withPolicy(policy.isOwner(video.id)));
+
+ yield* Effect.gen(function* () {
+ const s3 = yield* S3BucketAccess;
+ const user = yield* CurrentUser;
+
+ const prefix = `${user.id}/${video.id}/`;
+
+ const listedObjects = yield* s3.listObjects({ prefix });
+
+ if (listedObjects.Contents?.length) {
+ yield* s3.deleteObjects(
+ listedObjects.Contents.map((content) => ({
+ Key: content.Key,
+ }))
+ );
+ }
+ }).pipe(Effect.provide(S3ProviderLayer));
+ }),
+
+ /*
+ * Duplicates a video, its metadata, and its media files.
+ * Comments and reactions will not be duplicated or carried over.
+ */
+ duplicate: Effect.fn("Videos.duplicate")(function* (
+ videoId: Video.VideoId
+ ) {
+ const [video] = yield* repo
+ .getById(videoId)
+ .pipe(
+ Effect.flatMap(Effect.catchAll(() => new Video.NotFoundError())),
+ Policy.withPolicy(policy.isOwner(videoId))
+ );
+
+ const [S3ProviderLayer] = yield* s3Buckets.getProviderLayer(
+ video.bucketId
+ );
+
+ // Don't duplicate password or sharing data
+ const newVideoId = yield* repo.create(yield* video.toJS());
+
+ yield* Effect.gen(function* () {
+ const s3 = yield* S3BucketAccess;
+ const bucketName = yield* s3.bucketName;
+
+ const prefix = `${video.ownerId}/${video.id}/`;
+ const newPrefix = `${video.ownerId}/${newVideoId}/`;
+
+ const allObjects = yield* s3.listObjects({ prefix });
+
+ if (allObjects.Contents)
+ yield* Effect.all(
+ Array.filterMap(allObjects.Contents, (obj) =>
+ Option.map(Option.fromNullable(obj.Key), (key) => {
+ const newKey = key.replace(prefix, newPrefix);
+ return s3.copyObject(`${bucketName}/${obj.Key}`, newKey);
+ })
+ ),
+ { concurrency: 1 }
+ );
+ }).pipe(Effect.provide(S3ProviderLayer));
+ }),
+ };
+ }),
+ dependencies: [VideosPolicy.Default, VideosRepo.Default, S3Buckets.Default],
+}) {}
diff --git a/packages/web-backend/src/index.ts b/packages/web-backend/src/index.ts
new file mode 100644
index 000000000..3697593bc
--- /dev/null
+++ b/packages/web-backend/src/index.ts
@@ -0,0 +1,9 @@
+import "server-only";
+
+export * from "./Rpcs";
+export * from "./Auth";
+export { Videos } from "./Videos";
+export { S3Buckets } from "./S3Buckets";
+export { S3BucketAccess } from "./S3Buckets/S3BucketAccess";
+export { Folders } from "./Folders";
+export * from "./Database";
diff --git a/packages/web-backend/tsconfig.json b/packages/web-backend/tsconfig.json
new file mode 100644
index 000000000..32f7bc4fa
--- /dev/null
+++ b/packages/web-backend/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "extends": "../config/base.tsconfig.json",
+ "exclude": ["node_modules", "dist"],
+ "include": ["**/*.ts", "**/*.tsx"],
+ "compilerOptions": {
+ "moduleResolution": "bundler",
+ "composite": true,
+ "outDir": "dist",
+ "noEmit": false,
+ "emitDeclarationOnly": true,
+ "strict": true,
+ "noUncheckedIndexedAccess": true
+ },
+ "references": [{ "path": "../web-domain" }, { "path": "../database" }]
+}
diff --git a/packages/web-domain/README.md b/packages/web-domain/README.md
new file mode 100644
index 000000000..f751bfd4e
--- /dev/null
+++ b/packages/web-domain/README.md
@@ -0,0 +1,10 @@
+# `@cap/web-domain`
+
+Types, functions, definitions, and more that comprise the domain of `@cap/web`.
+All code in this package is designed to be shared across the client and server.
+
+Things in this package include but are not limited to:
+- Domain data types (`Video.Video`, `Video.VideoId`)
+- RPC definitions (`VideoRpcs`)
+- Full-stack errors (`InternalError`)
+- Client-safe utilities (`Policy`)
diff --git a/packages/web-domain/package.json b/packages/web-domain/package.json
new file mode 100644
index 000000000..1e686ffd0
--- /dev/null
+++ b/packages/web-domain/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@cap/web-domain",
+ "private": true,
+ "main": "./src/index.ts",
+ "types": "./src/index.ts",
+ "type": "module",
+ "dependencies": {
+ "@effect/platform": "^0.90.1",
+ "@effect/rpc": "^0.68.3",
+ "effect": "^3.17.7"
+ }
+}
diff --git a/packages/web-domain/src/Authentication.ts b/packages/web-domain/src/Authentication.ts
new file mode 100644
index 000000000..ad041f409
--- /dev/null
+++ b/packages/web-domain/src/Authentication.ts
@@ -0,0 +1,33 @@
+import { Context, Schema } from "effect";
+import { HttpApiError, HttpApiMiddleware } from "@effect/platform";
+import { RpcMiddleware } from "@effect/rpc";
+import { InternalError } from "./Errors";
+
+export class CurrentUser extends Context.Tag("CurrentUser")<
+ CurrentUser,
+ { id: string; email: string }
+>() {}
+
+export class HttpAuthMiddleware extends HttpApiMiddleware.Tag()(
+ "HttpAuthMiddleware",
+ {
+ provides: CurrentUser,
+ failure: Schema.Union(
+ HttpApiError.Unauthorized,
+ HttpApiError.InternalServerError
+ ),
+ }
+) {}
+
+export class UnauthenticatedError extends Schema.TaggedError()(
+ "UnauthenticatedError",
+ {}
+) {}
+
+export class RpcAuthMiddleware extends RpcMiddleware.Tag()(
+ "RpcAuthMiddleware",
+ {
+ provides: CurrentUser,
+ failure: Schema.Union(InternalError, UnauthenticatedError),
+ }
+) {}
diff --git a/packages/web-domain/src/Errors.ts b/packages/web-domain/src/Errors.ts
new file mode 100644
index 000000000..acbb3e890
--- /dev/null
+++ b/packages/web-domain/src/Errors.ts
@@ -0,0 +1,6 @@
+import { Schema } from "effect";
+
+export class InternalError extends Schema.TaggedError()(
+ "InternalError",
+ { type: Schema.Literal("database", "s3") }
+) {}
diff --git a/packages/web-domain/src/Folder.ts b/packages/web-domain/src/Folder.ts
new file mode 100644
index 000000000..aee50a986
--- /dev/null
+++ b/packages/web-domain/src/Folder.ts
@@ -0,0 +1,20 @@
+import { Rpc, RpcGroup } from "@effect/rpc";
+import { Schema } from "effect";
+import { InternalError } from "./Errors";
+import { PolicyDeniedError } from "./Policy";
+import { RpcAuthMiddleware } from "./Authentication";
+
+export const FolderId = Schema.String.pipe(Schema.brand("FolderId"));
+export type FolderId = typeof FolderId.Type;
+
+export class NotFoundError extends Schema.TaggedError()(
+ "FolderNotFoundError",
+ {}
+) {}
+
+export class FolderRpcs extends RpcGroup.make(
+ Rpc.make("FolderDelete", {
+ payload: FolderId,
+ error: Schema.Union(NotFoundError, InternalError, PolicyDeniedError),
+ }).middleware(RpcAuthMiddleware)
+) {}
diff --git a/packages/web-domain/src/Organisation.ts b/packages/web-domain/src/Organisation.ts
new file mode 100644
index 000000000..0acdd6079
--- /dev/null
+++ b/packages/web-domain/src/Organisation.ts
@@ -0,0 +1,6 @@
+import { Schema } from "effect";
+
+export class Organisation extends Schema.Class("Organisation")({
+ id: Schema.String.pipe(Schema.brand("OrganisationId")),
+ name: Schema.String,
+}) {}
diff --git a/packages/web-domain/src/Policy.ts b/packages/web-domain/src/Policy.ts
new file mode 100644
index 000000000..406fbf97e
--- /dev/null
+++ b/packages/web-domain/src/Policy.ts
@@ -0,0 +1,76 @@
+// shoutout https://lucas-barake.github.io/building-a-composable-policy-system/
+
+import { Context, Data, Effect, Option, Schema } from "effect";
+import { CurrentUser } from "./Authentication";
+
+export type Policy = Effect.Effect<
+ void,
+ PolicyDeniedError | E,
+ CurrentUser | R
+>;
+
+export type PublicPolicy = Effect.Effect<
+ void,
+ PolicyDeniedError | E,
+ R
+>;
+
+export class PolicyDeniedError extends Schema.TaggedError()(
+ "PolicyDenied",
+ {}
+) {}
+
+/**
+ * Creates a policy from a predicate function that evaluates the current user.
+ */
+export const policy = (
+ predicate: (
+ user: CurrentUser["Type"]
+ ) => Effect.Effect
+): Policy =>
+ Effect.flatMap(CurrentUser, (user) =>
+ Effect.flatMap(
+ predicate(user).pipe(
+ Effect.catchTag("DenyAccess", () => Effect.succeed(false))
+ ),
+ (result) => (result ? Effect.void : Effect.fail(new PolicyDeniedError()))
+ )
+ );
+
+/**
+ * Creates a policy from a predicate function that may evaluate the current user,
+ * or None if there isn't one.
+ */
+export const publicPolicy = (
+ predicate: (
+ user: Option.Option
+ ) => Effect.Effect
+): PublicPolicy =>
+ Effect.gen(function* () {
+ const context = yield* Effect.context();
+ const user = Context.getOption(context, CurrentUser);
+
+ return yield* Effect.flatMap(predicate(user), (result) =>
+ result ? Effect.void : Effect.fail(new PolicyDeniedError())
+ );
+ });
+
+export class DenyAccess extends Data.TaggedError("DenyAccess")<{}> {}
+
+/**
+ * Applies a policy as a pre-check to an effect.
+ * If the policy fails, the effect will fail with Forbidden.
+ */
+export const withPolicy =
+ (policy: Policy) =>
+ (self: Effect.Effect ) =>
+ Effect.zipRight(policy, self);
+
+/**
+ * Applies a policy as a pre-check to an effect.
+ * If the policy fails, the effect will fail with Forbidden.
+ */
+export const withPublicPolicy =
+ (policy: PublicPolicy) =>
+ (self: Effect.Effect ) =>
+ Effect.zipRight(policy, self);
diff --git a/packages/web-domain/src/Rpcs.ts b/packages/web-domain/src/Rpcs.ts
new file mode 100644
index 000000000..747359980
--- /dev/null
+++ b/packages/web-domain/src/Rpcs.ts
@@ -0,0 +1,4 @@
+import { FolderRpcs } from "./Folder";
+import { VideoRpcs } from "./Video";
+
+export const Rpcs = VideoRpcs.merge(FolderRpcs);
diff --git a/packages/web-domain/src/S3Bucket.ts b/packages/web-domain/src/S3Bucket.ts
new file mode 100644
index 000000000..5824ed2b4
--- /dev/null
+++ b/packages/web-domain/src/S3Bucket.ts
@@ -0,0 +1,16 @@
+import { Schema } from "effect";
+
+export const S3BucketId = Schema.String.pipe(Schema.brand("S3BucketId"));
+export type S3BucketId = typeof S3BucketId.Type;
+
+export class S3Bucket extends Schema.Class("S3Bucket")({
+ id: Schema.String,
+ ownerId: Schema.String,
+ region: Schema.String,
+ endpoint: Schema.OptionFromNullOr(Schema.String),
+ name: Schema.String,
+ accessKeyId: Schema.String,
+ secretAccessKey: Schema.String,
+}) {}
+
+export const decodeSync = Schema.decodeSync(S3Bucket);
diff --git a/packages/web-domain/src/Video.ts b/packages/web-domain/src/Video.ts
new file mode 100644
index 000000000..bb398711f
--- /dev/null
+++ b/packages/web-domain/src/Video.ts
@@ -0,0 +1,88 @@
+import { Option, Context, Effect, Schema } from "effect";
+import { Rpc, RpcGroup } from "@effect/rpc";
+
+import { PolicyDeniedError as PolicyDeniedError } from "./Policy";
+import { S3BucketId } from "./S3Bucket";
+import { InternalError } from "./Errors";
+import { RpcAuthMiddleware } from "./Authentication";
+import { FolderId } from "./Folder";
+
+export const VideoId = Schema.String.pipe(Schema.brand("VideoId"));
+export type VideoId = typeof VideoId.Type;
+
+// Purposefully doesn't include password as this is a public class
+export class Video extends Schema.Class("Video")({
+ id: VideoId,
+ ownerId: Schema.String,
+ name: Schema.String,
+ public: Schema.Boolean,
+ metadata: Schema.Record({ key: Schema.String, value: Schema.Any }),
+ source: Schema.Struct({
+ type: Schema.Literal("MediaConvert", "local", "desktopMP4"),
+ }),
+ bucketId: Schema.OptionFromNullOr(S3BucketId),
+ folderId: Schema.OptionFromNullOr(FolderId),
+ transcriptionStatus: Schema.OptionFromNullOr(
+ Schema.Literal("PROCESSING", "COMPLETE", "ERROR")
+ ),
+ createdAt: Schema.Date,
+ updatedAt: Schema.Date,
+}) {
+ static decodeSync = Schema.decodeSync(Video);
+
+ toJS = () => Schema.encode(Video)(this).pipe(Effect.orDie);
+}
+
+/**
+ * Used to specify a video password provided by a user,
+ * whether via cookies in the case of the website,
+ * or via query params for the API.
+ */
+export class VideoPasswordAttachment extends Context.Tag(
+ "VideoPasswordAttachment"
+)() {}
+
+export class VerifyVideoPasswordError extends Schema.TaggedError