Compare commits
11 Commits
5204766276
...
349194e7e5
| Author | SHA1 | Date | |
|---|---|---|---|
| 349194e7e5 | |||
| 24d6460e4c | |||
| 127e783f66 | |||
| 198fd12bb2 | |||
| 34d5209165 | |||
| 9684ab75bb | |||
| 0a6b035a67 | |||
| cbfd3e5632 | |||
| 3faf0866a0 | |||
| bab3a160c2 | |||
| 1778cd0009 |
@@ -1,2 +1 @@
|
|||||||
VITE_SENTRY_DSN=https://264a5e95c5d31fe080a2e92fb008294d@o4511430568378368.ingest.us.sentry.io/4511430571982849
|
|
||||||
VITE_APP_VERSION=lotus
|
VITE_APP_VERSION=lotus
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ jobs:
|
|||||||
run: npm run build
|
run: npm run build
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: '--max_old_space_size=4096'
|
NODE_OPTIONS: '--max_old_space_size=4096'
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
|
||||||
VITE_APP_VERSION: ${{ github.sha }}
|
VITE_APP_VERSION: ${{ github.sha }}
|
||||||
|
|
||||||
# ── Quality checks (informational — pre-existing issues exist) ───────
|
# ── Quality checks (informational — pre-existing issues exist) ───────
|
||||||
|
|||||||
+3
-3
@@ -28,6 +28,8 @@ Implemented and gate-green; confirm each per `LOTUS_TESTING.md`, then delete the
|
|||||||
| #10 | Composer not hidden by keyboard (`100dvh`) | `src/index.css` | E4 |
|
| #10 | Composer not hidden by keyboard (`100dvh`) | `src/index.css` | E4 |
|
||||||
| #12 | PiP mute badge attribution (you vs. all-muted) | `CallEmbedProvider.tsx` | G1 |
|
| #12 | PiP mute badge attribution (you vs. all-muted) | `CallEmbedProvider.tsx` | G1 |
|
||||||
| N96 | Call-recovery overlay single "Back" button | `call/CallView.tsx` | A7 |
|
| N96 | Call-recovery overlay single "Back" button | `call/CallView.tsx` | A7 |
|
||||||
|
| N95 | AFK-monitor mic released on mute (OS indicator clears) | `hooks/useAfkAutoMute.ts` | L1 |
|
||||||
|
| N108 | Maskable PWA icons (Android adaptive) | `public/manifest.json` + `res/android/maskable-*` | L2 |
|
||||||
| EC | EC iframe load watchdog + self-heal + recovery UI | `plugins/call/CallEmbed.ts`, `CallView.tsx` | A7 |
|
| EC | EC iframe load watchdog + self-heal + recovery UI | `plugins/call/CallEmbed.ts`, `CallView.tsx` | A7 |
|
||||||
| Gal | MediaGallery lazy-decrypt (true virtualization deferred) | `room/MediaGallery.tsx` | H1 |
|
| Gal | MediaGallery lazy-decrypt (true virtualization deferred) | `room/MediaGallery.tsx` | H1 |
|
||||||
| a11y | aria-labels: edit-history / reaction / thread / reply | `message/*` (`FallbackContent`, `Reaction`, `Reply`) | I |
|
| a11y | aria-labels: edit-history / reaction / thread / reply | `message/*` (`FallbackContent`, `Reaction`, `Reply`) | I |
|
||||||
@@ -38,7 +40,6 @@ Implemented and gate-green; confirm each per `LOTUS_TESTING.md`, then delete the
|
|||||||
|
|
||||||
### Calls / Audio
|
### Calls / Audio
|
||||||
|
|
||||||
- **N95 — AFK auto-mute keeps the hardware mic active while muted.** `useAfkAutoMute.ts` holds its own `getUserMedia` stream independent of EC's; muting in the UI doesn't stop those tracks, so the OS recording indicator stays lit. Fix: stop the `MediaStream` tracks on mute, re-request on unmute. (Repro: `LOTUS_TESTING.md` L1.)
|
|
||||||
- **N127 — ML denoise shim is never injected in `vite dev`.** The `lotusDenoise` plugin injects only on `closeBundle` (build), so ML noise suppression is silently inactive during local dev. Add a dev-mode injection (`configureServer` / `transformIndexHtml`). Dev-only impact.
|
- **N127 — ML denoise shim is never injected in `vite dev`.** The `lotusDenoise` plugin injects only on `closeBundle` (build), so ML noise suppression is silently inactive during local dev. Add a dev-mode injection (`configureServer` / `transformIndexHtml`). Dev-only impact.
|
||||||
|
|
||||||
### Security & Privacy
|
### Security & Privacy
|
||||||
@@ -51,7 +52,6 @@ Implemented and gate-green; confirm each per `LOTUS_TESTING.md`, then delete the
|
|||||||
|
|
||||||
- **N105 — Service worker has no `notificationclick` handler** — notification clicks are broken when the tab is closed. Needs `showNotification()` via the SW + a `notificationclick` listener.
|
- **N105 — Service worker has no `notificationclick` handler** — notification clicks are broken when the tab is closed. Needs `showNotification()` via the SW + a `notificationclick` listener.
|
||||||
- **N107 — SW has no `push` handler** — Web Push delivery is entirely non-functional. Needs a `push` listener + a Matrix push-gateway integration.
|
- **N107 — SW has no `push` handler** — Web Push delivery is entirely non-functional. Needs a `push` listener + a Matrix push-gateway integration.
|
||||||
- **N108 — No maskable PWA icon** — Android adaptive icons render incorrectly. Needs a maskable icon asset + `purpose: "maskable"` manifest entry.
|
|
||||||
- **No app-asset caching strategy** (`src/sw.ts`) — no offline capability.
|
- **No app-asset caching strategy** (`src/sw.ts`) — no offline capability.
|
||||||
- **`manifest: false`** in `vite.config.js` — may block correct PWA install if not handled externally.
|
- **`manifest: false`** in `vite.config.js` — may block correct PWA install if not handled externally.
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ Implemented and gate-green; confirm each per `LOTUS_TESTING.md`, then delete the
|
|||||||
- **Hardcoded CDN URL** should move to an env var (the decoration CDN is now single-sourced in `avatarDecorations.ts`, but the literal is still in-repo).
|
- **Hardcoded CDN URL** should move to an env var (the decoration CDN is now single-sourced in `avatarDecorations.ts`, but the literal is still in-repo).
|
||||||
- **`patch-folds.mjs` edits `node_modules` directly** — consider `patch-package`.
|
- **`patch-folds.mjs` edits `node_modules` directly** — consider `patch-package`.
|
||||||
- **Infra docs:** `contrib/nginx` lacks security headers (HSTS/CSP) + uses rewrites over `try_files`; `contrib/caddy` has a placeholder path. CI/CD (`prod-deploy.yml`): sequential deploy, aggressive 1-min Netlify timeout, `package-manager-cache: false`.
|
- **Infra docs:** `contrib/nginx` lacks security headers (HSTS/CSP) + uses rewrites over `try_files`; `contrib/caddy` has a placeholder path. CI/CD (`prod-deploy.yml`): sequential deploy, aggressive 1-min Netlify timeout, `package-manager-cache: false`.
|
||||||
- **README / CONTRIBUTING:** stale upstream bug-tracker/donations/CLA links; README↔CONTRIBUTING misalignment.
|
- **README:** keep the fork-sync version + logo path current. (`CONTRIBUTING.md` is intentionally left as upstream Cinny's — not a Lotus concern.)
|
||||||
- **Architecture notes (low priority):** deep `features/` + `hooks/` nesting, many small coupled hooks, possible dead CSS/components, `SpacingVariant` / `DropTarget` recipe simplification.
|
- **Architecture notes (low priority):** deep `features/` + `hooks/` nesting, many small coupled hooks, possible dead CSS/components, `SpacingVariant` / `DropTarget` recipe simplification.
|
||||||
- **Git workflow (forward-looking):** keep commits scoped — past monolithic "fix all bugs" commits and inconsistent prefixes hurt `git bisect`.
|
- **Git workflow (forward-looking):** keep commits scoped — past monolithic "fix all bugs" commits and inconsistent prefixes hurt `git bisect`.
|
||||||
|
|
||||||
|
|||||||
+12
-9
@@ -342,22 +342,25 @@ Trigger a desktop/browser notification for a new message.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## L. Open bugs flagged by audit — reproduction needed before fix
|
## L. Fixed — verify
|
||||||
|
|
||||||
### L1. AFK auto-mute keeps the OS microphone indicator lit (N95) — 👥 live call
|
### L1. AFK auto-mute releases the OS microphone indicator on mute (N95) — 👥 live call
|
||||||
|
|
||||||
**Context:** `useAfkAutoMute.ts` calls `getUserMedia({ audio: true })` independently of Element Call's managed stream. When you mute in the Lotus UI, the LiveKit mic inside EC's iframe is muted via the widget API — but the separate `MediaStream` held by the AFK hook keeps its tracks running. The OS-level recording indicator (green dot on macOS, mic icon on Windows/Linux) therefore stays lit while your mic is muted.
|
**Context (now FIXED):** `useAfkAutoMute.ts` opened its own `getUserMedia` level-monitor capture for the whole call, so the OS recording indicator (green dot on macOS, mic icon on Windows/Linux) stayed lit even when muted. The capture is now gated on the reactive mic-on state — it runs only while unmuted, so muting releases the stream.
|
||||||
|
|
||||||
**To reproduce:**
|
**To verify:**
|
||||||
|
|
||||||
1. Enable **AFK auto-mute** in Settings → Calls and **join a call**.
|
1. Enable **AFK auto-mute** in Settings → Calls and **join a call**.
|
||||||
2. Manually **mute your mic** using the call controls.
|
2. Manually **mute your mic** using the call controls → the **OS recording indicator should clear** within ~a second.
|
||||||
3. Check the **OS recording indicator** (macOS: green dot top-right of menu bar; Windows: mic icon in taskbar).
|
3. **Unmute** → the indicator should re-appear (capture re-acquired).
|
||||||
|
4. Also confirm AFK still works end-to-end: stay unmuted and silent past the configured timeout → mic auto-mutes with the "muted after inactivity" toast, and the indicator clears.
|
||||||
|
|
||||||
**Expected (current broken behavior):** the OS recording indicator stays on even though your Lotus mic shows muted.
|
### L2. Maskable PWA icon (N108) — Android install
|
||||||
**Expected after fix:** the indicator should clear when you mute and re-appear when you unmute.
|
|
||||||
|
|
||||||
> **Note:** This is an **open bug** — no fix has been applied yet. Reproduce and confirm the symptom first. The fix involves stopping `MediaStream` tracks on mute and re-requesting `getUserMedia` on unmute (see LOTUS_BUGS.md N95 for full details). Once fixed, re-run this check to verify the indicator clears.
|
1. On **Android Chrome**, install Lotus Chat as a PWA (Add to Home Screen).
|
||||||
|
2. Look at the **home-screen icon**.
|
||||||
|
|
||||||
|
**Expected:** the icon fills the adaptive-icon shape cleanly (the logo centered with safe-zone padding on the dark background), **not** clipped at the corners or floating in an odd box. Also worth a quick check in Chrome DevTools → Application → Manifest that the two `purpose: maskable` icons load without a 404 (this also validates the manifest's icon paths resolve in production — a pre-existing path convention I couldn't verify statically).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
A Matrix chat client built for Lotus Guild — fast, private, and packed with the features you actually want.
|
A Matrix chat client built for Lotus Guild — fast, private, and packed with the features you actually want.
|
||||||
|
|
||||||
**Deployed at [chat.lotusguild.org](https://chat.lotusguild.org)** | Forked from [Cinny](https://github.com/cinnyapp/cinny) v4.12.1
|
**Deployed at [chat.lotusguild.org](https://chat.lotusguild.org)** | Forked from [Cinny](https://github.com/cinnyapp/cinny), synced through v4.12.3
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ A Matrix chat client built for Lotus Guild — fast, private, and packed with th
|
|||||||
|
|
||||||
The source code is licensed under [AGPLv3](LICENSE), the same license as the upstream Cinny project. The source for this fork is public at [code.lotusguild.org/LotusGuild/cinny](https://code.lotusguild.org/LotusGuild/cinny).
|
The source code is licensed under [AGPLv3](LICENSE), the same license as the upstream Cinny project. The source for this fork is public at [code.lotusguild.org/LotusGuild/cinny](https://code.lotusguild.org/LotusGuild/cinny).
|
||||||
|
|
||||||
The Lotus Chat logo (`lotus_chat.png`) is a derivative work based on the original Cinny logo by Ajay Bura and contributors, used under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). The modified logo is © Lotus Guild and is also made available under CC BY 4.0.
|
The Lotus Chat logo (`public/res/Lotus.png`) is a derivative work based on the original Cinny logo by Ajay Bura and contributors, used under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). The modified logo is © Lotus Guild and is also made available under CC BY 4.0.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Generated
-496
@@ -21,7 +21,6 @@
|
|||||||
"@giphy/js-util": "5.2.0",
|
"@giphy/js-util": "5.2.0",
|
||||||
"@giphy/react-components": "10.1.2",
|
"@giphy/react-components": "10.1.2",
|
||||||
"@sapphi-red/web-noise-suppressor": "0.3.5",
|
"@sapphi-red/web-noise-suppressor": "0.3.5",
|
||||||
"@sentry/react": "10.53.1",
|
|
||||||
"@tanstack/react-query": "5.100.13",
|
"@tanstack/react-query": "5.100.13",
|
||||||
"@tanstack/react-query-devtools": "5.100.13",
|
"@tanstack/react-query-devtools": "5.100.13",
|
||||||
"@tanstack/react-virtual": "3.13.25",
|
"@tanstack/react-virtual": "3.13.25",
|
||||||
@@ -81,7 +80,6 @@
|
|||||||
"@element-hq/element-call-embedded": "0.20.1",
|
"@element-hq/element-call-embedded": "0.20.1",
|
||||||
"@rollup/plugin-inject": "5.0.5",
|
"@rollup/plugin-inject": "5.0.5",
|
||||||
"@rollup/plugin-wasm": "6.2.2",
|
"@rollup/plugin-wasm": "6.2.2",
|
||||||
"@sentry/vite-plugin": "5.3.0",
|
|
||||||
"@types/chroma-js": "3.1.2",
|
"@types/chroma-js": "3.1.2",
|
||||||
"@types/file-saver": "2.0.7",
|
"@types/file-saver": "2.0.7",
|
||||||
"@types/is-hotkey": "0.1.10",
|
"@types/is-hotkey": "0.1.10",
|
||||||
@@ -3782,403 +3780,6 @@
|
|||||||
"integrity": "sha512-jh3+V9yM+zxLriQexoGm0GatoPaJWjs6ypFIbFYwQp+AoUb55eUXrjKtKQyuC5zShzzeAQUl0M5JzqB7SSrsRA==",
|
"integrity": "sha512-jh3+V9yM+zxLriQexoGm0GatoPaJWjs6ypFIbFYwQp+AoUb55eUXrjKtKQyuC5zShzzeAQUl0M5JzqB7SSrsRA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@sentry-internal/browser-utils": {
|
|
||||||
"version": "10.53.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.53.1.tgz",
|
|
||||||
"integrity": "sha512-X4d6y8sBMjmNhcDW4eMBU3ASsNIMz8dqaFkhyIMN/dkYr/yZKnbRZPaVuVUGvHKjnlficPpIH0/HK9KBjrYxPw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@sentry/core": "10.53.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry-internal/feedback": {
|
|
||||||
"version": "10.53.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.53.1.tgz",
|
|
||||||
"integrity": "sha512-vVpTI/aEYN5d9IgZeYJWMqVaN0+iFgidSrYNAsZTh1US5sJUzF/wrl+68KdpmCtFROrN3jiAn1oPSwL5CKvEJA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@sentry/core": "10.53.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry-internal/replay": {
|
|
||||||
"version": "10.53.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.53.1.tgz",
|
|
||||||
"integrity": "sha512-wZNzTBYkgGUPWMuUQv7L64+OJmoCnz7GQNiTrTFK6EVAjJXFBCSsPp/nhif0bLhbk8+0g4xz633uOhpXuQbFdw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@sentry-internal/browser-utils": "10.53.1",
|
|
||||||
"@sentry/core": "10.53.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry-internal/replay-canvas": {
|
|
||||||
"version": "10.53.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.53.1.tgz",
|
|
||||||
"integrity": "sha512-aueLaf/2prExwA76BGU5/bOXCKWqtt6jQXWA6WJQNrmKpPEtZJB4ypnpsou0McXQCF8tur2Y8U0TEkwQP13yJQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@sentry-internal/replay": "10.53.1",
|
|
||||||
"@sentry/core": "10.53.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/babel-plugin-component-annotate": {
|
|
||||||
"version": "5.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-5.3.0.tgz",
|
|
||||||
"integrity": "sha512-p4q8gn8wcFqZGP/s2MnJCAAd8fTikaU6A0mM97RDHQgStcrYiaS0Sc5zUNfb1V+UOLPuvdEdL6MwyxfzjYJQTA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/browser": {
|
|
||||||
"version": "10.53.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.53.1.tgz",
|
|
||||||
"integrity": "sha512-zXF373hzUOGzUOrqd8xb1U3LQi5uYC3mwv+z5OMKUUinQlu30tTWBs7ypy6YTchtix9QlYaHWlayUF8vBZ5UjA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@sentry-internal/browser-utils": "10.53.1",
|
|
||||||
"@sentry-internal/feedback": "10.53.1",
|
|
||||||
"@sentry-internal/replay": "10.53.1",
|
|
||||||
"@sentry-internal/replay-canvas": "10.53.1",
|
|
||||||
"@sentry/core": "10.53.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/bundler-plugin-core": {
|
|
||||||
"version": "5.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-5.3.0.tgz",
|
|
||||||
"integrity": "sha512-L5T60sWdAI3qWwdg3Ptwek/0TY59PERrxyqp4XMUkroayQvGd9r5dIW9Q1kSeXX9iJ442nXbFZKAOyCKV4Z13Q==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/core": "^7.18.5",
|
|
||||||
"@sentry/babel-plugin-component-annotate": "5.3.0",
|
|
||||||
"@sentry/cli": "^2.58.5",
|
|
||||||
"dotenv": "^16.3.1",
|
|
||||||
"find-up": "^5.0.0",
|
|
||||||
"glob": "^13.0.6",
|
|
||||||
"magic-string": "~0.30.8"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/bundler-plugin-core/node_modules/balanced-match": {
|
|
||||||
"version": "4.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
|
||||||
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": "18 || 20 || >=22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/bundler-plugin-core/node_modules/brace-expansion": {
|
|
||||||
"version": "5.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
|
||||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"balanced-match": "^4.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "18 || 20 || >=22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/bundler-plugin-core/node_modules/glob": {
|
|
||||||
"version": "13.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
|
|
||||||
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "BlueOak-1.0.0",
|
|
||||||
"dependencies": {
|
|
||||||
"minimatch": "^10.2.2",
|
|
||||||
"minipass": "^7.1.3",
|
|
||||||
"path-scurry": "^2.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "18 || 20 || >=22"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/bundler-plugin-core/node_modules/minimatch": {
|
|
||||||
"version": "10.2.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
|
||||||
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "BlueOak-1.0.0",
|
|
||||||
"dependencies": {
|
|
||||||
"brace-expansion": "^5.0.5"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "18 || 20 || >=22"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/bundler-plugin-core/node_modules/minipass": {
|
|
||||||
"version": "7.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
|
|
||||||
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "BlueOak-1.0.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16 || 14 >=14.17"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/cli": {
|
|
||||||
"version": "2.58.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.58.6.tgz",
|
|
||||||
"integrity": "sha512-baBcNPLLfUi9WuL+Tpri9BFaAdvugZIKelC5X0tt0Zdy+K0K+PCVSrnNmwMWU/HyaF/SEv6b6UHnXIdqanBlcg==",
|
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "FSL-1.1-MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"https-proxy-agent": "^5.0.0",
|
|
||||||
"node-fetch": "^2.6.7",
|
|
||||||
"progress": "^2.0.3",
|
|
||||||
"proxy-from-env": "^1.1.0",
|
|
||||||
"which": "^2.0.2"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"sentry-cli": "bin/sentry-cli"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@sentry/cli-darwin": "2.58.6",
|
|
||||||
"@sentry/cli-linux-arm": "2.58.6",
|
|
||||||
"@sentry/cli-linux-arm64": "2.58.6",
|
|
||||||
"@sentry/cli-linux-i686": "2.58.6",
|
|
||||||
"@sentry/cli-linux-x64": "2.58.6",
|
|
||||||
"@sentry/cli-win32-arm64": "2.58.6",
|
|
||||||
"@sentry/cli-win32-i686": "2.58.6",
|
|
||||||
"@sentry/cli-win32-x64": "2.58.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/cli-darwin": {
|
|
||||||
"version": "2.58.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.58.6.tgz",
|
|
||||||
"integrity": "sha512-udAVvcyfNa0R+95GvPz/+43/N3TC0TYKdkQ7D7jhPSzbcMc7l2fxRNN5yB3UpCA5fWFnW4toeaqwDBhb/Wh3LA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "FSL-1.1-MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/cli-linux-arm": {
|
|
||||||
"version": "2.58.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.58.6.tgz",
|
|
||||||
"integrity": "sha512-pD0LAt5PcUzAinBwvDqc66x9+2CabHEv486yP0gRjWO7SakbaxmfVq/EXd8VLq/Tzi39LAu422UYK1lpW3MILw==",
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "FSL-1.1-MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux",
|
|
||||||
"freebsd",
|
|
||||||
"android"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/cli-linux-arm64": {
|
|
||||||
"version": "2.58.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.58.6.tgz",
|
|
||||||
"integrity": "sha512-q8mEcNNmeXMy5i+jWT30TVpH7LcP4HD21CD5XRSPAd/a912HF6EpK0ybf/1USO14WOhoXbAGi9txwaWabSe33g==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "FSL-1.1-MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux",
|
|
||||||
"freebsd",
|
|
||||||
"android"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/cli-linux-i686": {
|
|
||||||
"version": "2.58.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.58.6.tgz",
|
|
||||||
"integrity": "sha512-q8vNJi1eOV/4vxAFWBsEwLHoSYapaZHIf4j76KJGJXFKTkEbsjCOOsKbwUIBTQQhRgV4DFWh3ryfsPS/que4Kg==",
|
|
||||||
"cpu": [
|
|
||||||
"x86",
|
|
||||||
"ia32"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "FSL-1.1-MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux",
|
|
||||||
"freebsd",
|
|
||||||
"android"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/cli-linux-x64": {
|
|
||||||
"version": "2.58.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.58.6.tgz",
|
|
||||||
"integrity": "sha512-DZu956Mhi3ZRjTBe1WdbGV46ldVbA8d2rgp/fh51GsI25zjBHah4wZnPTSzpc+YqxU6pJpg579B/r3jrIK530Q==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "FSL-1.1-MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux",
|
|
||||||
"freebsd",
|
|
||||||
"android"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/cli-win32-arm64": {
|
|
||||||
"version": "2.58.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.58.6.tgz",
|
|
||||||
"integrity": "sha512-nj0Ff/kmAB73EPDhR8B4O9r+NUHK5GkPCkGWC+kXVemqAJWL5jcJ5KdxG0l/S0z6RoEoltID8/43/B+TaMlT7A==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "FSL-1.1-MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/cli-win32-i686": {
|
|
||||||
"version": "2.58.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.58.6.tgz",
|
|
||||||
"integrity": "sha512-WNZiDzPbgsEMQWq4avsQ391v/xWKJDIWWWo9GYl+N/w5qcYKkoDW7wQG7T9FasI6ENn68phChTOAPXXxbfAdOg==",
|
|
||||||
"cpu": [
|
|
||||||
"x86",
|
|
||||||
"ia32"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "FSL-1.1-MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/cli-win32-x64": {
|
|
||||||
"version": "2.58.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.58.6.tgz",
|
|
||||||
"integrity": "sha512-R35WJ17oF4D2eqI1DR2sQQqr0fjRTt5xoP16WrTu91XM2lndRMFsnjh+/GttbxapLCBNlrjzia99MJ0PZHZpgA==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "FSL-1.1-MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/core": {
|
|
||||||
"version": "10.53.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.53.1.tgz",
|
|
||||||
"integrity": "sha512-XG4ezlkyuAPjBC5+9kXC94rXXuqYTw9NRhfaDHssbTFaGnqBR8vQX2UUgZfY7ucbeelRDGfBu1sywoU+mB04uA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/react": {
|
|
||||||
"version": "10.53.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.53.1.tgz",
|
|
||||||
"integrity": "sha512-lrwNq5T/zW84l60894TpKHPcvFuc1I/Hnohecc0TfYVpIcYYuw2orCHoU4v4wgkFaJUpegVetbgdOphViyLVjA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@sentry/browser": "10.53.1",
|
|
||||||
"@sentry/core": "10.53.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.14.0 || 17.x || 18.x || 19.x"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/rollup-plugin": {
|
|
||||||
"version": "5.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/rollup-plugin/-/rollup-plugin-5.3.0.tgz",
|
|
||||||
"integrity": "sha512-hgPGPYdQJ/G1cGYOxAb7d4z3V+/k/E5/P/5TFPEEBLuIbFFk+JG0CISUDJdzXJjO382Lb99PBJuXGbueBmO79w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@sentry/bundler-plugin-core": "5.3.0",
|
|
||||||
"magic-string": "~0.30.8"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"rollup": ">=3.2.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"rollup": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/vite-plugin": {
|
|
||||||
"version": "5.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-5.3.0.tgz",
|
|
||||||
"integrity": "sha512-qcoSzo4n2MulVQ70UUPLq6dTleb2a2HwL2wuwvAgWhPChrYTuk6A6mDg6aQb9fairPAwFPiU9PzOANpoDJcz1A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@sentry/bundler-plugin-core": "5.3.0",
|
|
||||||
"@sentry/rollup-plugin": "5.3.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@simple-libs/stream-utils": {
|
"node_modules/@simple-libs/stream-utils": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz",
|
||||||
@@ -4893,18 +4494,6 @@
|
|||||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/agent-base": {
|
|
||||||
"version": "6.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
|
||||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"debug": "4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.15.0",
|
"version": "6.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
|
||||||
@@ -6634,19 +6223,6 @@
|
|||||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dotenv": {
|
|
||||||
"version": "16.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
|
||||||
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-2-Clause",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://dotenvx.com"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/dunder-proto": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
@@ -8473,19 +8049,6 @@
|
|||||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/https-proxy-agent": {
|
|
||||||
"version": "5.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
|
||||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"agent-base": "6",
|
|
||||||
"debug": "4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/husky": {
|
"node_modules/husky": {
|
||||||
"version": "9.1.7",
|
"version": "9.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
|
||||||
@@ -10599,26 +10162,6 @@
|
|||||||
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/node-fetch": {
|
|
||||||
"version": "2.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
|
||||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"whatwg-url": "^5.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "4.x || >=6.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"encoding": "^0.1.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"encoding": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.19",
|
"version": "2.0.19",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
||||||
@@ -11178,16 +10721,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/progress": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
|
||||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/prop-types": {
|
"node_modules/prop-types": {
|
||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
@@ -11198,13 +10731,6 @@
|
|||||||
"react-is": "^16.13.1"
|
"react-is": "^16.13.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/proxy-from-env": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
@@ -12783,12 +12309,6 @@
|
|||||||
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==",
|
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/tr46": {
|
|
||||||
"version": "0.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
|
||||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/ts-api-utils": {
|
"node_modules/ts-api-utils": {
|
||||||
"version": "2.5.0",
|
"version": "2.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
|
||||||
@@ -13336,22 +12856,6 @@
|
|||||||
"defaults": "^1.0.3"
|
"defaults": "^1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webidl-conversions": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/whatwg-url": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tr46": "~0.0.3",
|
|
||||||
"webidl-conversions": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|||||||
@@ -45,7 +45,6 @@
|
|||||||
"@giphy/js-util": "5.2.0",
|
"@giphy/js-util": "5.2.0",
|
||||||
"@giphy/react-components": "10.1.2",
|
"@giphy/react-components": "10.1.2",
|
||||||
"@sapphi-red/web-noise-suppressor": "0.3.5",
|
"@sapphi-red/web-noise-suppressor": "0.3.5",
|
||||||
"@sentry/react": "10.53.1",
|
|
||||||
"@tanstack/react-query": "5.100.13",
|
"@tanstack/react-query": "5.100.13",
|
||||||
"@tanstack/react-query-devtools": "5.100.13",
|
"@tanstack/react-query-devtools": "5.100.13",
|
||||||
"@tanstack/react-virtual": "3.13.25",
|
"@tanstack/react-virtual": "3.13.25",
|
||||||
@@ -105,7 +104,6 @@
|
|||||||
"@element-hq/element-call-embedded": "0.20.1",
|
"@element-hq/element-call-embedded": "0.20.1",
|
||||||
"@rollup/plugin-inject": "5.0.5",
|
"@rollup/plugin-inject": "5.0.5",
|
||||||
"@rollup/plugin-wasm": "6.2.2",
|
"@rollup/plugin-wasm": "6.2.2",
|
||||||
"@sentry/vite-plugin": "5.3.0",
|
|
||||||
"@types/chroma-js": "3.1.2",
|
"@types/chroma-js": "3.1.2",
|
||||||
"@types/file-saver": "2.0.7",
|
"@types/file-saver": "2.0.7",
|
||||||
"@types/is-hotkey": "0.1.10",
|
"@types/is-hotkey": "0.1.10",
|
||||||
|
|||||||
@@ -54,6 +54,18 @@
|
|||||||
"src": "./res/android/android-chrome-512x512.png",
|
"src": "./res/android/android-chrome-512x512.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "./res/android/maskable-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "./res/android/maskable-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"categories": ["social", "communication", "productivity"],
|
"categories": ["social", "communication", "productivity"],
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@@ -9,6 +9,7 @@ import {
|
|||||||
config,
|
config,
|
||||||
Dialog,
|
Dialog,
|
||||||
Icon,
|
Icon,
|
||||||
|
IconButton,
|
||||||
Icons,
|
Icons,
|
||||||
Overlay,
|
Overlay,
|
||||||
OverlayBackdrop,
|
OverlayBackdrop,
|
||||||
@@ -51,6 +52,7 @@ import { mxcUrlToHttp, getMxIdLocalPart } from '../utils/matrix';
|
|||||||
import { RoomAvatar, RoomIcon } from './room-avatar';
|
import { RoomAvatar, RoomIcon } from './room-avatar';
|
||||||
import { useRoomNavigate } from '../hooks/useRoomNavigate';
|
import { useRoomNavigate } from '../hooks/useRoomNavigate';
|
||||||
import { getChatBg } from '../features/lotus/chatBackground';
|
import { getChatBg } from '../features/lotus/chatBackground';
|
||||||
|
import { ExitFullscreenIcon, FullscreenIcon } from '../features/call/Controls';
|
||||||
import { useTheme, ThemeKind } from '../hooks/useTheme';
|
import { useTheme, ThemeKind } from '../hooks/useTheme';
|
||||||
import { useSetting } from '../state/hooks/settings';
|
import { useSetting } from '../state/hooks/settings';
|
||||||
import { settingsAtom } from '../state/settings';
|
import { settingsAtom } from '../state/settings';
|
||||||
@@ -1095,10 +1097,13 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
|
|||||||
>
|
>
|
||||||
<div style={{ display: 'flex', gap: config.space.S100, alignItems: 'center' }}>
|
<div style={{ display: 'flex', gap: config.space.S100, alignItems: 'center' }}>
|
||||||
{document.fullscreenEnabled && (
|
{document.fullscreenEnabled && (
|
||||||
<button
|
<IconButton
|
||||||
type="button"
|
type="button"
|
||||||
|
size="300"
|
||||||
|
radii="300"
|
||||||
|
variant="Surface"
|
||||||
|
fill="None"
|
||||||
aria-label={pipIsFullscreen ? 'Exit fullscreen' : 'Fullscreen camera'}
|
aria-label={pipIsFullscreen ? 'Exit fullscreen' : 'Fullscreen camera'}
|
||||||
title={pipIsFullscreen ? 'Exit fullscreen' : 'Fullscreen camera'}
|
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handlePipFullscreen();
|
handlePipFullscreen();
|
||||||
@@ -1107,19 +1112,11 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
|
|||||||
// Dark scrim is intentional for legibility over arbitrary video.
|
// Dark scrim is intentional for legibility over arbitrary video.
|
||||||
background: 'rgba(0,0,0,0.65)',
|
background: 'rgba(0,0,0,0.65)',
|
||||||
backdropFilter: 'blur(4px)',
|
backdropFilter: 'blur(4px)',
|
||||||
border: 'none',
|
|
||||||
borderRadius: config.radii.R300,
|
|
||||||
padding: `${config.space.S100} ${config.space.S200}`,
|
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
fontSize: '13px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
lineHeight: 1,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{pipIsFullscreen ? '⊡' : '⛶'}
|
{pipIsFullscreen ? <ExitFullscreenIcon /> : <FullscreenIcon />}
|
||||||
</button>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Icon, Icons, Text, Tooltip, TooltipProvider } from 'folds';
|
import { color, Icon, Icons, Text, Tooltip, TooltipProvider } from 'folds';
|
||||||
import { useUserVerifiedStatus } from '../hooks/useUserVerifiedStatus';
|
import { useUserVerifiedStatus } from '../hooks/useUserVerifiedStatus';
|
||||||
|
|
||||||
type MemberVerificationBadgeProps = {
|
type MemberVerificationBadgeProps = {
|
||||||
@@ -9,8 +9,7 @@ type MemberVerificationBadgeProps = {
|
|||||||
export function MemberVerificationBadge({ userId }: MemberVerificationBadgeProps) {
|
export function MemberVerificationBadge({ userId }: MemberVerificationBadgeProps) {
|
||||||
const vs = useUserVerifiedStatus(userId);
|
const vs = useUserVerifiedStatus(userId);
|
||||||
if (vs === 'unknown') return null;
|
if (vs === 'unknown') return null;
|
||||||
const color =
|
const iconColor = vs === 'verified' ? color.Success.Main : color.Warning.Main;
|
||||||
vs === 'verified' ? 'var(--tc-positive-normal, #5effc4)' : 'var(--tc-warning-normal, #ffcc55)';
|
|
||||||
const label = vs === 'verified' ? 'Identity verified' : 'Not verified';
|
const label = vs === 'verified' ? 'Identity verified' : 'Not verified';
|
||||||
return (
|
return (
|
||||||
<TooltipProvider
|
<TooltipProvider
|
||||||
@@ -27,7 +26,7 @@ export function MemberVerificationBadge({ userId }: MemberVerificationBadgeProps
|
|||||||
title={label}
|
title={label}
|
||||||
style={{ display: 'inline-flex', alignItems: 'center', flexShrink: 0 }}
|
style={{ display: 'inline-flex', alignItems: 'center', flexShrink: 0 }}
|
||||||
>
|
>
|
||||||
<Icon size="100" src={Icons.ShieldUser} style={{ color }} />
|
<Icon size="100" src={Icons.ShieldUser} style={{ color: iconColor }} />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|||||||
@@ -529,7 +529,7 @@ export function MLocation({ content }: MLocationProps) {
|
|||||||
style={{
|
style={{
|
||||||
width: '280px',
|
width: '280px',
|
||||||
height: '160px',
|
height: '160px',
|
||||||
border: '1px solid var(--bg-surface-border)',
|
border: `${config.borderWidth.B300} solid ${color.SurfaceVariant.ContainerLine}`,
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
display: 'block',
|
display: 'block',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ export const Sidebar = style([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
export const SidebarGlass = style({
|
export const SidebarGlass = style({
|
||||||
backgroundColor: 'rgba(3, 5, 8, 0.55)',
|
backgroundColor: `color-mix(in srgb, ${color.Surface.Container} 55%, transparent)`,
|
||||||
backdropFilter: 'blur(12px)',
|
backdropFilter: 'blur(12px)',
|
||||||
WebkitBackdropFilter: 'blur(12px)',
|
WebkitBackdropFilter: 'blur(12px)',
|
||||||
borderRight: '1px solid rgba(255,255,255,0.06)',
|
borderRight: `${config.borderWidth.B300} solid ${color.SurfaceVariant.ContainerLine}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SidebarStack = style([
|
export const SidebarStack = style([
|
||||||
|
|||||||
@@ -91,10 +91,10 @@ function UserDeviceRow({ userId, device }: UserDeviceRowProps) {
|
|||||||
{(status) => {
|
{(status) => {
|
||||||
const deviceColor =
|
const deviceColor =
|
||||||
status === VerificationStatus.Verified
|
status === VerificationStatus.Verified
|
||||||
? 'var(--tc-positive-normal, #5effc4)'
|
? color.Success.Main
|
||||||
: status === VerificationStatus.Unverified
|
: status === VerificationStatus.Unverified
|
||||||
? 'var(--tc-warning-normal, #ffcc55)'
|
? color.Warning.Main
|
||||||
: 'var(--tc-surface-low-contrast)';
|
: color.SurfaceVariant.OnContainer;
|
||||||
return (
|
return (
|
||||||
<Box alignItems="Center" gap="200">
|
<Box alignItems="Center" gap="200">
|
||||||
<Icon size="100" src={Icons.ShieldUser} style={{ color: deviceColor, flexShrink: 0 }} />
|
<Icon size="100" src={Icons.ShieldUser} style={{ color: deviceColor, flexShrink: 0 }} />
|
||||||
@@ -106,7 +106,7 @@ function UserDeviceRow({ userId, device }: UserDeviceRowProps) {
|
|||||||
<Text
|
<Text
|
||||||
size="T200"
|
size="T200"
|
||||||
truncate
|
truncate
|
||||||
style={{ color: 'var(--tc-surface-low-contrast)', fontFamily: 'monospace' }}
|
style={{ color: color.SurfaceVariant.OnContainer, fontFamily: 'monospace' }}
|
||||||
>
|
>
|
||||||
{device.deviceId}
|
{device.deviceId}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -160,7 +160,7 @@ function UserDeviceSessions({ userId }: UserDeviceSessionsProps) {
|
|||||||
direction="Column"
|
direction="Column"
|
||||||
gap="100"
|
gap="100"
|
||||||
style={{
|
style={{
|
||||||
background: 'var(--bg-surface-variant)',
|
background: color.SurfaceVariant.Container,
|
||||||
borderRadius: config.radii.R300,
|
borderRadius: config.radii.R300,
|
||||||
padding: config.space.S300,
|
padding: config.space.S300,
|
||||||
}}
|
}}
|
||||||
@@ -171,7 +171,7 @@ function UserDeviceSessions({ userId }: UserDeviceSessionsProps) {
|
|||||||
<Text size="T300">
|
<Text size="T300">
|
||||||
<b>Sessions</b>
|
<b>Sessions</b>
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="T200" style={{ color: 'var(--tc-surface-low-contrast)' }}>
|
<Text size="T200" style={{ color: color.SurfaceVariant.OnContainer }}>
|
||||||
{devices.length}
|
{devices.length}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Chip,
|
Chip,
|
||||||
|
color,
|
||||||
config,
|
config,
|
||||||
Icon,
|
Icon,
|
||||||
IconButton,
|
IconButton,
|
||||||
@@ -276,8 +277,8 @@ export function CallControls({ callEmbed }: CallControlsProps) {
|
|||||||
bottom: '110%',
|
bottom: '110%',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translateX(-50%)',
|
transform: 'translateX(-50%)',
|
||||||
background: 'var(--bg-surface)',
|
background: color.Surface.Container,
|
||||||
border: '1px solid var(--bg-surface-border)',
|
border: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`,
|
||||||
borderRadius: '0.75rem',
|
borderRadius: '0.75rem',
|
||||||
padding: '1rem 1.25rem',
|
padding: '1rem 1.25rem',
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
|
|||||||
@@ -166,13 +166,13 @@ export function ScreenShareButton({ enabled, onToggle }: ScreenShareButtonProps)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const FullscreenIcon = () => (
|
export const FullscreenIcon = () => (
|
||||||
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="currentColor">
|
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="currentColor">
|
||||||
<path d="M3 3h6v2H5v4H3V3zm12 0h6v6h-2V5h-4V3zM3 15h2v4h4v2H3v-6zm16 4h-4v2h6v-6h-2v4z" />
|
<path d="M3 3h6v2H5v4H3V3zm12 0h6v6h-2V5h-4V3zM3 15h2v4h4v2H3v-6zm16 4h-4v2h6v-6h-2v4z" />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
const ExitFullscreenIcon = () => (
|
export const ExitFullscreenIcon = () => (
|
||||||
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="currentColor">
|
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="currentColor">
|
||||||
<path d="M9 3H7v4H3v2h6V3zm6 0v6h6V7h-4V3h-2zM3 13v2h4v4h2v-6H3zm14 4v-4h2v6h-6v-2h4z" />
|
<path d="M9 3H7v4H3v2h6V3zm6 0v6h6V7h-4V3h-2zM3 13v2h4v4h2v-6H3zm14 4v-4h2v6h-6v-2h4z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Box, Button, Icon, Icons, Spinner, Text } from 'folds';
|
import { Box, Button, color, Icon, Icons, Spinner, Text } from 'folds';
|
||||||
import { SequenceCard } from '../../components/sequence-card';
|
import { SequenceCard } from '../../components/sequence-card';
|
||||||
import * as css from './styles.css';
|
import * as css from './styles.css';
|
||||||
import { ChatButton, ControlDivider, MicrophoneButton, SoundButton, VideoButton } from './Controls';
|
import { ChatButton, ControlDivider, MicrophoneButton, SoundButton, VideoButton } from './Controls';
|
||||||
@@ -78,10 +78,7 @@ export function PrescreenControls({ canJoin }: PrescreenControlsProps) {
|
|||||||
</Box>
|
</Box>
|
||||||
<Box grow="Yes" direction="Column" gap="200">
|
<Box grow="Yes" direction="Column" gap="200">
|
||||||
{micDenied && (
|
{micDenied && (
|
||||||
<Text
|
<Text size="T200" style={{ color: color.Critical.Main, textAlign: 'center' }}>
|
||||||
size="T200"
|
|
||||||
style={{ color: 'var(--tc-critical-high, #e53e3e)', textAlign: 'center' }}
|
|
||||||
>
|
|
||||||
Microphone access is blocked. Enable it in your browser settings to join.
|
Microphone access is blocked. Enable it in your browser settings to join.
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
import React, { RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Text, Box, Icon, Icons, config, Spinner, IconButton, Line, toRem, Button } from 'folds';
|
import {
|
||||||
|
Text,
|
||||||
|
Box,
|
||||||
|
Icon,
|
||||||
|
Icons,
|
||||||
|
color,
|
||||||
|
config,
|
||||||
|
Spinner,
|
||||||
|
IconButton,
|
||||||
|
Line,
|
||||||
|
toRem,
|
||||||
|
Button,
|
||||||
|
} from 'folds';
|
||||||
import { useAtom, useAtomValue } from 'jotai';
|
import { useAtom, useAtomValue } from 'jotai';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
@@ -112,7 +124,7 @@ function EncryptedRoomCachePanel({ roomIds, onLoaded }: EncryptedRoomCachePanelP
|
|||||||
gap="200"
|
gap="200"
|
||||||
style={{
|
style={{
|
||||||
padding: config.space.S200,
|
padding: config.space.S200,
|
||||||
background: 'var(--bg-surface-variant)',
|
background: color.SurfaceVariant.Container,
|
||||||
borderRadius: config.radii.R300,
|
borderRadius: config.radii.R300,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1106,7 +1106,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
<Text
|
<Text
|
||||||
size="T200"
|
size="T200"
|
||||||
style={{
|
style={{
|
||||||
color: 'var(--tc-danger-normal)',
|
color: color.Critical.Main,
|
||||||
padding: '2px 6px',
|
padding: '2px 6px',
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
@@ -1119,7 +1119,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
<Text
|
<Text
|
||||||
size="T200"
|
size="T200"
|
||||||
style={{
|
style={{
|
||||||
color: 'var(--tc-danger-normal)',
|
color: color.Critical.Main,
|
||||||
padding: '2px 6px',
|
padding: '2px 6px',
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
|
|||||||
@@ -482,9 +482,9 @@ function ProfileStatus() {
|
|||||||
opacity: statusMsg.length >= 56 ? 1 : 0.45,
|
opacity: statusMsg.length >= 56 ? 1 : 0.45,
|
||||||
color:
|
color:
|
||||||
statusMsg.length >= 64
|
statusMsg.length >= 64
|
||||||
? 'var(--tc-critical-normal)'
|
? color.Critical.Main
|
||||||
: statusMsg.length >= 56
|
: statusMsg.length >= 56
|
||||||
? 'var(--tc-warning-normal)'
|
? color.Warning.Main
|
||||||
: undefined,
|
: undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -536,7 +536,7 @@ function ProfileStatus() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
{saveState.status === AsyncStatus.Error && (
|
{saveState.status === AsyncStatus.Error && (
|
||||||
<Text size="T200" style={{ color: 'var(--tc-critical-normal)' }}>
|
<Text size="T200" style={{ color: color.Critical.Main }}>
|
||||||
Failed to save status — server may be rate limiting. Try again.
|
Failed to save status — server may be rate limiting. Try again.
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
@@ -730,7 +730,7 @@ function ProfilePronouns() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
{saveState.status === AsyncStatus.Error && (
|
{saveState.status === AsyncStatus.Error && (
|
||||||
<Text size="T200" style={{ color: 'var(--tc-critical-normal)' }}>
|
<Text size="T200" style={{ color: color.Critical.Main }}>
|
||||||
Failed to save pronouns. Try again.
|
Failed to save pronouns. Try again.
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
@@ -873,7 +873,7 @@ function ProfileTimezone() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
{saveState.status === AsyncStatus.Error && (
|
{saveState.status === AsyncStatus.Error && (
|
||||||
<Text size="T200" style={{ color: 'var(--tc-critical-normal)' }}>
|
<Text size="T200" style={{ color: color.Critical.Main }}>
|
||||||
Failed to save timezone. Try again.
|
Failed to save timezone. Try again.
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -37,12 +37,12 @@ function DecorationPreviewCell({
|
|||||||
width: CELL_SIZE,
|
width: CELL_SIZE,
|
||||||
height: CELL_SIZE,
|
height: CELL_SIZE,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
border: `2px solid ${selected ? 'var(--accent-cyan)' : 'transparent'}`,
|
border: `2px solid ${selected ? color.Primary.Main : 'transparent'}`,
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
background: 'var(--bg-surface-variant)',
|
background: color.SurfaceVariant.Container,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
padding: 0,
|
padding: 0,
|
||||||
boxShadow: selected ? '0 0 0 1px var(--accent-cyan)' : 'none',
|
boxShadow: selected ? `0 0 0 1px ${color.Primary.Main}` : 'none',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
}}
|
}}
|
||||||
@@ -142,7 +142,7 @@ export function ProfileDecoration() {
|
|||||||
height: CELL_SIZE,
|
height: CELL_SIZE,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
background: 'var(--bg-surface-variant)',
|
background: color.SurfaceVariant.Container,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { Box, Button, Text } from 'folds';
|
import { Box, Button, color, config, Text } from 'folds';
|
||||||
import { DenoiseModelId } from '../../../state/settings';
|
import { DenoiseModelId } from '../../../state/settings';
|
||||||
import { DENOISE_MODELS } from '../../../utils/lotusDenoiseUtils';
|
import { DENOISE_MODELS } from '../../../utils/lotusDenoiseUtils';
|
||||||
import {
|
import {
|
||||||
@@ -49,8 +49,8 @@ function DbMeter({ label, db, threshold }: { label: string; db: number; threshol
|
|||||||
style={{
|
style={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
height: '12px',
|
height: '12px',
|
||||||
background: 'var(--bg-card)',
|
background: color.Surface.Container,
|
||||||
border: '1px solid var(--border-color)',
|
border: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`,
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
@@ -62,7 +62,7 @@ function DbMeter({ label, db, threshold }: { label: string; db: number; threshol
|
|||||||
left: 0,
|
left: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
width: `${pct}%`,
|
width: `${pct}%`,
|
||||||
background: 'var(--accent-green)',
|
background: color.Success.Main,
|
||||||
transition: 'width 0.05s linear',
|
transition: 'width 0.05s linear',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -74,7 +74,7 @@ function DbMeter({ label, db, threshold }: { label: string; db: number; threshol
|
|||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: `${markerPct}%`,
|
left: `${markerPct}%`,
|
||||||
width: '2px',
|
width: '2px',
|
||||||
background: 'var(--accent-orange)',
|
background: color.Primary.Main,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1372,8 +1372,8 @@ function Calls() {
|
|||||||
style={{
|
style={{
|
||||||
padding: '16px',
|
padding: '16px',
|
||||||
marginTop: '8px',
|
marginTop: '8px',
|
||||||
borderTop: '1px solid var(--border-color)',
|
borderTop: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`,
|
||||||
background: 'var(--bg-card)',
|
background: color.Surface.Container,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* ── Model selection ───────────────────────────────────────── */}
|
{/* ── Model selection ───────────────────────────────────────── */}
|
||||||
@@ -1397,8 +1397,8 @@ function Calls() {
|
|||||||
style={{
|
style={{
|
||||||
padding: '12px',
|
padding: '12px',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
border: '1px solid var(--border-color)',
|
border: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`,
|
||||||
background: 'var(--bg-input)',
|
background: color.SurfaceVariant.Container,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text size="T300">{selectedDenoiseModel.name}</Text>
|
<Text size="T300">{selectedDenoiseModel.name}</Text>
|
||||||
@@ -1436,7 +1436,7 @@ function Calls() {
|
|||||||
direction="Row"
|
direction="Row"
|
||||||
gap="100"
|
gap="100"
|
||||||
style={{
|
style={{
|
||||||
borderBottom: '1px solid var(--border-color)',
|
borderBottom: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`,
|
||||||
paddingBottom: '4px',
|
paddingBottom: '4px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -1489,7 +1489,10 @@ function Calls() {
|
|||||||
<Box
|
<Box
|
||||||
direction="Column"
|
direction="Column"
|
||||||
gap="300"
|
gap="300"
|
||||||
style={{ paddingTop: '12px', borderTop: '1px solid var(--border-color)' }}
|
style={{
|
||||||
|
paddingTop: '12px',
|
||||||
|
borderTop: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Text size="L400">Enhancements</Text>
|
<Text size="L400">Enhancements</Text>
|
||||||
<SettingTile
|
<SettingTile
|
||||||
@@ -1525,7 +1528,7 @@ function Calls() {
|
|||||||
step="1"
|
step="1"
|
||||||
value={callDenoiseGateThreshold}
|
value={callDenoiseGateThreshold}
|
||||||
onChange={(e) => setCallDenoiseGateThreshold(parseInt(e.target.value, 10))}
|
onChange={(e) => setCallDenoiseGateThreshold(parseInt(e.target.value, 10))}
|
||||||
style={{ width: '100%', accentColor: 'var(--accent-orange)' }}
|
style={{ width: '100%', accentColor: color.Primary.Main }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@@ -1535,7 +1538,10 @@ function Calls() {
|
|||||||
<Box
|
<Box
|
||||||
direction="Column"
|
direction="Column"
|
||||||
gap="200"
|
gap="200"
|
||||||
style={{ paddingTop: '12px', borderTop: '1px solid var(--border-color)' }}
|
style={{
|
||||||
|
paddingTop: '12px',
|
||||||
|
borderTop: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Text size="L400">Test & calibrate</Text>
|
<Text size="L400">Test & calibrate</Text>
|
||||||
<Text size="T200" priority="300">
|
<Text size="T200" priority="300">
|
||||||
@@ -1658,7 +1664,7 @@ function Calls() {
|
|||||||
value={ringtoneVolume}
|
value={ringtoneVolume}
|
||||||
onChange={(e) => setRingtoneVolume(parseInt(e.target.value, 10))}
|
onChange={(e) => setRingtoneVolume(parseInt(e.target.value, 10))}
|
||||||
aria-label="Ringtone volume"
|
aria-label="Ringtone volume"
|
||||||
style={{ flex: 1, accentColor: 'var(--accent-orange)' }}
|
style={{ flex: 1, accentColor: color.Primary.Main }}
|
||||||
/>
|
/>
|
||||||
<Text size="T200" style={{ minWidth: '32px', textAlign: 'right' }}>
|
<Text size="T200" style={{ minWidth: '32px', textAlign: 'right' }}>
|
||||||
{ringtoneVolume}%
|
{ringtoneVolume}%
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import React, { useEffect, useRef, CSSProperties } from 'react';
|
import React, { useEffect, useRef, CSSProperties } from 'react';
|
||||||
import { useAtomValue, useSetAtom } from 'jotai';
|
import { useAtomValue, useSetAtom } from 'jotai';
|
||||||
|
import { color, config, Icon, IconButton, Icons } from 'folds';
|
||||||
import { toastQueueAtom, dismissToastAtom, ToastNotif } from '../../state/toast';
|
import { toastQueueAtom, dismissToastAtom, ToastNotif } from '../../state/toast';
|
||||||
|
import { useSetting } from '../../state/hooks/settings';
|
||||||
|
import { settingsAtom } from '../../state/settings';
|
||||||
|
|
||||||
// Inject the keyframe animation once
|
// Inject the keyframe animation once
|
||||||
const STYLE_ID = 'lotus-toast-keyframes';
|
const STYLE_ID = 'lotus-toast-keyframes';
|
||||||
@@ -29,6 +32,10 @@ type ToastCardProps = {
|
|||||||
|
|
||||||
function ToastCard({ toast }: ToastCardProps) {
|
function ToastCard({ toast }: ToastCardProps) {
|
||||||
const dismiss = useSetAtom(dismissToastAtom);
|
const dismiss = useSetAtom(dismissToastAtom);
|
||||||
|
// Lotus Terminal (TDS) gets its bespoke glow/accents; every other theme uses
|
||||||
|
// folds tokens so toasts render correctly on stock Cinny themes (the --lt-*
|
||||||
|
// vars only exist while Terminal mode is active).
|
||||||
|
const [lotusTerminal] = useSetting(settingsAtom, 'lotusTerminal');
|
||||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -56,17 +63,29 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
dismiss(toast.id);
|
dismiss(toast.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const accent = toast.sticky ? color.Primary.Main : color.Surface.OnContainer;
|
||||||
|
|
||||||
const cardStyle: CSSProperties = {
|
const cardStyle: CSSProperties = {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
background: 'var(--lt-bg-card)',
|
background: lotusTerminal ? 'var(--lt-bg-card)' : color.Surface.Container,
|
||||||
border: toast.sticky
|
border: `${config.borderWidth.B300} solid ${
|
||||||
? '1px solid var(--lt-accent-cyan-border)'
|
lotusTerminal
|
||||||
: '1px solid var(--lt-border-color)',
|
? toast.sticky
|
||||||
borderRadius: '12px',
|
? 'var(--lt-accent-cyan-border)'
|
||||||
padding: '12px 14px',
|
: 'var(--lt-border-color)'
|
||||||
|
: toast.sticky
|
||||||
|
? color.Primary.Main
|
||||||
|
: color.Surface.ContainerLine
|
||||||
|
}`,
|
||||||
|
borderRadius: config.radii.R400,
|
||||||
|
padding: `${config.space.S300} ${config.space.S400}`,
|
||||||
minWidth: '280px',
|
minWidth: '280px',
|
||||||
maxWidth: '340px',
|
maxWidth: '340px',
|
||||||
boxShadow: toast.sticky ? 'var(--lt-box-glow-cyan)' : 'var(--lt-box-glow-orange)',
|
boxShadow: lotusTerminal
|
||||||
|
? toast.sticky
|
||||||
|
? 'var(--lt-box-glow-cyan)'
|
||||||
|
: 'var(--lt-box-glow-orange)'
|
||||||
|
: `0 8px 24px ${color.Other.Shadow}`,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
animation: 'lotusToastIn 0.2s ease-out both',
|
animation: 'lotusToastIn 0.2s ease-out both',
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
@@ -75,8 +94,8 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
const rowStyle: CSSProperties = {
|
const rowStyle: CSSProperties = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: '8px',
|
gap: config.space.S200,
|
||||||
marginRight: '20px',
|
marginRight: config.space.S500,
|
||||||
};
|
};
|
||||||
|
|
||||||
const avatarStyle: CSSProperties = {
|
const avatarStyle: CSSProperties = {
|
||||||
@@ -91,19 +110,25 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
width: '24px',
|
width: '24px',
|
||||||
height: '24px',
|
height: '24px',
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
background: 'var(--lt-accent-orange-dim)',
|
background: lotusTerminal ? 'var(--lt-accent-orange-dim)' : color.Primary.Container,
|
||||||
border: '1px solid var(--lt-accent-orange-border)',
|
border: `${config.borderWidth.B300} solid ${
|
||||||
|
lotusTerminal ? 'var(--lt-accent-orange-border)' : color.Primary.ContainerLine
|
||||||
|
}`,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
fontSize: '10px',
|
fontSize: '10px',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: 'var(--lt-accent-orange)',
|
color: lotusTerminal ? 'var(--lt-accent-orange)' : color.Primary.OnContainer,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const nameStyle: CSSProperties = {
|
const nameStyle: CSSProperties = {
|
||||||
color: toast.sticky ? 'var(--lt-accent-cyan)' : 'var(--lt-accent-orange)',
|
color: lotusTerminal
|
||||||
|
? toast.sticky
|
||||||
|
? 'var(--lt-accent-cyan)'
|
||||||
|
: 'var(--lt-accent-orange)'
|
||||||
|
: accent,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: '0.85rem',
|
fontSize: '0.85rem',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
@@ -111,22 +136,8 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
};
|
};
|
||||||
|
|
||||||
const dismissBtnStyle: CSSProperties = {
|
|
||||||
position: 'absolute',
|
|
||||||
top: '8px',
|
|
||||||
right: '10px',
|
|
||||||
background: 'none',
|
|
||||||
border: 'none',
|
|
||||||
color: 'var(--lt-text-secondary)',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontSize: '14px',
|
|
||||||
lineHeight: 1,
|
|
||||||
padding: '2px 4px',
|
|
||||||
borderRadius: '4px',
|
|
||||||
};
|
|
||||||
|
|
||||||
const bodyStyle: CSSProperties = {
|
const bodyStyle: CSSProperties = {
|
||||||
color: 'var(--lt-text-primary)',
|
color: lotusTerminal ? 'var(--lt-text-primary)' : color.Surface.OnContainer,
|
||||||
fontSize: '0.82rem',
|
fontSize: '0.82rem',
|
||||||
margin: '4px 0 2px',
|
margin: '4px 0 2px',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
@@ -136,7 +147,7 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const roomNameStyle: CSSProperties = {
|
const roomNameStyle: CSSProperties = {
|
||||||
color: 'var(--lt-text-secondary)',
|
color: lotusTerminal ? 'var(--lt-text-secondary)' : color.SurfaceVariant.OnContainer,
|
||||||
fontSize: '0.75rem',
|
fontSize: '0.75rem',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
@@ -161,14 +172,19 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
}}
|
}}
|
||||||
aria-label={`Notification from ${toast.displayName} in ${toast.roomName}`}
|
aria-label={`Notification from ${toast.displayName} in ${toast.roomName}`}
|
||||||
>
|
>
|
||||||
<button
|
<span style={{ position: 'absolute', top: config.space.S100, right: config.space.S100 }}>
|
||||||
|
<IconButton
|
||||||
type="button"
|
type="button"
|
||||||
style={dismissBtnStyle}
|
size="300"
|
||||||
|
radii="300"
|
||||||
|
variant="Surface"
|
||||||
|
fill="None"
|
||||||
onClick={handleDismiss}
|
onClick={handleDismiss}
|
||||||
aria-label="Dismiss notification"
|
aria-label="Dismiss notification"
|
||||||
>
|
>
|
||||||
×
|
<Icon size="100" src={Icons.Cross} />
|
||||||
</button>
|
</IconButton>
|
||||||
|
</span>
|
||||||
<div style={rowStyle}>
|
<div style={rowStyle}>
|
||||||
{toast.avatarUrl ? (
|
{toast.avatarUrl ? (
|
||||||
<img src={toast.avatarUrl} alt="" style={avatarStyle} aria-hidden="true" />
|
<img src={toast.avatarUrl} alt="" style={avatarStyle} aria-hidden="true" />
|
||||||
@@ -201,7 +217,7 @@ export function LotusToastContainer() {
|
|||||||
zIndex: 10001,
|
zIndex: 10001,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: '8px',
|
gap: config.space.S200,
|
||||||
pointerEvents: 'auto',
|
pointerEvents: 'auto',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { CallEmbed } from '../plugins/call';
|
import { CallEmbed, useCallControlState } from '../plugins/call';
|
||||||
import { useSetting } from '../state/hooks/settings';
|
import { useSetting } from '../state/hooks/settings';
|
||||||
import { settingsAtom } from '../state/settings';
|
import { settingsAtom } from '../state/settings';
|
||||||
import { toastQueueAtom } from '../state/toast';
|
import { toastQueueAtom } from '../state/toast';
|
||||||
@@ -9,17 +9,25 @@ const SILENCE_RMS_THRESHOLD = 0.008;
|
|||||||
const CHECK_INTERVAL_MS = 500;
|
const CHECK_INTERVAL_MS = 500;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monitors microphone audio while in a call. If the mic stays active but
|
* Monitors microphone audio while in a call. If the mic stays unmuted but
|
||||||
* silent for longer than the configured timeout, the mic is muted and a
|
* silent for longer than the configured timeout, the mic is muted and a toast
|
||||||
* toast is shown. Cleans up its own AudioContext and stream on unmount.
|
* is shown.
|
||||||
|
*
|
||||||
|
* The level-monitoring capture (`getUserMedia`) is opened ONLY while the mic is
|
||||||
|
* unmuted — there is nothing to auto-mute once you are already muted, so
|
||||||
|
* holding the capture would keep the OS recording indicator lit even though the
|
||||||
|
* UI shows you as muted (N95). Muting therefore releases our stream; unmuting
|
||||||
|
* re-acquires it. The AudioContext + stream are also torn down on unmount.
|
||||||
*/
|
*/
|
||||||
export function useAfkAutoMute(callEmbed: CallEmbed | undefined): void {
|
export function useAfkAutoMute(callEmbed: CallEmbed | undefined): void {
|
||||||
const [enabled] = useSetting(settingsAtom, 'afkAutoMute');
|
const [enabled] = useSetting(settingsAtom, 'afkAutoMute');
|
||||||
const [timeoutMinutes] = useSetting(settingsAtom, 'afkTimeoutMinutes');
|
const [timeoutMinutes] = useSetting(settingsAtom, 'afkTimeoutMinutes');
|
||||||
const setToast = useSetAtom(toastQueueAtom);
|
const setToast = useSetAtom(toastQueueAtom);
|
||||||
|
const { microphone } = useCallControlState(callEmbed?.control);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!callEmbed || !enabled) return;
|
// Only capture while in a call, enabled, AND unmuted (see N95 note above).
|
||||||
|
if (!callEmbed || !enabled || !microphone) return undefined;
|
||||||
|
|
||||||
let stream: MediaStream | undefined;
|
let stream: MediaStream | undefined;
|
||||||
let audioCtx: AudioContext | undefined;
|
let audioCtx: AudioContext | undefined;
|
||||||
@@ -49,12 +57,12 @@ export function useAfkAutoMute(callEmbed: CallEmbed | undefined): void {
|
|||||||
const rms = Math.sqrt(buffer.reduce((sum, v) => sum + v * v, 0) / buffer.length);
|
const rms = Math.sqrt(buffer.reduce((sum, v) => sum + v * v, 0) / buffer.length);
|
||||||
|
|
||||||
if (rms > SILENCE_RMS_THRESHOLD) {
|
if (rms > SILENCE_RMS_THRESHOLD) {
|
||||||
// Audio detected — reset the silence timer
|
// Audio detected — reset the silence timer.
|
||||||
silenceStart = null;
|
silenceStart = null;
|
||||||
} else if (callEmbed.control.microphone) {
|
} else if (silenceStart === null) {
|
||||||
// Mic is on but silent — start or advance the timer
|
// Mic is unmuted (effect only runs while unmuted) but silent — start the timer.
|
||||||
if (silenceStart === null) silenceStart = Date.now();
|
silenceStart = Date.now();
|
||||||
else if (Date.now() - silenceStart >= timeoutMs) {
|
} else if (Date.now() - silenceStart >= timeoutMs) {
|
||||||
callEmbed.control.setMicrophone(false);
|
callEmbed.control.setMicrophone(false);
|
||||||
setToast({
|
setToast({
|
||||||
id: `afk-mute-${Date.now()}`,
|
id: `afk-mute-${Date.now()}`,
|
||||||
@@ -65,10 +73,6 @@ export function useAfkAutoMute(callEmbed: CallEmbed | undefined): void {
|
|||||||
});
|
});
|
||||||
silenceStart = null;
|
silenceStart = null;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Mic is already muted — don't count silence
|
|
||||||
silenceStart = null;
|
|
||||||
}
|
|
||||||
}, CHECK_INTERVAL_MS);
|
}, CHECK_INTERVAL_MS);
|
||||||
})
|
})
|
||||||
.catch(() => undefined);
|
.catch(() => undefined);
|
||||||
@@ -79,5 +83,5 @@ export function useAfkAutoMute(callEmbed: CallEmbed | undefined): void {
|
|||||||
stream?.getTracks().forEach((t) => t.stop());
|
stream?.getTracks().forEach((t) => t.stop());
|
||||||
audioCtx?.close().catch(() => undefined);
|
audioCtx?.close().catch(() => undefined);
|
||||||
};
|
};
|
||||||
}, [callEmbed, enabled, timeoutMinutes, setToast]);
|
}, [callEmbed, enabled, timeoutMinutes, setToast, microphone]);
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-35
@@ -1,7 +1,16 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import * as Sentry from '@sentry/react';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { Provider as JotaiProvider, useAtomValue } from 'jotai';
|
import { Provider as JotaiProvider, useAtomValue } from 'jotai';
|
||||||
import { OverlayContainerProvider, PopOutContainerProvider, TooltipContainerProvider } from 'folds';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
config,
|
||||||
|
OverlayContainerProvider,
|
||||||
|
PopOutContainerProvider,
|
||||||
|
Text,
|
||||||
|
toRem,
|
||||||
|
TooltipContainerProvider,
|
||||||
|
} from 'folds';
|
||||||
import { RouterProvider } from 'react-router-dom';
|
import { RouterProvider } from 'react-router-dom';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||||
@@ -102,41 +111,25 @@ function App() {
|
|||||||
const portalContainer = document.getElementById('portalContainer') ?? undefined;
|
const portalContainer = document.getElementById('portalContainer') ?? undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sentry.ErrorBoundary
|
<ErrorBoundary
|
||||||
fallback={({ error, resetError }) => (
|
fallbackRender={({ error, resetErrorBoundary }) => (
|
||||||
<div
|
<Box
|
||||||
style={{
|
direction="Column"
|
||||||
display: 'flex',
|
alignItems="Center"
|
||||||
flexDirection: 'column',
|
justifyContent="Center"
|
||||||
alignItems: 'center',
|
gap="400"
|
||||||
justifyContent: 'center',
|
style={{ height: '100vh', padding: config.space.S700, textAlign: 'center' }}
|
||||||
height: '100vh',
|
|
||||||
gap: '16px',
|
|
||||||
fontFamily: 'sans-serif',
|
|
||||||
padding: '24px',
|
|
||||||
textAlign: 'center',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<h2 style={{ margin: 0 }}>Something went wrong</h2>
|
<Text size="H2">Something went wrong</Text>
|
||||||
<p style={{ margin: 0, color: '#666', maxWidth: '400px' }}>
|
<Text size="T300" priority="300" style={{ maxWidth: toRem(400) }}>
|
||||||
{error instanceof Error ? error.message : 'An unexpected error occurred.'}
|
{error instanceof Error ? error.message : 'An unexpected error occurred.'}
|
||||||
</p>
|
</Text>
|
||||||
<button
|
<Button variant="Primary" onClick={resetErrorBoundary}>
|
||||||
type="button"
|
<Text as="span" size="B400">
|
||||||
onClick={resetError}
|
|
||||||
style={{
|
|
||||||
padding: '8px 20px',
|
|
||||||
borderRadius: '6px',
|
|
||||||
border: 'none',
|
|
||||||
background: '#5865f2',
|
|
||||||
color: '#fff',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontSize: '14px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Try again
|
Try again
|
||||||
</button>
|
</Text>
|
||||||
</div>
|
</Button>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<TooltipContainerProvider value={portalContainer}>
|
<TooltipContainerProvider value={portalContainer}>
|
||||||
@@ -171,7 +164,7 @@ function App() {
|
|||||||
</OverlayContainerProvider>
|
</OverlayContainerProvider>
|
||||||
</PopOutContainerProvider>
|
</PopOutContainerProvider>
|
||||||
</TooltipContainerProvider>
|
</TooltipContainerProvider>
|
||||||
</Sentry.ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useRouteError, isRouteErrorResponse } from 'react-router-dom';
|
import { useRouteError, isRouteErrorResponse } from 'react-router-dom';
|
||||||
|
import { Box, Button, config, Text, toRem } from 'folds';
|
||||||
|
|
||||||
export function RouteError() {
|
export function RouteError() {
|
||||||
const error = useRouteError();
|
const error = useRouteError();
|
||||||
@@ -11,33 +12,22 @@ export function RouteError() {
|
|||||||
: 'An unexpected error occurred.';
|
: 'An unexpected error occurred.';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Box
|
||||||
style={{
|
direction="Column"
|
||||||
display: 'flex',
|
alignItems="Center"
|
||||||
flexDirection: 'column',
|
justifyContent="Center"
|
||||||
alignItems: 'center',
|
gap="400"
|
||||||
justifyContent: 'center',
|
style={{ height: '100dvh', padding: config.space.S700 }}
|
||||||
height: '100dvh',
|
|
||||||
gap: '16px',
|
|
||||||
padding: '32px',
|
|
||||||
fontFamily: 'sans-serif',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<h2 style={{ margin: 0, fontSize: '1.25rem' }}>Something went wrong</h2>
|
|
||||||
<p style={{ margin: 0, opacity: 0.7, textAlign: 'center' }}>{message}</p>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => window.location.reload()}
|
|
||||||
style={{
|
|
||||||
padding: '8px 20px',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: 'none',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
|
<Text size="H3">Something went wrong</Text>
|
||||||
|
<Text size="T300" priority="300" style={{ textAlign: 'center', maxWidth: toRem(400) }}>
|
||||||
|
{message}
|
||||||
|
</Text>
|
||||||
|
<Button variant="Primary" onClick={() => window.location.reload()}>
|
||||||
|
<Text as="span" size="B400">
|
||||||
Reload
|
Reload
|
||||||
</button>
|
</Text>
|
||||||
</div>
|
</Button>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable import/first */
|
/* eslint-disable import/first */
|
||||||
import * as Sentry from '@sentry/react';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { enableMapSet } from 'immer';
|
import { enableMapSet } from 'immer';
|
||||||
@@ -7,31 +6,6 @@ import '@fontsource-variable/inter/index.css';
|
|||||||
import 'folds/dist/style.css';
|
import 'folds/dist/style.css';
|
||||||
import { configClass, varsClass } from 'folds';
|
import { configClass, varsClass } from 'folds';
|
||||||
|
|
||||||
const sentryDsn = import.meta.env.VITE_SENTRY_DSN;
|
|
||||||
if (sentryDsn) {
|
|
||||||
Sentry.init({
|
|
||||||
dsn: sentryDsn,
|
|
||||||
environment: import.meta.env.MODE,
|
|
||||||
release: import.meta.env.VITE_APP_VERSION,
|
|
||||||
// browserTracingIntegration omitted — it injects sentry-trace/baggage headers
|
|
||||||
// into outgoing fetch calls, which breaks Synapse CORS on matrix.lotusguild.org
|
|
||||||
// No propagation targets — we don't control the Matrix server's CORS allow-list
|
|
||||||
tracePropagationTargets: [],
|
|
||||||
tracesSampleRate: 0,
|
|
||||||
// Don't send PII (IPs, usernames) — this is a private chat app
|
|
||||||
sendDefaultPii: false,
|
|
||||||
// Forward Sentry logs to the dashboard
|
|
||||||
enableLogs: true,
|
|
||||||
// Suppress benign PostmessageTransport / matrixRTC heartbeat timeouts (upstream library noise)
|
|
||||||
ignoreErrors: ['Request timed out'],
|
|
||||||
beforeSend(event) {
|
|
||||||
// Drop any event that may have leaked an access token into breadcrumbs/data
|
|
||||||
if (JSON.stringify(event).includes('access_token')) return null;
|
|
||||||
return event;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
enableMapSet();
|
enableMapSet();
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|||||||
+1
-16
@@ -1,6 +1,5 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import { sentryVitePlugin } from '@sentry/vite-plugin';
|
|
||||||
import { wasm } from '@rollup/plugin-wasm';
|
import { wasm } from '@rollup/plugin-wasm';
|
||||||
import inject from '@rollup/plugin-inject';
|
import inject from '@rollup/plugin-inject';
|
||||||
import { viteStaticCopy } from 'vite-plugin-static-copy';
|
import { viteStaticCopy } from 'vite-plugin-static-copy';
|
||||||
@@ -261,20 +260,6 @@ export default defineConfig({
|
|||||||
react(),
|
react(),
|
||||||
copyPdfWorker(),
|
copyPdfWorker(),
|
||||||
lotusDenoise(),
|
lotusDenoise(),
|
||||||
...(process.env.SENTRY_AUTH_TOKEN
|
|
||||||
? [
|
|
||||||
sentryVitePlugin({
|
|
||||||
org: 'lotus-guild',
|
|
||||||
project: 'javascript-react',
|
|
||||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
|
||||||
sourcemaps: {
|
|
||||||
filesToDeleteAfterUpload: ['./dist/**/*.map'],
|
|
||||||
},
|
|
||||||
release: { name: process.env.VITE_APP_VERSION ?? 'lotus' },
|
|
||||||
telemetry: false,
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
VitePWA({
|
VitePWA({
|
||||||
srcDir: 'src',
|
srcDir: 'src',
|
||||||
filename: 'sw.ts',
|
filename: 'sw.ts',
|
||||||
@@ -302,7 +287,7 @@ export default defineConfig({
|
|||||||
build: {
|
build: {
|
||||||
target: 'esnext',
|
target: 'esnext',
|
||||||
outDir: 'dist',
|
outDir: 'dist',
|
||||||
sourcemap: process.env.SENTRY_AUTH_TOKEN ? 'hidden' : false,
|
sourcemap: false,
|
||||||
copyPublicDir: false,
|
copyPublicDir: false,
|
||||||
// manualChunks must be in rolldownOptions (not rollupOptions) for Vite 8 / Rolldown
|
// manualChunks must be in rolldownOptions (not rollupOptions) for Vite 8 / Rolldown
|
||||||
rolldownOptions: {
|
rolldownOptions: {
|
||||||
|
|||||||
Reference in New Issue
Block a user