diff --git a/README.md b/README.md index 546e491a1..ecbabcf2c 100644 --- a/README.md +++ b/README.md @@ -115,30 +115,33 @@ For more details on the packages, see the For operating and deploying Element Call on your own server, refer to the [**Self-Hosting Guide**](./docs/self_hosting.md). -## đź§­ MatrixRTC Backend Discovery and Selection +## MatrixRTC Transports -For proper Element Call operation each site deployment needs a MatrixRTC backend -setup as outlined in the [Self-Hosting Guide](./docs/self_hosting.md). A typical -federated site deployment for three different sites A, B and C is depicted below. +For proper operation of Element Call, each deployment needs to set up a +MatrixRTC transport in the form of a LiveKit server as outlined in the +[Self-Hosting Guide](./docs/self_hosting.md). A typical federated site +deployment for three different sites A, B and C is depicted below.

Element Call federated setup

-### Backend Discovery +### Transport Discovery -The MatrixRTC backend (according to -[MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143)) is -announced by the Matrix site's `.well-known/matrix/client` file and discovered -via the `org.matrix.msc4143.rtc_foci` key, e.g.: +Element Call discovers the available MatrixRTC transports (as defined by +[MSC4519](https://github.com/matrix-org/matrix-spec-proposals/pull/4519)) by +hitting the `GET /_matrix/client/unstable/org.matrix.msc4143/rtc/transports` +endpoint of the Client-Server API. An example response: ```json -"org.matrix.msc4143.rtc_foci": [ +{ + "rtc_transports": [ { - "type": "livekit", - "livekit_service_url": "https://matrix-rtc.example.com/livekit/jwt" - }, -] + "type": "livekit", + "livekit_service_url": "https://matrix-rtc.example.com/livekit/jwt" + } + ] +} ``` where the format for MatrixRTC using LiveKit backend is defined in @@ -149,7 +152,7 @@ via `livekit_service_url`. ### Backend Selection -- Each call participant proposes their discovered MatrixRTC backend from +- Each call participant proposes their discovered MatrixRTC transport from `org.matrix.msc4143.rtc_foci` in their `org.matrix.msc3401.call.member` state event. - For the **LiveKit** MatrixRTC backend ([MSC4195](https://github.com/hughns/matrix-spec-proposals/blob/hughns/matrixrtc-livekit/proposals/4195-matrixrtc-livekit.md)), @@ -244,10 +247,9 @@ pnpm backend > [!NOTE] > To ensure your local development frontend functions properly, you’ll need to -> add certificate exceptions in your browser for `https://localhost:3000`, -> `https://matrix-rtc.m.localhost/livekit/jwt/healthz` and -> `https://synapse.m.localhost/.well-known/matrix/client`. This can be either -> done by adding the minimum localhost CA +> add certificate exceptions in your browser for `https://localhost:3000` and +> `https://matrix-rtc.m.localhost/livekit/jwt/healthz`. This can be done either +> by adding the minimum localhost CA > ([./backend/dev_tls_local-ca.crt](./backend/dev_tls_local-ca.crt)) to your web > browser's trusted certificates or by simply copying and pasting each URL into > your browser’s address bar and follow the prompts to add the exception. diff --git a/backend/dev_homeserver-othersite.yaml b/backend/dev_homeserver-othersite.yaml index 7eb8f294a..3f4c18413 100644 --- a/backend/dev_homeserver-othersite.yaml +++ b/backend/dev_homeserver-othersite.yaml @@ -40,6 +40,8 @@ experimental_features: msc4222_enabled: true # sticky events for MatrixRTC user state msc4354_enabled: true + # MatrixRTC + msc4143_enabled: true # The maximum allowed duration by which sent events can be delayed, as # per MSC4140. Must be a positive value if set. Defaults to no @@ -54,7 +56,6 @@ enable_registration_without_verification: true registration_shared_secret: "test_shared_secret_for_local_dev_only" report_stats: false -serve_server_wellknown: true # Ratelimiting settings for client actions (registration, login, messaging). # @@ -67,3 +68,8 @@ rc_message: # Currently the heart-beat is every 5 seconds which translates into a rate of 0.2s per_second: 0.5 burst_count: 30 + +matrix_rtc: + transports: + - type: livekit + livekit_service_url: https://matrix-rtc.othersite.m.localhost/livekit/jwt diff --git a/backend/dev_homeserver.yaml b/backend/dev_homeserver.yaml index 0aea2ece2..e38e91e5f 100644 --- a/backend/dev_homeserver.yaml +++ b/backend/dev_homeserver.yaml @@ -40,6 +40,8 @@ experimental_features: msc4222_enabled: true # sticky events for MatrixRTC user state msc4354_enabled: true + # MatrixRTC + msc4143_enabled: true # The maximum allowed duration by which sent events can be delayed, as # per MSC4140. Must be a positive value if set. Defaults to no @@ -54,7 +56,6 @@ enable_registration_without_verification: true registration_shared_secret: "test_shared_secret_for_local_dev_only" report_stats: false -serve_server_wellknown: true # Ratelimiting settings for client actions (registration, login, messaging). # @@ -67,3 +68,8 @@ rc_message: # Currently the heart-beat is every 5 seconds which translates into a rate of 0.2s per_second: 0.5 burst_count: 30 + +matrix_rtc: + transports: + - type: livekit + livekit_service_url: https://matrix-rtc.m.localhost/livekit/jwt diff --git a/backend/dev_nginx.conf b/backend/dev_nginx.conf index 6ec0d7010..c227370df 100644 --- a/backend/dev_nginx.conf +++ b/backend/dev_nginx.conf @@ -1,4 +1,4 @@ -# Synapse reverse proxy including .well-known/matrix/client +# Synapse reverse proxy # domain synapse.m.localhost server { listen 80; @@ -11,18 +11,6 @@ server { ssl_certificate /root/ssl/cert.pem; ssl_certificate_key /root/ssl/key.pem; - # well-known config adding rtc_foci backend - # Note well-known is currently not effective due to: - # https://spec.matrix.org/v1.12/client-server-api/#well-known-uri the spec - # says it must be at https://$server_name/... (implied port 443) Hence, we - # currently rely for local development environment on deprecated config.json - # setting for livekit_service_url - location /.well-known/matrix/client { - add_header Access-Control-Allow-Origin *; - return 200 '{"m.homeserver": {"base_url": "https://synapse.m.localhost"}, "org.matrix.msc4143.rtc_foci": [{"type": "livekit", "livekit_service_url": "https://matrix-rtc.m.localhost/livekit/jwt"}]}'; - default_type application/json; - } - # Reverse proxy for Matrix Synapse Homeserver # This is also required for development environment. # Reason: the lk-jwt-service uses the federation API for the openid token @@ -44,7 +32,7 @@ server { } -# Synapse reverse proxy including .well-known/matrix/client +# Synapse reverse proxy # domain synapse.othersite.m.localhost server { listen 80; @@ -57,18 +45,6 @@ server { ssl_certificate /root/ssl/cert.pem; ssl_certificate_key /root/ssl/key.pem; - # well-known config adding rtc_foci backend - # Note well-known is currently not effective due to: - # https://spec.matrix.org/v1.12/client-server-api/#well-known-uri the spec - # says it must be at https://$server_name/... (implied port 443) Hence, we - # currently rely for local development environment on deprecated config.json - # setting for livekit_service_url - location /.well-known/matrix/client { - add_header Access-Control-Allow-Origin *; - return 200 '{"m.homeserver": {"base_url": "https://synapse.othersite.m.localhost"}, "org.matrix.msc4143.rtc_foci": [{"type": "livekit", "livekit_service_url": "https://matrix-rtc.othersite.m.localhost/livekit/jwt"}]}'; - default_type application/json; - } - # Reverse proxy for Matrix Synapse Homeserver # This is also required for development environment. # Reason: the lk-jwt-service uses the federation API for the openid token diff --git a/backend/playwright_homeserver-othersite.yaml b/backend/playwright_homeserver-othersite.yaml index 86c77b35f..5bce4c1d5 100644 --- a/backend/playwright_homeserver-othersite.yaml +++ b/backend/playwright_homeserver-othersite.yaml @@ -40,6 +40,8 @@ experimental_features: msc4222_enabled: true # sticky events for MatrixRTC user state msc4354_enabled: true + # MatrixRTC + msc4143_enabled: true # The maximum allowed duration by which sent events can be delayed, as # per MSC4140. Must be a positive value if set. Defaults to no @@ -54,7 +56,6 @@ enable_registration_without_verification: true registration_shared_secret: "test_shared_secret_for_local_dev_only" report_stats: false -serve_server_wellknown: true # Ratelimiting settings for client actions (registration, login, messaging). # @@ -84,3 +85,8 @@ rc_login: rc_registration: per_second: 10000 burst_count: 10000 + +matrix_rtc: + transports: + - type: livekit + livekit_service_url: https://matrix-rtc.othersite.m.localhost/livekit/jwt diff --git a/backend/playwright_homeserver.yaml b/backend/playwright_homeserver.yaml index 8f4375241..d0439e42c 100644 --- a/backend/playwright_homeserver.yaml +++ b/backend/playwright_homeserver.yaml @@ -40,6 +40,8 @@ experimental_features: msc4222_enabled: true # sticky events for MatrixRTC user state msc4354_enabled: true + # MatrixRTC + msc4143_enabled: true # The maximum allowed duration by which sent events can be delayed, as # per MSC4140. Must be a positive value if set. Defaults to no @@ -54,7 +56,6 @@ enable_registration_without_verification: true registration_shared_secret: "test_shared_secret_for_local_dev_only" report_stats: false -serve_server_wellknown: true # Ratelimiting settings for client actions (registration, login, messaging). # @@ -84,3 +85,8 @@ rc_login: rc_registration: per_second: 10000 burst_count: 10000 + +matrix_rtc: + transports: + - type: livekit + livekit_service_url: https://matrix-rtc.m.localhost/livekit/jwt diff --git a/docker-compose-playwright.yml b/docker-compose-playwright.yml index eecb172d2..4dac164ff 100644 --- a/docker-compose-playwright.yml +++ b/docker-compose-playwright.yml @@ -13,7 +13,7 @@ services: - ./backend/playwright_homeserver-othersite.yaml:/data/cfg/homeserver.yaml:Z element-web: # Pin to a SHA so that upstream cannot break our tests. Renovate handles regular updates. - image: ghcr.io/element-hq/element-web:develop@sha256:b4e2d2d0919f26362755d4df29dbc7a55075880fdef329f53a7937241712c526 + image: ghcr.io/element-hq/element-web:develop@sha256:af44e1e18bdd363a3332a577c3af66a9dde4ad352508a42ee5012c4a9528b30d element-web-1: # Pin to a SHA so that upstream cannot break our tests. Renovate handles regular updates. - image: ghcr.io/element-hq/element-web:develop@sha256:b4e2d2d0919f26362755d4df29dbc7a55075880fdef329f53a7937241712c526 + image: ghcr.io/element-hq/element-web:develop@sha256:af44e1e18bdd363a3332a577c3af66a9dde4ad352508a42ee5012c4a9528b30d diff --git a/docs/self_hosting.md b/docs/self_hosting.md index 224a82cd8..0bdfe6284 100644 --- a/docs/self_hosting.md +++ b/docs/self_hosting.md @@ -68,10 +68,10 @@ As a prerequisite for the make sure that your Synapse server has either a `federation` or `openid` [listener configured](https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html#listeners). -### MatrixRTC Backend +### LiveKit backend -In order to **guarantee smooth operation** of Element Call, a MatrixRTC backend is -required for each site deployment. +In order to **guarantee smooth operation** of Element Call, a dedicated LiveKit +backend is required for each site deployment. ![MSC4195 compatible setup](MSC4195_setup.drawio.png) @@ -165,7 +165,7 @@ Using Haproxy, you can achieve this by: use_backend mxrtc_auth_backend if is_mxrtc_auth matrixrtc_domain # Backend -## MatrixRTC backend +## LiveKit backend backend sfu_backend server livekit 127.0.0.1:7880 http-request set-path %[path,regsub(^/livekit/sfu/,/)] @@ -187,43 +187,22 @@ backend mxrtc_auth_backend ``` -#### MatrixRTC backend announcement +#### MatrixRTC transport announcement -> [!IMPORTANT] -> As defined in -> [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143), -> the MatrixRTC backend(s) must be announced to the client via your **Matrix site's -> `.well-known/matrix/client`** file (e.g. -> `example.com/.well-known/matrix/client` matching the site deployment example -> from above). The configuration is a list of Foci configs: +Enable the unstable feature flag `msc4143_enabled`, and update the +[`matrix_rtc` section](https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html#matrix_rtc) +of your Synapse config file: -```json -"org.matrix.msc4143.rtc_foci": [ - { - "type": "livekit", - "livekit_service_url": "https://matrix-rtc.example.com/livekit/jwt" - }, - { - "type": "livekit", - "livekit_service_url": "https://matrix-rtc-2.example.com/livekit/jwt" - } -] +```yaml +matrix_rtc: + transports: + - type: livekit + livekit_service_url: https://matrix-rtc.example.com/livekit/jwt ``` -Make sure this file is served with the correct MIME type (`application/json`). -Additionally, ensure the appropriate CORS headers are set to allow web clients -to access it across origins. For more details, refer to the -[Matrix Client-Server API: 2. Web Browser Clients](https://spec.matrix.org/latest/client-server-api/#web-browser-clients). - -``` -Access-Control-Allow-Origin: * -Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS -Access-Control-Allow-Headers: X-Requested-With, Content-Type, Authorization -``` - -> [!NOTE] -> Most `org.matrix.msc4143.rtc_foci` configurations will only have one entry in -> the array. +The transport you specify will be made available to clients over the +`/_matrix/client/unstable/org.matrix.msc4143/rtc/transports` endpoint as defined +in [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143). ## Building Element Call @@ -278,7 +257,7 @@ runtime. Documentation and default values for `public/config.json` can be found in [ConfigOptions.ts](../src/config/ConfigOptions.ts). > [!CAUTION] -> Please note configuring MatrixRTC backend via `config.json` of +> Please note configuring LiveKit backend via `config.json` of > Element Call is only available for developing and debug purposes. Relying on > it might break Element Call going forward! diff --git a/locales/en/app.json b/locales/en/app.json index 3ff956a6a..2b3e08358 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -61,7 +61,7 @@ "crypto_version": "Crypto version: {{version}}", "custom_livekit_url": { "current_url": "Currently set to: ", - "from_config": "Currently, no overwrite is set. Url from well-known or config is used.", + "from_config": "Currently, no overwrite is set. Url from config is used.", "label": "Custom Livekit-url", "reset": "Reset overwrite", "save": "Save", @@ -196,13 +196,23 @@ "room_auth_view_ssla_caption": "By clicking \"Join call now\", you agree to our <2>Software and Services License Agreement (SSLA)", "screenshare_button_label": "Share screen", "settings": { + "advanced_camera_description": "Configure resolution, framerate, bitrate, and codec for camera video. Changes apply on next call join.", + "advanced_camera_label": "Advanced camera settings", + "advanced_screen_share_description": "Configure resolution, framerate, bitrate, and codec for screen sharing", + "advanced_screen_share_label": "Advanced screen share settings", + "audio_processing_description": "Changes apply on next call join.", + "audio_processing_header": "Audio processing", "audio_tab": { "effect_volume_description": "Adjust the volume at which reactions and hand raised effects play.", "effect_volume_label": "Sound effect volume" }, + "auto_gain_control_label": "Automatic gain control", "background_blur_header": "Background", "background_blur_label": "Blur the background of the video", + "bitrate_label": "Bitrate", "blur_not_supported_by_browser": "(Background blur is not supported by this device.)", + "camera_header": "Camera quality", + "codec_label": "Codec", "developer_tab_title": "Developer", "devices": { "activating": "Activating…", @@ -218,12 +228,15 @@ "speaker": "Speaker", "speaker_numbered": "Speaker {{n}}" }, + "echo_cancellation_label": "Echo cancellation", "feedback_tab_body": "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.", "feedback_tab_description_label": "Your feedback", "feedback_tab_h4": "Submit feedback", "feedback_tab_send_logs_label": "Include debug logs", "feedback_tab_thank_you": "Thanks, we received your feedback!", "feedback_tab_title": "Feedback", + "framerate_label": "Framerate", + "noise_suppression_label": "Noise suppression", "opt_in_description": "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.", "preferences_tab": { "developer_mode_label": "Developer mode", @@ -235,7 +248,9 @@ "reactions_show_label": "Show reactions", "show_hand_raised_timer_description": "Show a timer when a participant raises their hand", "show_hand_raised_timer_label": "Show hand raise duration" - } + }, + "resolution_label": "Resolution", + "screen_share_header": "Screen sharing" }, "star_rating_input_label_one": "{{count}} star", "star_rating_input_label_other": "{{count}} stars", diff --git a/package.json b/package.json index 68b718aef..2f4faa601 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "@typescript-eslint/utils": "^8.61.0", "@use-gesture/react": "^10.2.11", "@vector-im/compound-design-tokens": "^10.0.0", - "@vector-im/compound-web": "^9.3.0", + "@vector-im/compound-web": "^10.0.0", "@vitejs/plugin-react": "^6.0.2", "@vitest/browser-playwright": "^4.1.5", "@vitest/coverage-v8": "^4.0.18", @@ -97,7 +97,7 @@ "lodash-es": "^4.17.21", "loglevel": "^1.9.1", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", - "matrix-widget-api": "^1.16.1", + "matrix-widget-api": "^1.18.0", "node-stdlib-browser": "^1.3.1", "normalize.css": "^8.0.1", "observable-hooks": "^4.2.3", @@ -131,5 +131,5 @@ "vitest": "^4.1.5", "vitest-axe": "^1.0.0-pre.3" }, - "packageManager": "pnpm@11.6.0+sha512.9a36518224080c6fe5165afdcfe79bfa118c29be703f3f462b1e32efe1e98e47e8750b148e08286250aad4113cc7993ca413c4e2cd447752708c2ee5751bc95f" + "packageManager": "pnpm@11.21.0+sha512.521705bce689924eac72f5a3587122f362689ef6571e55ba80076fd637c11132ecffada26fad4ea79c485bfddbfd3d5a2a5b05805a77e893de71ec8a6cca3bb1" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 91f9a656b..0519571fa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,10 +39,10 @@ importers: version: 11.7.12 '@livekit/components-core': specifier: ^0.12.0 - version: 0.12.14(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(tslib@2.8.1) + version: 0.12.15(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(tslib@2.8.1) '@livekit/components-react': specifier: ^2.0.0 - version: 2.9.23(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tslib@2.8.1) + version: 2.9.24(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tslib@2.8.1) '@livekit/track-processors': specifier: ^0.7.1 version: 0.7.2(@types/dom-mediacapture-transform@0.1.11)(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22)) @@ -66,13 +66,13 @@ importers: version: 10.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@rolldown/plugin-babel': specifier: ^0.2.3 - version: 0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) + version: 0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) '@sentry/react': specifier: ^8.0.0 version: 8.55.2(react@19.2.8) '@sentry/vite-plugin': specifier: ^3.0.0 - version: 3.6.1 + version: 3.6.1(supports-color@7.2.0) '@storybook/addon-docs': specifier: ^10.3.6 version: 10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(rollup@4.60.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) @@ -81,7 +81,7 @@ importers: version: 10.5.5(@vitest/browser-playwright@4.1.10)(@vitest/browser@4.1.10)(@vitest/runner@4.1.10)(react@19.2.8)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(vitest@4.1.10) '@storybook/react-vite': specifier: ^10.3.6 - version: 10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(rollup@4.60.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) + version: 10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(rollup@4.60.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(supports-color@7.2.0)(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) '@testing-library/dom': specifier: ^10.1.0 version: 10.4.1 @@ -126,7 +126,7 @@ importers: version: 2.15.0 '@typescript-eslint/utils': specifier: ^8.61.0 - version: 8.65.0(eslint@8.57.1)(typescript@5.9.3) + version: 8.65.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) '@use-gesture/react': specifier: ^10.2.11 version: 10.3.1(react@19.2.8) @@ -134,11 +134,11 @@ importers: specifier: ^10.0.0 version: 10.2.3(@types/react@19.2.17)(react@19.2.8) '@vector-im/compound-web': - specifier: ^9.3.0 - version: 9.9.0(@fontsource/inconsolata@5.3.0)(@fontsource/inter@5.3.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@vector-im/compound-design-tokens@10.2.3(@types/react@19.2.17)(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + specifier: ^10.0.0 + version: 10.0.1(@fontsource/inconsolata@5.3.0)(@fontsource/inter@5.3.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@vector-im/compound-design-tokens@10.2.3(@types/react@19.2.17)(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@vitejs/plugin-react': specifier: ^6.0.2 - version: 6.0.5(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) + version: 6.0.5(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) '@vitest/browser-playwright': specifier: ^4.1.5 version: 4.1.10(playwright@1.62.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10) @@ -162,10 +162,10 @@ importers: version: link:eslint eslint-plugin-storybook: specifier: ^10.3.6 - version: 10.5.5(eslint@8.57.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(typescript@5.9.3) + version: 10.5.5(eslint@8.57.1(supports-color@7.2.0))(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(supports-color@7.2.0)(typescript@5.9.3) fetch-mock: specifier: 11.1.5 - version: 11.1.5 + version: 11.1.5(node-fetch@2.7.0) i18next: specifier: ^26.0.0 version: 26.3.6(typescript@5.9.3) @@ -177,7 +177,7 @@ importers: version: 1.67.3(@types/node@24.13.3)(react-dom@19.2.8(react@19.2.8))(typescript@5.9.3) jsdom: specifier: ^26.0.0 - version: 26.1.0 + version: 26.1.0(supports-color@7.2.0) knip: specifier: ^5.86.0 version: 5.88.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@24.13.3)(typescript@5.9.3) @@ -192,9 +192,9 @@ importers: version: 1.9.2 matrix-js-sdk: specifier: github:matrix-org/matrix-js-sdk#develop - version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8c95727b6278fe7942c20d0b9485f984dd0694b7 + version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/178dc6528ebc6a15e2f6fab38410127f16b500d0 matrix-widget-api: - specifier: ^1.16.1 + specifier: ^1.18.0 version: 1.18.0 node-stdlib-browser: specifier: ^1.3.1 @@ -282,13 +282,13 @@ importers: version: 0.2.1(node-stdlib-browser@1.3.1)(rollup@4.60.1)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) vite-plugin-svgr: specifier: ^4.0.0 - version: 4.5.0(rollup@4.60.1)(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) + version: 4.5.0(rollup@4.60.1)(supports-color@7.2.0)(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) vite-plugin-wasm: specifier: ^3.6.0 version: 3.6.0(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) vitest: specifier: ^4.1.5 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0(supports-color@7.2.0))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) vitest-axe: specifier: ^1.0.0-pre.3 version: 1.0.0-pre.5(vitest@4.1.10) @@ -332,10 +332,6 @@ packages: resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.29.7': - resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} - engines: {node: '>=6.9.0'} - '@babel/generator@7.29.8': resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} engines: {node: '>=6.9.0'} @@ -391,14 +387,13 @@ packages: resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} + '@babel/runtime@8.0.0': + resolution: {integrity: sha512-sL6cvO2IfkSu/iU+zs2S/w01B7A8V7suXSIKEN4hPFFdZoiPGxrj5pAG0lCaqLWiEIrjKzdznIWuaLcxPR53qw==} + '@babel/template@7.29.7': resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.7': - resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} - engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.8': resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} engines: {node: '>=6.9.0'} @@ -1164,15 +1159,15 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@livekit/components-core@0.12.14': - resolution: {integrity: sha512-6OKP/1Ok2fCZewDLKd3SzaTb7KvfZl/6hjggI+TgUdhp0Y7HBg3+tHA7YmhZRc0BO8wIyVy4VgTPn7qkLHt2nQ==} + '@livekit/components-core@0.12.15': + resolution: {integrity: sha512-bDkK+jkPMgyT4a5lZmdcuc/hZ8fZUrXW+qQPNHVeSpXCBMzk5q9QWzx+HPtTsPJ8hnncWGfoT81RnXHBsy+Z8g==} engines: {node: '>=18'} peerDependencies: livekit-client: ^2.20.1 tslib: ^2.6.2 - '@livekit/components-react@2.9.23': - resolution: {integrity: sha512-clO+0g/u3YBpuOvnAjUSAJBH/o7w+RpUAseswjiSML9rHrXlTUhwBNm8LPk+qN5GOU3A6lzsNnFYVpUHUBVOkg==} + '@livekit/components-react@2.9.24': + resolution: {integrity: sha512-qw5Oy1EfPg1f/xrKYrFielSrmtxY8BZ9mDt6299G5aikotQIztmldBfTNosrVS7VSNit7qeYfOFSyKaML20W6Q==} engines: {node: '>=18'} peerDependencies: '@livekit/krisp-noise-filter': ^0.2.12 || ^0.3.0 || ^0.4.0 @@ -1196,8 +1191,8 @@ packages: '@types/dom-mediacapture-transform': ^0.1.9 livekit-client: ^1.12.0 || ^2.1.0 - '@matrix-org/matrix-sdk-crypto-wasm@18.3.1': - resolution: {integrity: sha512-VRjWhE1UgHnPpJ3b9B5+8z71ZC/HICFngPPFIN6ktzmUBKI5RusPujzbAQUoB3CgZ0yU58L99AfSQS4YTztSWw==} + '@matrix-org/matrix-sdk-crypto-wasm@18.4.0': + resolution: {integrity: sha512-osxkU1DQ+05+anGHapjWyvZqdHUb94Id37gy54mCKn1Cq/D7iGT5oEUEhjp4oTnCLo4TOtI6ULJ/LHsapaIptQ==} engines: {node: '>= 18'} '@mdx-js/react@3.1.1': @@ -3256,8 +3251,8 @@ packages: react: optional: true - '@vector-im/compound-web@9.9.0': - resolution: {integrity: sha512-GjAEhzB/mt9MA7Y1ySe6/AlW26vodDu4hvTi1ThB5rTrt4KAiHMpvk+T56KV5Lwo0VvBxAyHMtm/IY5tkyH12A==} + '@vector-im/compound-web@10.0.1': + resolution: {integrity: sha512-CT3aGMalPSf53o3JUbBF7EA10Rfp+auftIuBwOUSna+hfZ/zPxGLTFcuCuOJ7L8hrzijM+V4I8L0PUkfyOczpg==} peerDependencies: '@fontsource/inconsolata': ^5 '@fontsource/inter': ^5 @@ -4381,8 +4376,8 @@ packages: resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} engines: {node: '>= 0.4'} - is-network-error@1.3.1: - resolution: {integrity: sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==} + is-network-error@1.3.2: + resolution: {integrity: sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==} engines: {node: '>=16'} is-number@7.0.0: @@ -4467,6 +4462,9 @@ packages: jose@6.2.4: resolution: {integrity: sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA==} + jose@6.2.9: + resolution: {integrity: sha512-XrchZOFZUl/T3vTwRe8XK+cJrGtMF4th1ARnDfwbBXFKThGhlsxEE4Zu03AD/bjJSt/9jT/mxrOCkJWOg77aPA==} + js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} @@ -4518,10 +4516,6 @@ packages: jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - jwt-decode@4.0.0: - resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} - engines: {node: '>=18'} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -4685,9 +4679,9 @@ packages: matrix-events-sdk@0.0.1: resolution: {integrity: sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==} - matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8c95727b6278fe7942c20d0b9485f984dd0694b7: - resolution: {gitHosted: true, integrity: sha512-ThGe61xx0d1L1weA8hvodzZOcATv2jR+LXd/WKwSsVT2GcR8AADY4pPxgaNvhFfl9tLMi2qMHeNay36RjDa6Zw==, tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8c95727b6278fe7942c20d0b9485f984dd0694b7} - version: 41.7.0 + matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/178dc6528ebc6a15e2f6fab38410127f16b500d0: + resolution: {gitHosted: true, integrity: sha512-UmlkoXs9VMZvNaDeQqBb2k+gLifnIqEZc5cxrLeEwdYEDakQLbRqrFtmvIp4ipUCjdIPkCgtzb7QLlpPBLAwqA==, tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/178dc6528ebc6a15e2f6fab38410127f16b500d0} + version: 42.1.0 engines: {node: '>=22.0.0'} matrix-widget-api@1.18.0: @@ -4829,10 +4823,6 @@ packages: resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} engines: {node: '>=12.20.0'} - oidc-client-ts@3.5.0: - resolution: {integrity: sha512-l2q8l9CTCTOlbX+AnK4p3M+4CEpKpyQhle6blQkdFhm0IsBqsxm15bYaSa11G7pWdsYr6epdsRZxJpCyCRbT8A==} - engines: {node: '>=18'} - once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -6254,34 +6244,26 @@ snapshots: '@babel/compat-data@7.29.7': {} - '@babel/core@7.29.7': + '@babel/core@7.29.7(supports-color@7.2.0)': dependencies: '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 + '@babel/generator': 7.29.8 '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) '@babel/helpers': 7.29.7 '@babel/parser': 7.29.8 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@7.2.0) '@babel/types': 7.29.8 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/generator@7.29.7': - dependencies: - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - '@babel/generator@7.29.8': dependencies: '@babel/parser': 7.29.8 @@ -6300,19 +6282,19 @@ snapshots: '@babel/helper-globals@7.29.7': {} - '@babel/helper-module-imports@7.29.7': + '@babel/helper-module-imports@7.29.7(supports-color@7.2.0)': dependencies: - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@7.2.0) '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/helper-module-imports': 7.29.7(supports-color@7.2.0) '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -6337,25 +6319,15 @@ snapshots: '@babel/runtime@7.29.7': {} + '@babel/runtime@8.0.0': {} + '@babel/template@7.29.7': dependencies: '@babel/code-frame': 7.29.7 '@babel/parser': 7.29.8 '@babel/types': 7.29.8 - '@babel/traverse@7.29.7': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.8 - '@babel/template': 7.29.7 - '@babel/types': 7.29.8 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - '@babel/traverse@7.29.8': + '@babel/traverse@7.29.8(supports-color@7.2.0)': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.8 @@ -6363,7 +6335,7 @@ snapshots: '@babel/parser': 7.29.8 '@babel/template': 7.29.7 '@babel/types': 7.29.8 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -6829,17 +6801,17 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.10.1(eslint@8.57.1)': + '@eslint-community/eslint-utils@4.10.1(eslint@8.57.1(supports-color@7.2.0))': dependencies: - eslint: 8.57.1 + eslint: 8.57.1(supports-color@7.2.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/eslintrc@2.1.4': + '@eslint/eslintrc@2.1.4(supports-color@7.2.0)': dependencies: ajv: 6.15.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -6920,10 +6892,10 @@ snapshots: '@formatjs/intl-localematcher': 0.6.2 tslib: 2.8.1 - '@humanwhocodes/config-array@0.13.0': + '@humanwhocodes/config-array@0.13.0(supports-color@7.2.0)': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) minimatch: 10.2.6 transitivePeerDependencies: - supports-color @@ -7092,7 +7064,7 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@livekit/components-core@0.12.14(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(tslib@2.8.1)': + '@livekit/components-core@0.12.15(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(tslib@2.8.1)': dependencies: '@floating-ui/dom': 1.7.6 livekit-client: 2.21.0(@types/dom-mediacapture-record@1.0.22) @@ -7100,12 +7072,12 @@ snapshots: rxjs: 7.8.2 tslib: 2.8.1 - '@livekit/components-react@2.9.23(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tslib@2.8.1)': + '@livekit/components-react@2.9.24(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tslib@2.8.1)': dependencies: - '@livekit/components-core': 0.12.14(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(tslib@2.8.1) + '@livekit/components-core': 0.12.15(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(tslib@2.8.1) clsx: 2.1.1 events: 3.3.0 - jose: 6.2.4 + jose: 6.2.9 livekit-client: 2.21.0(@types/dom-mediacapture-record@1.0.22) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) @@ -7124,7 +7096,7 @@ snapshots: '@types/dom-mediacapture-transform': 0.1.11 livekit-client: 2.21.0(@types/dom-mediacapture-record@1.0.22) - '@matrix-org/matrix-sdk-crypto-wasm@18.3.1': {} + '@matrix-org/matrix-sdk-crypto-wasm@18.4.0': {} '@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.8)': dependencies: @@ -8075,9 +8047,9 @@ snapshots: '@rolldown/binding-win32-x64-msvc@1.1.5': optional: true - '@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))': + '@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) picomatch: 4.0.4 rolldown: 1.1.5 optionalDependencies: @@ -8220,11 +8192,11 @@ snapshots: '@sentry-internal/replay-canvas': 8.55.2 '@sentry/core': 8.55.2 - '@sentry/bundler-plugin-core@3.6.1': + '@sentry/bundler-plugin-core@3.6.1(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) '@sentry/babel-plugin-component-annotate': 3.6.1 - '@sentry/cli': 2.58.5 + '@sentry/cli': 2.58.5(supports-color@7.2.0) dotenv: 16.6.1 find-up: 5.0.0 glob: 10.5.0 @@ -8258,9 +8230,9 @@ snapshots: '@sentry/cli-win32-x64@2.58.5': optional: true - '@sentry/cli@2.58.5': + '@sentry/cli@2.58.5(supports-color@7.2.0)': dependencies: - https-proxy-agent: 5.0.1 + https-proxy-agent: 5.0.1(supports-color@7.2.0) node-fetch: 2.7.0 progress: 2.0.3 proxy-from-env: 1.1.0 @@ -8287,9 +8259,9 @@ snapshots: hoist-non-react-statics: 3.3.2 react: 19.2.8 - '@sentry/vite-plugin@3.6.1': + '@sentry/vite-plugin@3.6.1(supports-color@7.2.0)': dependencies: - '@sentry/bundler-plugin-core': 3.6.1 + '@sentry/bundler-plugin-core': 3.6.1(supports-color@7.2.0) unplugin: 1.0.1 transitivePeerDependencies: - encoding @@ -8327,7 +8299,7 @@ snapshots: '@vitest/browser': 4.1.10(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10) '@vitest/browser-playwright': 4.1.10(playwright@1.62.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10) '@vitest/runner': 4.1.10 - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0(supports-color@7.2.0))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) transitivePeerDependencies: - react @@ -8366,16 +8338,16 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@storybook/react-vite@10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(rollup@4.60.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))': + '@storybook/react-vite@10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(rollup@4.60.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(supports-color@7.2.0)(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))': dependencies: '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) '@rollup/pluginutils': 5.4.0(rollup@4.60.1) '@storybook/builder-vite': 10.5.5(esbuild@0.28.1)(rollup@4.60.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) - '@storybook/react': 10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(typescript@5.9.3) + '@storybook/react': 10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(supports-color@7.2.0)(typescript@5.9.3) empathic: 2.0.1 magic-string: 0.30.21 react: 19.2.8 - react-docgen: 8.0.3 + react-docgen: 8.0.3(supports-color@7.2.0) react-dom: 19.2.8(react@19.2.8) resolve: 1.22.12 storybook: 10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8) @@ -8391,12 +8363,12 @@ snapshots: - supports-color - webpack - '@storybook/react@10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(typescript@5.9.3)': + '@storybook/react@10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@storybook/global': 5.0.0 '@storybook/react-dom-shim': 10.5.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8)) react: 19.2.8 - react-docgen: 8.0.3 + react-docgen: 8.0.3(supports-color@7.2.0) react-docgen-typescript: 2.4.0(typescript@5.9.3) react-dom: 19.2.8(react@19.2.8) storybook: 10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8) @@ -8407,54 +8379,54 @@ snapshots: transitivePeerDependencies: - supports-color - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.7)': + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) - '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.29.7)': + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) - '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.29.7)': + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) - '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.29.7)': + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) - '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.29.7)': + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) - '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.29.7)': + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) - '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.29.7)': + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) - '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.29.7)': + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) - '@svgr/babel-preset@8.1.0(@babel/core@7.29.7)': + '@svgr/babel-preset@8.1.0(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: - '@babel/core': 7.29.7 - '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.29.7) - '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.29.7) - '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.29.7) - '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.29.7) - '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.29.7) - '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.29.7) - '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.7) - '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@7.2.0) + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.29.7(supports-color@7.2.0)) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.29.7(supports-color@7.2.0)) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.29.7(supports-color@7.2.0)) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.29.7(supports-color@7.2.0)) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.29.7(supports-color@7.2.0)) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.29.7(supports-color@7.2.0)) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.7(supports-color@7.2.0)) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.7(supports-color@7.2.0)) - '@svgr/core@8.1.0(typescript@5.9.3)': + '@svgr/core@8.1.0(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - '@babel/core': 7.29.7 - '@svgr/babel-preset': 8.1.0(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@7.2.0) + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.7(supports-color@7.2.0)) camelcase: 6.3.0 cosmiconfig: 8.3.6(typescript@5.9.3) snake-case: 3.0.4 @@ -8467,11 +8439,11 @@ snapshots: '@babel/types': 7.29.8 entities: 4.5.0 - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))': + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(supports-color@7.2.0)(typescript@5.9.3))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 - '@svgr/babel-preset': 8.1.0(@babel/core@7.29.7) - '@svgr/core': 8.1.0(typescript@5.9.3) + '@babel/core': 7.29.7(supports-color@7.2.0) + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.7(supports-color@7.2.0)) + '@svgr/core': 8.1.0(supports-color@7.2.0)(typescript@5.9.3) '@svgr/hast-util-to-babel-ast': 8.0.0 svg-parser: 2.0.4 transitivePeerDependencies: @@ -8679,11 +8651,11 @@ snapshots: '@types/trusted-types@2.0.7': optional: true - '@typescript-eslint/project-service@8.65.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.65.0(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3) '@typescript-eslint/types': 8.65.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -8699,13 +8671,13 @@ snapshots: '@typescript-eslint/types@8.65.0': {} - '@typescript-eslint/typescript-estree@8.65.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.65.0(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.65.0(typescript@5.9.3) + '@typescript-eslint/project-service': 8.65.0(supports-color@7.2.0)(typescript@5.9.3) '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3) '@typescript-eslint/types': 8.65.0 '@typescript-eslint/visitor-keys': 8.65.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) minimatch: 10.2.6 semver: 7.8.5 tinyglobby: 0.2.17 @@ -8714,13 +8686,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.65.0(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.65.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1(supports-color@7.2.0)) '@typescript-eslint/scope-manager': 8.65.0 '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3) - eslint: 8.57.1 + '@typescript-eslint/typescript-estree': 8.65.0(supports-color@7.2.0)(typescript@5.9.3) + eslint: 8.57.1(supports-color@7.2.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -8744,7 +8716,7 @@ snapshots: '@types/react': 19.2.17 react: 19.2.8 - '@vector-im/compound-web@9.9.0(@fontsource/inconsolata@5.3.0)(@fontsource/inter@5.3.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@vector-im/compound-design-tokens@10.2.3(@types/react@19.2.17)(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@vector-im/compound-web@10.0.1(@fontsource/inconsolata@5.3.0)(@fontsource/inter@5.3.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@vector-im/compound-design-tokens@10.2.3(@types/react@19.2.17)(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@floating-ui/react': 0.27.20(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@floating-ui/react-dom': 2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -8766,12 +8738,12 @@ snapshots: - '@types/react-dom' - react-dom - '@vitejs/plugin-react@6.0.5(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))': + '@vitejs/plugin-react@6.0.5(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))': dependencies: '@rolldown/pluginutils': 1.0.1 vite: 8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0) optionalDependencies: - '@rolldown/plugin-babel': 0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) + '@rolldown/plugin-babel': 0.2.3(@babel/core@7.29.7(supports-color@7.2.0))(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) babel-plugin-react-compiler: 1.0.0 '@vitest/browser-playwright@4.1.10(playwright@1.62.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10)': @@ -8780,7 +8752,7 @@ snapshots: '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) playwright: 1.62.0 tinyrainbow: 3.1.1 - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0(supports-color@7.2.0))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) transitivePeerDependencies: - bufferutil - msw @@ -8796,7 +8768,7 @@ snapshots: pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.1 - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0(supports-color@7.2.0))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) ws: 8.21.1 transitivePeerDependencies: - bufferutil @@ -8816,7 +8788,7 @@ snapshots: obug: 2.1.4 std-env: 4.2.0 tinyrainbow: 3.1.1 - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0(supports-color@7.2.0))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) optionalDependencies: '@vitest/browser': 4.1.10(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10) @@ -8880,7 +8852,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.17 tinyrainbow: 3.1.1 - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0(supports-color@7.2.0))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) '@vitest/utils@3.2.4': dependencies: @@ -8904,9 +8876,9 @@ snapshots: acorn@8.18.0: {} - agent-base@6.0.2: + agent-base@6.0.2(supports-color@7.2.0): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -9234,7 +9206,7 @@ snapshots: cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.1 - js-yaml: 4.3.0 + js-yaml: 4.3.1 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: @@ -9329,9 +9301,11 @@ snapshots: whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - debug@4.4.3: + debug@4.4.3(supports-color@7.2.0): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 7.2.0 decamelize@1.2.0: {} @@ -9507,11 +9481,11 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-storybook@10.5.5(eslint@8.57.1)(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(typescript@5.9.3): + eslint-plugin-storybook@10.5.5(eslint@8.57.1(supports-color@7.2.0))(storybook@10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8))(supports-color@7.2.0)(typescript@5.9.3): dependencies: '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/utils': 8.65.0(eslint@8.57.1)(typescript@5.9.3) - eslint: 8.57.1 + '@typescript-eslint/utils': 8.65.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + eslint: 8.57.1(supports-color@7.2.0) storybook: 10.5.5(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.8) transitivePeerDependencies: - supports-color @@ -9526,20 +9500,20 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@8.57.1: + eslint@8.57.1(supports-color@7.2.0): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1(supports-color@7.2.0)) '@eslint-community/regexpp': 4.12.2 - '@eslint/eslintrc': 2.1.4 + '@eslint/eslintrc': 2.1.4(supports-color@7.2.0) '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/config-array': 0.13.0(supports-color@7.2.0) '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 '@ungap/structured-clone': 1.3.3 ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -9655,13 +9629,15 @@ snapshots: optionalDependencies: picomatch: 4.0.5 - fetch-mock@11.1.5: + fetch-mock@11.1.5(node-fetch@2.7.0): dependencies: '@types/glob-to-regexp': 0.4.4 dequal: 2.0.3 glob-to-regexp: 0.4.1 is-subset: 0.1.1 regexparam: 3.0.0 + optionalDependencies: + node-fetch: 2.7.0 fflate@0.4.8: {} @@ -9859,26 +9835,26 @@ snapshots: html-parse-stringify@4.0.1: {} - http-proxy-agent@7.0.2: + http-proxy-agent@7.0.2(supports-color@7.2.0): dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color https-browserify@1.0.0: {} - https-proxy-agent@5.0.1: + https-proxy-agent@5.0.1(supports-color@7.2.0): dependencies: - agent-base: 6.0.2 - debug: 4.4.3 + agent-base: 6.0.2(supports-color@7.2.0) + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color - https-proxy-agent@7.0.6: + https-proxy-agent@7.0.6(supports-color@7.2.0): dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -10009,7 +9985,7 @@ snapshots: call-bind: 1.0.9 define-properties: 1.2.1 - is-network-error@1.3.1: {} + is-network-error@1.3.2: {} is-number@7.0.0: {} @@ -10079,6 +10055,8 @@ snapshots: jose@6.2.4: {} + jose@6.2.9: {} + js-tokens@10.0.0: {} js-tokens@4.0.0: {} @@ -10091,14 +10069,14 @@ snapshots: dependencies: argparse: 2.0.1 - jsdom@26.1.0: + jsdom@26.1.0(supports-color@7.2.0): dependencies: cssstyle: 4.6.0 data-urls: 5.0.0 decimal.js: 10.6.0 html-encoding-sniffer: 4.0.0 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 + http-proxy-agent: 7.0.2(supports-color@7.2.0) + https-proxy-agent: 7.0.6(supports-color@7.2.0) is-potential-custom-element-name: 1.0.1 nwsapi: 2.2.23 parse5: 7.3.0 @@ -10138,8 +10116,6 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - jwt-decode@4.0.0: {} - keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -10293,18 +10269,16 @@ snapshots: matrix-events-sdk@0.0.1: {} - matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8c95727b6278fe7942c20d0b9485f984dd0694b7: + matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/178dc6528ebc6a15e2f6fab38410127f16b500d0: dependencies: - '@babel/runtime': 7.29.2 - '@matrix-org/matrix-sdk-crypto-wasm': 18.3.1 + '@babel/runtime': 8.0.0 + '@matrix-org/matrix-sdk-crypto-wasm': 18.4.0 another-json: 0.2.0 bs58: 6.0.0 content-type: 2.0.0 - jwt-decode: 4.0.0 loglevel: 1.9.2 matrix-events-sdk: 0.0.1 matrix-widget-api: 1.18.0 - oidc-client-ts: 3.5.0 p-retry: 8.0.0 sdp-transform: 3.0.0 unhomoglyph: 1.0.6 @@ -10454,10 +10428,6 @@ snapshots: obug@2.1.4: {} - oidc-client-ts@3.5.0: - dependencies: - jwt-decode: 4.0.0 - once@1.4.0: dependencies: wrappy: 1.0.2 @@ -10642,7 +10612,7 @@ snapshots: p-retry@8.0.0: dependencies: - is-network-error: 1.3.1 + is-network-error: 1.3.2 p-try@2.2.0: {} @@ -11060,10 +11030,10 @@ snapshots: dependencies: typescript: 5.9.3 - react-docgen@8.0.3: + react-docgen@8.0.3(supports-color@7.2.0): dependencies: - '@babel/core': 7.29.7 - '@babel/traverse': 7.29.8 + '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/traverse': 7.29.8(supports-color@7.2.0) '@babel/types': 7.29.8 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.28.0 @@ -11516,7 +11486,7 @@ snapshots: terser@5.46.1: dependencies: '@jridgewell/source-map': 0.3.11 - acorn: 8.16.0 + acorn: 8.18.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -11745,11 +11715,11 @@ snapshots: transitivePeerDependencies: - rollup - vite-plugin-svgr@4.5.0(rollup@4.60.1)(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)): + vite-plugin-svgr@4.5.0(rollup@4.60.1)(supports-color@7.2.0)(typescript@5.9.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)): dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.60.1) - '@svgr/core': 8.1.0(typescript@5.9.3) - '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3)) + '@svgr/core': 8.1.0(supports-color@7.2.0)(typescript@5.9.3) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(supports-color@7.2.0)(typescript@5.9.3))(supports-color@7.2.0) vite: 8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0) transitivePeerDependencies: - rollup @@ -11782,9 +11752,9 @@ snapshots: axe-core: 4.11.3 chalk: 5.6.2 lodash-es: 4.18.1 - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0(supports-color@7.2.0))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) - vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)): + vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@26.1.0(supports-color@7.2.0))(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.10 '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0)) @@ -11812,7 +11782,7 @@ snapshots: '@vitest/browser-playwright': 4.1.10(playwright@1.62.0)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.102.0)(terser@5.46.1)(yaml@2.9.0))(vitest@4.1.10) '@vitest/coverage-v8': 4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10) '@vitest/ui': 4.1.10(vitest@4.1.10) - jsdom: 26.1.0 + jsdom: 26.1.0(supports-color@7.2.0) transitivePeerDependencies: - msw diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 30999eb19..c32736caa 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -9,7 +9,7 @@ allowBuilds: "@swc/core": true "core-js": true "esbuild": true - "matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8c95727b6278fe7942c20d0b9485f984dd0694b7": true + "matrix-js-sdk@git+https://github.com/matrix-org/matrix-js-sdk.git": true "protobufjs": true overrides: # We need compatible versions of RxJS in our dependencies and LiveKit's dependencies, but diff --git a/src/Slider.tsx b/src/Slider.tsx index c6520e422..29f9ef426 100644 --- a/src/Slider.tsx +++ b/src/Slider.tsx @@ -31,6 +31,11 @@ interface Props { max: number; step: number; disabled?: boolean; + /** + * Custom formatter for the tooltip label. If not provided, the value is + * displayed as a percentage. + */ + tooltipFormatter?: (value: number) => string; } /** @@ -46,6 +51,7 @@ export const Slider: FC = ({ max, step, disabled, + tooltipFormatter, }) => { const onValueChange = useCallback( ([v]: number[]) => onValueChangeProp(v), @@ -71,7 +77,14 @@ export const Slider: FC = ({ {/* Note: This is expected not to be visible on mobile.*/} - + diff --git a/src/config/ConfigOptions.ts b/src/config/ConfigOptions.ts index ff0aadc37..75704cfc8 100644 --- a/src/config/ConfigOptions.ts +++ b/src/config/ConfigOptions.ts @@ -74,10 +74,7 @@ export interface ConfigOptions { livekit?: { // The link to the service that returns a livekit url and token to use it. // This is a fallback link in case the homeserver in use does not advertise - // a livekit service url in the client well-known. - // The well known needs to be formatted like so: - // {"type":"livekit", "livekit_service_url":"https://livekit.example.com"} - // and stored under the key: "org.matrix.msc4143.rtc_foci" + // a livekit service url over the transports endpoint. livekit_service_url: string; }; @@ -105,6 +102,61 @@ export interface ConfigOptions { */ ssla?: string; + /** + * Media quality settings for video and screen sharing. + * These override the hardcoded LiveKit defaults. + */ + media_quality?: { + /** + * Video codec preference. The server must also have the codec enabled. + * @default "vp8" + */ + video_codec?: "vp8" | "vp9" | "h264" | "av1"; + + /** + * Camera video settings. + */ + video?: { + /** Max resolution height in pixels (e.g. 720, 1080, 1440). @default 720 */ + max_resolution?: number; + /** Max bitrate in bits per second. @default 1700000 */ + max_bitrate?: number; + /** Max framerate. @default 30 */ + max_framerate?: number; + /** + * Simulcast layers as an array of {height, bitrate} objects, + * ordered from lowest to highest quality. + * @default [{height: 180, bitrate: 160000}, {height: 360, bitrate: 450000}] + */ + simulcast_layers?: Array<{ + height: number; + bitrate: number; + }>; + }; + + /** + * Screen share settings. + */ + screen_share?: { + /** Max resolution height in pixels. @default 1080 */ + max_resolution?: number; + /** Max bitrate in bits per second. @default 5000000 */ + max_bitrate?: number; + /** Max framerate. @default 30 */ + max_framerate?: number; + /** + * Simulcast layers for screen sharing as an array of {height, bitrate, framerate} objects, + * ordered from lowest to highest quality. If omitted, LiveKit SDK defaults apply (1 extra + * layer at half resolution). + */ + simulcast_layers?: Array<{ + height: number; + bitrate: number; + framerate?: number; + }>; + }; + }; + media_devices?: { /** * Defines whether participants should start with audio enabled by default. @@ -185,6 +237,24 @@ export interface ConfigOptions { export interface ResolvedConfigOptions extends ConfigOptions { sync_disconnect_grace_period_ms: number; ssla: string; + media_quality: Required< + Pick, "video_codec"> + > & { + video: Required< + Pick< + NonNullable["video"]>, + "max_resolution" | "max_bitrate" | "max_framerate" + > + >; + screen_share: Required< + Pick< + NonNullable< + NonNullable["screen_share"] + >, + "max_resolution" | "max_bitrate" | "max_framerate" + > + >; + }; matrix_rtc_session: { wait_for_key_rotation_ms?: number; delayed_leave_event_delay_ms: number; @@ -201,6 +271,19 @@ export const DEFAULT_CONFIG: ResolvedConfigOptions = { }, sync_disconnect_grace_period_ms: 10000, ssla: "https://static.element.io/legal/element-software-and-services-license-agreement-uk-1.pdf", + media_quality: { + video_codec: "vp8", + video: { + max_resolution: 720, + max_bitrate: 1_700_000, + max_framerate: 30, + }, + screen_share: { + max_resolution: 1080, + max_bitrate: 5_000_000, + max_framerate: 30, + }, + }, matrix_rtc_session: { delayed_leave_event_delay_ms: 10000, network_error_retry_ms: 1000, diff --git a/src/grid/Grid.tsx b/src/grid/Grid.tsx index 05e4d6ed2..69bfc0760 100644 --- a/src/grid/Grid.tsx +++ b/src/grid/Grid.tsx @@ -41,6 +41,8 @@ import { TileWrapper } from "./TileWrapper"; import { usePrefersReducedMotion } from "../usePrefersReducedMotion"; import { useInitial } from "../useInitial"; +const MAX_ANIMATED_TILES = 50; // Capped for performance reasons + interface Rect { x: number; y: number; @@ -285,7 +287,6 @@ export function Grid< const [visibleTilesCallback, setVisibleTilesCallback] = useState(null); const tiles = useInitial(() => new Map>()); - const prefersReducedMotion = usePrefersReducedMotion(); const Slot: FC> = useMemo( () => @@ -372,6 +373,10 @@ export function Grid< // react-spring's imperative API during gestures to improve responsiveness const dragState = useRef(null); + // If true, disables animations + const immediate = + usePrefersReducedMotion() || placedTiles.length > MAX_ANIMATED_TILES; + const [tileTransitions, springRef] = useTransition( placedTiles, () => ({ @@ -389,9 +394,9 @@ export function Grid< y, width, height, - immediate: prefersReducedMotion, + immediate, }), - enter: { opacity: 1, scale: 1, immediate: prefersReducedMotion }, + enter: { opacity: 1, scale: 1, immediate }, update: ({ id, x, @@ -406,9 +411,9 @@ export function Grid< y, width, height, - immediate: prefersReducedMotion, + immediate, }, - leave: { opacity: 0, scale: 0, immediate: prefersReducedMotion }, + leave: { opacity: 0, scale: 0, immediate }, config: { mass: 0.7, tension: 252, friction: 25 }, }), // react-spring's types are bugged and can't infer the spring type @@ -441,8 +446,7 @@ export function Grid< y: tile.y, width: tile.width, height: tile.height, - immediate: - prefersReducedMotion || ((key): boolean => key === "zIndex"), + immediate: immediate || ((key): boolean => key === "zIndex"), // Allow the tile's position to settle before pushing its // z-index back down delay: (key): number => (key === "zIndex" ? 500 : 0), @@ -453,7 +457,7 @@ export function Grid< x: tileX, y: tileY, immediate: - prefersReducedMotion || + immediate || ((key): boolean => key === "zIndex" || key === "x" || key === "y"), }, diff --git a/src/grid/SpotlightLandscapeLayout.tsx b/src/grid/SpotlightLandscapeLayout.tsx index d76890c54..5da12a89c 100644 --- a/src/grid/SpotlightLandscapeLayout.tsx +++ b/src/grid/SpotlightLandscapeLayout.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import { type ReactNode } from "react"; +import { type FC, type ReactNode } from "react"; import { useObservableEagerState } from "observable-hooks"; import classNames from "classnames"; @@ -13,6 +13,9 @@ import { type CallLayout } from "./CallLayout"; import { type SpotlightLandscapeLayout as SpotlightLandscapeLayoutModel } from "../state/layout-types.ts"; import styles from "./SpotlightLandscapeLayout.module.css"; import { useUpdateLayout, useVisibleTiles } from "./Grid"; +import { type MediaViewModel } from "../state/media/MediaViewModel.ts"; +import { type Behavior } from "../state/Behavior.ts"; +import { useBehavior } from "../useBehavior.ts"; /** * An implementation of the "spotlight landscape" layout, in which the spotlight @@ -54,16 +57,10 @@ export const makeSpotlightLandscapeLayout: CallLayout< useUpdateLayout(); useVisibleTiles(model.setVisibleTiles); useObservableEagerState(minBounds$); - const withIndicators = - useObservableEagerState(model.spotlight.media$).length > 1; return (
-
+
{model.grid.map((m) => ( @@ -73,3 +70,20 @@ export const makeSpotlightLandscapeLayout: CallLayout< ); }, }); + +interface SpotlightSlotProps { + media$: Behavior; +} + +// This component isolates the subscription to the spotlight media so that it +// can change without causing the whole layout to re-render +const SpotlightSlot: FC = ({ media$ }) => { + const withIndicators = useBehavior(media$).length > 1; + return ( +
+ ); +}; diff --git a/src/initializer.tsx b/src/initializer.tsx index 7c6fc529d..91436d100 100644 --- a/src/initializer.tsx +++ b/src/initializer.tsx @@ -30,6 +30,7 @@ import { import { getUrlParams } from "./UrlParams"; import { Config } from "./config/Config"; +import { seedSettingsFromConfig } from "./settings/settings"; import { platform } from "./Platform"; import { isFailure } from "./utils/fetch"; import { initializeWidget } from "./widget"; @@ -237,6 +238,7 @@ export class Initializer { this.loadStates.config = LoadState.Loading; Config.init().then( () => { + seedSettingsFromConfig(Config.get().media_quality); this.loadStates.config = LoadState.Loaded; this.initStep(resolve); }, diff --git a/src/livekit/options.test.ts b/src/livekit/options.test.ts new file mode 100644 index 000000000..03134f0ce --- /dev/null +++ b/src/livekit/options.test.ts @@ -0,0 +1,213 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { describe, expect, it, vi } from "vitest"; +import { VideoPresets, type VideoPreset } from "livekit-client"; + +import { buildLiveKitOptions, getLiveKitOptions } from "./options"; +import { Config } from "../config/Config"; + +vi.mock("../config/Config", () => ({ + Config: { + get: vi.fn(), + }, +})); + +describe("buildLiveKitOptions", () => { + it("returns sensible defaults with no config", () => { + const opts = buildLiveKitOptions(); + expect(opts.adaptiveStream).toBe(true); + expect(opts.dynacast).toBe(true); + expect(opts.videoCaptureDefaults?.resolution).toEqual( + VideoPresets.h720.resolution, + ); + expect(opts.publishDefaults?.videoCodec).toBe("vp8"); + expect(opts.publishDefaults?.videoEncoding).toEqual({ + maxBitrate: 1_700_000, + maxFramerate: 30, + }); + expect(opts.publishDefaults?.screenShareEncoding).toEqual({ + maxBitrate: 5_000_000, + maxFramerate: 30, + }); + expect(opts.publishDefaults?.videoSimulcastLayers).toEqual([ + VideoPresets.h180, + VideoPresets.h360, + ]); + }); + + it("applies video codec from config", () => { + const opts = buildLiveKitOptions({ video_codec: "vp9" }); + expect(opts.publishDefaults?.videoCodec).toBe("vp9"); + }); + + it("applies video resolution and encoding from config", () => { + const baseVideoConfig = { + max_resolution: 1080, + max_bitrate: 3_000_000, + max_framerate: 60, + }; + const opts1080 = buildLiveKitOptions({ + video: baseVideoConfig, + }); + const opts1440 = buildLiveKitOptions({ + video: { ...baseVideoConfig, max_resolution: 1440 }, + }); + const opts2160 = buildLiveKitOptions({ + video: { ...baseVideoConfig, max_resolution: 2160 }, + }); + expect(opts1080.videoCaptureDefaults?.resolution).toEqual( + VideoPresets.h1080.resolution, + ); + expect(opts1440.videoCaptureDefaults?.resolution).toEqual( + VideoPresets.h1440.resolution, + ); + expect(opts2160.videoCaptureDefaults?.resolution).toEqual( + VideoPresets.h2160.resolution, + ); + expect(opts1080.publishDefaults?.videoEncoding).toEqual({ + maxBitrate: 3_000_000, + maxFramerate: 60, + }); + }); + + it("applies screen share encoding from config", () => { + const opts = buildLiveKitOptions({ + screen_share: { + max_bitrate: 8_000_000, + max_framerate: 15, + }, + }); + expect(opts.publishDefaults?.screenShareEncoding).toEqual({ + maxBitrate: 8_000_000, + maxFramerate: 15, + }); + }); + + it("uses DEFAULT_CONFIG defaults when only resolution is set", () => { + const opts = buildLiveKitOptions({ + screen_share: { + max_resolution: 720, + }, + }); + // Bitrate and framerate fall back to DEFAULT_CONFIG, not the preset + expect(opts.publishDefaults?.screenShareEncoding).toEqual({ + maxBitrate: 5_000_000, + maxFramerate: 30, + }); + }); + + it("maps low resolutions to the closest preset, rounding up", () => { + const expectations: [number, VideoPreset][] = [ + [180, VideoPresets.h180], + [360, VideoPresets.h360], + [480, VideoPresets.h540], + [540, VideoPresets.h540], + [720, VideoPresets.h720], + ]; + for (const [height, preset] of expectations) { + const opts = buildLiveKitOptions({ video: { max_resolution: height } }); + expect(opts.videoCaptureDefaults?.resolution).toEqual(preset.resolution); + } + }); + + it("screen share layers fall back to max_framerate, then 30", () => { + const fromMax = buildLiveKitOptions({ + screen_share: { + max_framerate: 15, + simulcast_layers: [{ height: 540, bitrate: 1_000_000 }], + }, + }); + expect( + fromMax.publishDefaults?.screenShareSimulcastLayers?.[0], + ).toMatchObject({ encoding: { maxFramerate: 15 } }); + + const fromDefault = buildLiveKitOptions({ + screen_share: { + simulcast_layers: [{ height: 540, bitrate: 1_000_000 }], + }, + }); + expect( + fromDefault.publishDefaults?.screenShareSimulcastLayers?.[0], + ).toMatchObject({ encoding: { maxFramerate: 30 } }); + }); + + it("applies custom video simulcast layers", () => { + const opts = buildLiveKitOptions({ + video: { + simulcast_layers: [ + { height: 180, bitrate: 100_000 }, + { height: 360, bitrate: 300_000 }, + { height: 540, bitrate: 600_000 }, + ], + max_framerate: 24, + }, + }); + const layers = opts.publishDefaults?.videoSimulcastLayers; + expect(layers).toHaveLength(3); + expect(layers?.[0]).toMatchObject({ + width: 320, + height: 180, + encoding: { maxBitrate: 100_000, maxFramerate: 24 }, + }); + expect(layers?.[2]).toMatchObject({ + width: 960, + height: 540, + encoding: { maxBitrate: 600_000, maxFramerate: 24 }, + }); + }); + + it("applies custom screen share simulcast layers", () => { + const opts = buildLiveKitOptions({ + screen_share: { + simulcast_layers: [{ height: 540, bitrate: 1_000_000, framerate: 5 }], + }, + }); + const layers = opts.publishDefaults?.screenShareSimulcastLayers; + expect(layers).toHaveLength(1); + expect(layers?.[0]).toMatchObject({ + width: 960, + height: 540, + encoding: { maxBitrate: 1_000_000, maxFramerate: 5 }, + }); + }); + + it("does not include screenShareSimulcastLayers when not configured", () => { + const opts = buildLiveKitOptions(); + expect(opts.publishDefaults?.screenShareSimulcastLayers).toBeUndefined(); + }); + + it("backupCodec always uses stock VP8 720p encoding", () => { + const opts = buildLiveKitOptions({ + video_codec: "av1", + video: { max_bitrate: 10_000_000, max_framerate: 60 }, + }); + const backup = opts.publishDefaults?.backupCodec as { + codec: string; + encoding: { maxBitrate: number; maxFramerate: number }; + }; + expect(backup.codec).toBe("vp8"); + expect(backup.encoding).toEqual(VideoPresets.h720.encoding); + }); +}); + +describe("getLiveKitOptions", () => { + it("reads from Config singleton", () => { + vi.mocked(Config.get).mockReturnValue({ + media_quality: { video_codec: "h264" }, + } as ReturnType); + const opts = getLiveKitOptions(); + expect(opts.publishDefaults?.videoCodec).toBe("h264"); + }); + + it("throws when Config is not initialized", () => { + vi.mocked(Config.get).mockImplementation(() => { + throw new Error("Config not initialized"); + }); + expect(() => getLiveKitOptions()).toThrow("Config not initialized"); + }); +}); diff --git a/src/livekit/options.ts b/src/livekit/options.ts index 1d4cad774..a4972f002 100644 --- a/src/livekit/options.ts +++ b/src/livekit/options.ts @@ -9,46 +9,144 @@ import { AudioPresets, DefaultReconnectPolicy, type RoomOptions, - ScreenSharePresets, type TrackPublishDefaults, type VideoPreset, VideoPresets, + VideoPreset as VideoPresetClass, } from "livekit-client"; -const defaultLiveKitPublishOptions: TrackPublishDefaults = { - audioPreset: AudioPresets.music, - dtx: true, - // disable red because the livekit server strips out red packets for clients - // that don't support it (firefox) but of course that doesn't work with e2ee. - red: false, - forceStereo: false, - simulcast: true, - videoSimulcastLayers: [VideoPresets.h180, VideoPresets.h360] as VideoPreset[], - screenShareEncoding: ScreenSharePresets.h1080fps30.encoding, - stopMicTrackOnMute: false, - videoCodec: "vp8", - videoEncoding: VideoPresets.h720.encoding, - backupCodec: { codec: "vp8", encoding: VideoPresets.h720.encoding }, -} as const; +import { Config } from "../config/Config"; +import { DEFAULT_CONFIG, type ConfigOptions } from "../config/ConfigOptions"; -export const defaultLiveKitOptions: RoomOptions = { - // automatically manage subscribed video quality - adaptiveStream: true, +/** + * Find the closest matching VideoPreset for a given height. + */ +function videoPresetForHeight(height: number): VideoPreset { + if (height <= 180) return VideoPresets.h180; + if (height <= 360) return VideoPresets.h360; + if (height <= 540) return VideoPresets.h540; + if (height <= 720) return VideoPresets.h720; + if (height <= 1080) return VideoPresets.h1080; + if (height <= 1440) return VideoPresets.h1440; + return VideoPresets.h2160; +} - // optimize publishing bandwidth and CPU for published tracks - dynacast: true, +/** + * Build LiveKit publish options from config, falling back to sensible defaults. + */ +function buildPublishOptions( + mediaQuality: ConfigOptions["media_quality"], +): TrackPublishDefaults { + const defaults = DEFAULT_CONFIG.media_quality; + const videoConf = mediaQuality?.video; + const screenConf = mediaQuality?.screen_share; + const codec = mediaQuality?.video_codec ?? defaults.video_codec; - // capture settings - videoCaptureDefaults: { - resolution: VideoPresets.h720.resolution, - }, + // Camera video encoding + const videoEncoding = { + maxBitrate: videoConf?.max_bitrate ?? defaults.video.max_bitrate, + maxFramerate: videoConf?.max_framerate ?? defaults.video.max_framerate, + }; - // publish settings - publishDefaults: defaultLiveKitPublishOptions, + // Camera simulcast layers + let videoSimulcastLayers: VideoPreset[]; + if (videoConf?.simulcast_layers) { + videoSimulcastLayers = videoConf.simulcast_layers.map( + (layer) => + new VideoPresetClass( + Math.round((layer.height * 16) / 9), + layer.height, + layer.bitrate, + videoConf?.max_framerate ?? defaults.video.max_framerate, + ), + ); + } else { + videoSimulcastLayers = [VideoPresets.h180, VideoPresets.h360]; + } - // default LiveKit options that seem to be sane - stopLocalTrackOnUnpublish: true, - reconnectPolicy: new DefaultReconnectPolicy(), - disconnectOnPageLeave: true, - webAudioMix: false, -}; + // Screen share encoding + const screenShareEncoding = { + maxBitrate: screenConf?.max_bitrate ?? defaults.screen_share.max_bitrate, + maxFramerate: + screenConf?.max_framerate ?? defaults.screen_share.max_framerate, + }; + + // Screen share simulcast layers + let screenShareSimulcastLayers: VideoPreset[] | undefined; + if (screenConf?.simulcast_layers) { + screenShareSimulcastLayers = screenConf.simulcast_layers.map( + (layer) => + new VideoPresetClass( + Math.round((layer.height * 16) / 9), + layer.height, + layer.bitrate, + layer.framerate ?? screenConf?.max_framerate ?? 30, + ), + ); + } + + return { + audioPreset: AudioPresets.music, + dtx: true, + // disable red because the livekit server strips out red packets for clients + // that don't support it (firefox) but of course that doesn't work with e2ee. + red: false, + forceStereo: false, + simulcast: true, + videoSimulcastLayers: videoSimulcastLayers as VideoPreset[], + screenShareEncoding, + ...(screenShareSimulcastLayers && { + screenShareSimulcastLayers: screenShareSimulcastLayers as VideoPreset[], + }), + stopMicTrackOnMute: false, + videoCodec: codec, + videoEncoding, + backupCodec: { + codec: "vp8", + encoding: VideoPresets.h720.encoding, + }, + } as TrackPublishDefaults; +} + +/** + * Build LiveKit RoomOptions from config. + * Call this after Config.init() has resolved. + */ +export function buildLiveKitOptions( + mediaQuality?: ConfigOptions["media_quality"], +): RoomOptions { + const videoHeight = + mediaQuality?.video?.max_resolution ?? + DEFAULT_CONFIG.media_quality.video.max_resolution; + const basePreset = videoPresetForHeight(videoHeight); + + return { + // automatically manage subscribed video quality + adaptiveStream: true, + + // optimize publishing bandwidth and CPU for published tracks + dynacast: true, + + // capture settings + videoCaptureDefaults: { + resolution: basePreset.resolution, + }, + + // publish settings + publishDefaults: buildPublishOptions(mediaQuality), + + // default LiveKit options that seem to be sane + stopLocalTrackOnUnpublish: true, + reconnectPolicy: new DefaultReconnectPolicy(), + disconnectOnPageLeave: true, + webAudioMix: false, + }; +} + +/** + * Get LiveKit options, reading from the loaded Config singleton. + * Requires Config.init() to have resolved first. + */ +export function getLiveKitOptions(): RoomOptions { + return buildLiveKitOptions(Config.get().media_quality); +} diff --git a/src/settings/DeveloperSettingsTab.tsx b/src/settings/DeveloperSettingsTab.tsx index cc15ae549..70db13db9 100644 --- a/src/settings/DeveloperSettingsTab.tsx +++ b/src/settings/DeveloperSettingsTab.tsx @@ -8,6 +8,7 @@ Please see LICENSE in the repository root for full details. import { type ChangeEvent, type FC, + type ReactNode, useCallback, useEffect, useMemo, @@ -29,12 +30,14 @@ import { InlineField, Label, RadioControl, + Separator, } from "@vector-im/compound-web"; import { type Room as LivekitRoom } from "livekit-client"; import { FieldRow, InputField } from "../input/Input"; import { Config } from "../config/Config"; import { + type Setting, useSetting, duplicateTiles as duplicateTilesSetting, debugTileLayout as debugTileLayoutSetting, @@ -43,10 +46,26 @@ import { alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting, matrixRTCMode as matrixRTCModeSetting, customLivekitUrl as customLivekitUrlSetting, + advancedScreenShare as advancedScreenShareSetting, + screenShareResolution as screenShareResolutionSetting, + screenShareFramerate as screenShareFramerateSetting, + screenShareBitrate as screenShareBitrateSetting, + screenShareCodec as screenShareCodecSetting, + advancedCamera as advancedCameraSetting, + cameraResolution as cameraResolutionSetting, + cameraFramerate as cameraFramerateSetting, + cameraBitrate as cameraBitrateSetting, + cameraCodec as cameraCodecSetting, + echoCancellationSetting, + noiseSuppressionSetting, + autoGainControlSetting, + type VideoCodec, enableExtendedLivekitLogs as enableExtendedLivekitLogsSetting, } from "./settings"; import { MatrixRTCMode } from "../config/ConfigOptions"; import styles from "./DeveloperSettingsTab.module.css"; +import settingsStyles from "./SettingsModal.module.css"; +import { Slider } from "../Slider"; import { useUrlParams } from "../UrlParams"; import { getSFUConfigWithOpenID } from "../livekit/openIDSFU"; @@ -138,6 +157,185 @@ export const DeveloperSettingsTab: FC = ({ return null; }, [livekitRooms]); + const MediaQualitySettings: React.FC<{ + id: string; + header: string; + toggleLabel: string; + description: string; + toggleSetting: Setting; + resolutionSetting: Setting; + framerateSetting: Setting; + bitrateSetting: Setting; + codecSetting: Setting; + resolutionOptions: { value: string; label: string }[]; + bitrateRange: { min: number; max: number; step: number }; + }> = ({ + id, + header, + toggleLabel, + description, + toggleSetting, + resolutionSetting, + framerateSetting, + bitrateSetting, + codecSetting, + resolutionOptions, + bitrateRange, + }): ReactNode => { + const [advancedEnabled, setAdvancedEnabled] = useSetting(toggleSetting); + const [resolution, setResolution] = useSetting(resolutionSetting); + const [framerate, setFramerate] = useSetting(framerateSetting); + const [framerateRaw, setFramerateRaw] = useState(framerate); + const [bitrate, setBitrate] = useSetting(bitrateSetting); + const [bitrateRaw, setBitrateRaw] = useState(bitrate); + const [codec, setCodec] = useSetting(codecSetting); + + return ( + <> +

{header}

+ + setAdvancedEnabled(e.target.checked)} + /> + + {advancedEnabled && ( + <> +
+ + +
+
+ + `${v} fps`} + /> +
+
+ + + `${(v / 1_000_000).toFixed(1)} Mbps` + } + /> +
+
+ + +
+ + )} + + ); + }; + + const AudioProcessingSettings: React.FC = (): ReactNode => { + const [echoCancellation, setEchoCancellation] = useSetting( + echoCancellationSetting, + ); + const [noiseSuppression, setNoiseSuppression] = useSetting( + noiseSuppressionSetting, + ); + const [autoGainControl, setAutoGainControl] = useSetting( + autoGainControlSetting, + ); + + return ( + <> +

{t("settings.audio_processing_header", "Audio processing")}

+

+ {t( + "settings.audio_processing_description", + "Changes apply on next call join.", + )} +

+ + setEchoCancellation(e.target.checked)} + /> + + + setNoiseSuppression(e.target.checked)} + /> + + + setAutoGainControl(e.target.checked)} + /> + + + ); + }; + return ( <>

@@ -166,6 +364,7 @@ export const DeveloperSettingsTab: FC = ({ id: client.getDeviceId() || "unknown", })}

+ = ({ {customLivekitUrlUpdateError} )} + {t("developer_mode.matrixRTCMode.title")} @@ -403,6 +603,61 @@ export const DeveloperSettingsTab: FC = ({
))} + + + + + + +

{t("developer_mode.environment_variables")}

{JSON.stringify(env, null, 2)}

{t("developer_mode.url_params")}

diff --git a/src/settings/SettingsModal.module.css b/src/settings/SettingsModal.module.css index b07cb4c81..bff1a30ee 100644 --- a/src/settings/SettingsModal.module.css +++ b/src/settings/SettingsModal.module.css @@ -33,3 +33,27 @@ Please see LICENSE in the repository root for full details. .volumeSlider > p { color: var(--cpd-color-text-secondary); } + +.volumeSlider > select { + display: block; + width: 100%; + padding: 8px 12px; + margin-top: var(--cpd-space-1x); + border: 1px solid var(--cpd-color-border-interactive-primary); + border-radius: 4px; + background-color: var(--cpd-color-bg-canvas-default); + color: var(--cpd-color-text-primary); + font-size: var(--font-size-body); + font-family: inherit; + cursor: pointer; +} + +.volumeSlider > select:focus { + outline: none; + border-color: var(--cpd-color-text-link-external); +} + +.settingValue { + font-weight: normal; + color: var(--cpd-color-text-secondary); +} diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index 4eb1efdd3..665eadf01 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -152,7 +152,13 @@ export const SettingsModal: FC = ({ />
- +

{t("settings.audio_tab.effect_volume_description")}

renders and matches snapshot 1`] = `

Device ID: DEVICE123

+